lyft|入门即享受!coolbpf 硬核提升 BPF 开发效率( 三 )


BPF Map 本质上是以键/值方式存储在内核中的数据结构 。 在内核空间的程序创建 BPF Map 并返回对应的文件描述符 , 在用户空间运行的程序就可以通过这个文件描述符来访问并操作 BPF Map 。
根据申请内存方式的不同 , BPF Map 有很多种类型 , 常用的类型是BPF_MAP_TYPE_HASH 和 BPF_MAP_TYPE_ARRAY , 它们背后的内存管理方式跟我们熟悉的哈希表和数组基本一致 。 随着多 CPU 架构的成熟发展 , BPF Map 也引入了per-cpu 类型 , 如 BPF_MAP_TYPE_PERCPU_HASH、BPF_MAP_TYPE_PERCPU_ARRAY 等 , 每个 CPU 都会存储并看到它自己的 Map 数据 , 从属于不同 CPU 之间的数据是互相隔离的 。
下面是描述 BPF map 的枚举结构:
enum bpf_map_type { BPF_MAP_TYPE_UNSPEC BPF_MAP_TYPE_HASH BPF_MAP_TYPE_ARRAY BPF_MAP_TYPE_PROG_ARRAY BPF_MAP_TYPE_PERF_EVENT_ARRAY BPF_MAP_TYPE_PERCPU_HASH BPF_MAP_TYPE_PERCPU_ARRAY BPF_MAP_TYPE_STACK_TRACE BPF_MAP_TYPE_CGROUP_ARRAY BPF_MAP_TYPE_LRU_HASH BPF_MAP_TYPE_LRU_PERCPU_HASH BPF_MAP_TYPE_LPM_TRIE BPF_MAP_TYPE_ARRAY_OF_MAPS BPF_MAP_TYPE_HASH_OF_MAPS BPF_MAP_TYPE_DEVMAP BPF_MAP_TYPE_SOCKMAP BPF_MAP_TYPE_CPUMAP BPF_MAP_TYPE_XSKMAP BPF_MAP_TYPE_SOCKHASH BPF_MAP_TYPE_CGROUP_STORAGE BPF_MAP_TYPE_REUSEPORT_SOCKARRAY BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE BPF_MAP_TYPE_QUEUE BPF_MAP_TYPE_STACK BPF_MAP_TYPE_SK_STORAGE BPF_MAP_TYPE_DEVMAP_HASH BPF_MAP_TYPE_STRUCT_OPS BPF_MAP_TYPE_RINGBUF BPF_MAP_TYPE_INODE_STORAGE BPF_MAP_TYPE_TASK_STORAGE BPF_MAP_TYPE_BLOOM_FILTER; 三、BPF 的开发姿势 3.1 BPF 开发框架

上图就是非常经典的 BPF 开发框架图了 , 一般开发流程都是先将用户编写的特定程序通过 LLVM 编译为 BPF 字节码 , 在注入到内核中时会经过 verifier 严格的检查 , 确保代码不会出现死循环、宕机之类的问题 , 然后再通过jit将其翻译为 native code 进行执行 , 用户可以通过查看内核透出的数据 , 了解系统的运行情况 。
虽然 BPF 有非常多的应用 , 但使用的时候也有许多的限制 。 比如:
不能有不确定的循环操作;内核要确保执行流一定可以从BPF程序中出来 。不允许睡眠;睡眠可能导致内核执行流一直出不来 。不允许修改内核数据结构;一般不能修改数据报文 。栈空间有限;当前只有 512 字节 。不允许被直接调用内核函数;必须通过辅助函数 。3.2 开源工具和平台
BPF的编程分为两部分 , 一部分是运行在内核态 , 这是 BPF 编程的核心;另一部分是运行在用户态 , 这部分代码主要用来加载BPF , 采集并处理数据等 。
libbpf
libbpf是官方的用户态库 。 和编译普通的c程序会得到一个.o文件一样 , LLVM编译完BPF 程序也会得到一个的.o文件(一般我们命名为xx.bpf.o) 。 这个 bpf.o 文件按照 elf 的格式组织 BPF 程序的字节码、map定义以及符号等信息 , libbpf 库负责解析这些信息 , 创建 map , 注入 BPF 程序到内核工作 。 因此 , 对于用户来说 , 需要做很多繁琐的工作:
用 C 语言来编写 BPF 程序; 编写 makefile , 调用 LLVM 编译生成 .o 文件; 调用 libbpf 的接口加载 .o 文件; 使用 libbpf 的接口获取 map 中的数据 。BCC
BCC 是如今最热门也是对新手最友好的开源平台 。 它用 python 封装了编译、加载和读取数据的过程 , 提供了很多非常好用的 API 。
和 libbpf 需要提前把 bpf 程序编译成 bpf.o 不同 , BCC 是在运行时才调用 LLVM 进行编译的 , 所以要求用户环境上有 llvm 和 kernel-devel 。 这个就会像后面我们提到的 , 它会出现运行时偏移导致的瞬时资源冲高问题 。
bpftrace
bpftrace 是单命令工具 , 让用户像使用脚本一样使用 BPF 。 它自定义了自己的 DSL 作为前端 , 底层也是调用 LLVM 的 。 事实上 , 它依赖于 BCC 提供的 libbcc.so 文件 。