xdp-tutorial-Base

xdp-tutorial-Base


  • xdp-tutorial 教程中 base1-4 笔记
  • 资料来源:
    • https://github.com/xdp-project/xdp-tutorial
  • 更新

导语

xdp-tutorial 是非常好的 xdp 教程, 这一节先从入门开始.

流水账, 非翻译, 只记录了部分内容.

Base

01

最简单的 xdp 程序, 通过所有流量.编译后是 xdp_pass_kern.o

  • section xdp 的名字是 SEC("xdp")

加载 xdp 程序当然可以使用 ip 命令

ip 命令加载是直接加载到 nic, 因此一次 nic 只能加载一个 xdp 程序, 但是 xdp-loader 实现了 xdp nult-dispatch 更加强大更加丰富

自然也有使用用户程序加载, 简单来说是 libxdp

  • xdp_program__create -> xdp_program__attach
  • xdp_program__fd 获取程序的 fd
  • 这里警告似乎是无所谓;
  • 两次加载的 Prio 是完全相同的, 使用其他加载方式也是一样
  • xdp_multiprog__detach 卸载全部的 api

02

一个 elf 文件可以包含多个 xdp 程序, 使用 libxdp 选择需要加载的程序.

libbpf libxdp 提供了系统调用的封装和对应的操作 api

在 libxdp 内附加到一个 nic 的 xdp 程序以 struct xdp_program 表示;

  • xdp_program_opts
  • xdp_program__create() 创建
  • xdp_program__bpf_obj(prog) 转换为 bpf_obj

02

先初始化程序

  • testenv.sh 可以做到的事情实在是太多了. 初始化多个测试环境, 切换不同的测试环境 xxx
  • 应该是通过不同的网络命名空间隔离, 使用 veth 连接;

xdp_prog_kern.c 是使用 libxdp 加载卸载 xdp 程序的示例, 几个需要留意的点

03

小结: 本节介绍了 bpf 程序中存储机制, 如何在 kernel userspace 中共享/修改数据.

  • BPF_MAP_TYPE_ARRAYBPF_MAP_TYPE_PERCPU_ARRAY 两种类型的 bpf map
  • 两个实际的例子

定义数据结构, 这个部分在 bpf 程序中

BPF_MAP_TYPE_ARRAY 数组

  • 固定长度 预先分配, 32 整数索引, 访问 O(1)
  • 多个 cpu 共享无碍

struct bpf_object 代表了 elf 文件和其全部内容, map 也在其中. 通过名称找到 map 再找到 map 的 fd

  • find_map_fd -> bpf_map__fd = bpf_object__find_map_fd_by_name

用户空间读取 map:

  • bpf_map_lookup_elem: 将值存入用户传入指针 (内核也能调用, 两者实现有一些区别)
  • bpf_obj_get_info_by_fd: 查看 map 本身的属性

字节计数器

数据结构定义在 bpf 程序中

elf 文件中不存在 map 中 value 的具体类型只有 value 的长度, 类型安全由 用户 or 内核包装.

xdp 中获取 pkt 的长度 -> struct xdp_md, 这个结构体有点意思: (Note: type __u32 is NOT the real-type)

  • int xdp_prog_simple(struct xdp_md *ctx)
  • lock_xadd 是 bpf 提供的原子操作函数

用户空间读取不再赘述.

Pre CPU 统计

lock_xadd 的时间成本很高, 原子操作 + 内存屏障;

更好的做法是: pre cpu, 每个 cpu 都有一个数据的副本, 读写都是在自己的副本上;

求和的成本就转移到了 userspace;

对于 BPF_MAP_TYPE_PERCPU_ARRAY 调用 bpf_map_lookup_elem 返回的是这个 数据类型 * cpu 数量

04

03 节中对用户空间读取 bpf map 还有一个隐形限制: 必须是加载 bpf 的程序才能读取到 map.

破局的关键: 可以将 bpf map 挂载到文件系统,公开访问;

base-04 将 03 的程序进一步划分成了:

  • xdp_loader.c
  • xdp_stats.c: 如果是仅读取 map 甚至不需要 libxdp, libbpf 就足够了.

将 bpf_map pin 到文件系统 -> bpf_object__pin_maps (struct bpf_object *obj, const char *path)

  • 与其他 libxdp 的函数一样, 包接包送,一学就会.
  • 传入的子路径如何不存在, 这个函数也会直接给你创建;

bpf_map 的挂载流程 -> pin_maps_in_bpf_object

  • 多了对程序启动前遗留状态的处理;
  • 在子路径已存在文件, 就先 int bpf_object__unpin_maps(struct bpf_object *obj, const char *path) 卸载
  • 最后挂上 map

原文特别提了一点: xdp_loader.c 的挂载并不会重用已存在的 map

  • 说人话就是,一个 map 你通过 xdp_loader.c 挂载两次,已有的 map 就删掉了, 第二次是一个新创建的 map;
  • 但是通过 iproute2 命令不会, 第二次挂载时会尝试将 已有的 map 关联到 bpf 程序上, 即使 bpf 程序逻辑变了, 只要 map 定义不变, 已有数据在新 bpf 程序中就能得到保留.

那么如何在 xdp_loader.c 中实现 iproute2 的效果? 教程给出了示例:

这一节内容可能比较适合对 bpf 程序的监控? maybe

尾巴

这一篇其实也出现了拖延症, 但是只看短期目标还是好的, 一个一个子任务无限拆解, 快速反馈.这样才是自己的节奏.

Generated by RSStT. The copyright belongs to the original author.

Source

Report Page