eBPF Talk: 全局变量、常量与 bpf map
文章目录
全局变量的用法请参考 eBPF Talk: 全局变量实战指南。
常量的使用例子请参考 为 eBPF 程序注入黑魔法。
有同事提了个问题:在 eBPF 运行的时候是怎么访问它们的内存的呢?
疑惑
被问到的时候,一脸懵逼,无从回答。
按照曾经的经验,全局变量和常量都是通过 bpf map 进行管理的。那么,在 eBPF 运行的时候是不是通过 bpf_map_lookup_elem()
去访问它们呢?
全局变量、常量的设计目标是对标 C 语言的全局变量、常量,在 eBPF 运行的时候是不是直接访问它们的内存呢?
层层解剖
我们知道,eBPF 程序需要经过编译、加载、加载进内核、verify、JIT、attach 等步骤后,才能真正运行起来。
解剖编译得到的 eBPF 汇编
因为 llvm-objdump -g -S xxx_bpfel.o
里没有源代码,所以使用 cilium/ebpf 来查看编译后的 eBPF 汇编吧。参考 eBPF Talk: bpf2bpf 特性揭秘。
得到全局变量、常量、eBPF map 的汇编,如下:
|
|
解剖 JIT 前的 eBPF 汇编
在 attach 后 eBPF 程序运行的时候,使用 bpftool prog dump xlated id ${PROG ID}
查看 JIT 前的 eBPF 汇编。
得到全局变量、常量、eBPF map 的汇编,如下:
|
|
解剖 JIT 后的机器汇编
在 attach 后 eBPF 程序运行的时候,使用 bpftool prog dump jited id ${PROG ID}
查看 JIT 后的机器汇编。
得到全局变量、常量、eBPF map 的汇编,如下:
|
|
小结
纵观这 3 段汇编代码片段,可知对于全局变量、常量的访问并不是通过 bpf_map_lookup_elem()
函数去访问的,而是直接访问它们的内存。这可由第 3 段的机器汇编分析得到。
全局变量及其 eBPF map
通过 eBPF Talk: 全局变量实战指南 可知,eBPF 使用了一个名称是 .bss
的 eBPF map 来管理全局变量。当 eBPF 程序运行起来后,可以使用 bpftool 查看 .bss
eBPF map:
|
|
而在最初的 eBPF 实现中,在 eBPF 程序运行的时候的确是通过一次 bpf map 查询来访问全局变量的。
在 5.2 内核的 bpf: implement lookup-free direct value access for maps 这次 patch 里,就实现了无需 bpf map 查询的访问方式,即直接访问全局变量所在的内存地址。
在 eBPF 程序运行的时候,因为访问全局变量是不上锁的,所以如果想要在运行的时候去变更全局变量,需要使用原子性操作,即使用 __sync_fetch_and_XXX()
系列函数去变更全局变量(参考 [BPF] support atomic instructions)。
对于用户态应用程序而言,把全局变量当作一个普通的 ARRAY 类型的 bpf map 对待即可,可按需通过 LOOKUP, UPDATE 等 bpf map 操作来读取、变更全局变量。
全局变量的更新
当用户态应用程序通过 bpf map 的 UPDATE 操作更新全局变量时,具体是怎么更新的呢?如果运行中的 eBPF 程序同时变更全局变量,会发生什么?
直接看下内核更新 ARRAY bpf map 的函数调用栈吧。
|
|
最终,是使用 memcpy()
去更新全局变量的。
然而,memcpy()
不会保证内存更新的原子性,所以如果运行中的 eBPF 程序同时变更全局变量,则有可能会被覆盖掉、也有可能只更新了一部分。
关于全局变量的使用建议:
- 要么用户态应用程序可读可写全局变量,而 eBPF 程序只读全局变量。
- 要么用户态应用程序只读全局变量,而 eBPF 程序可读可写全局变量。
常量及其 eBPF map
类似于全局变量,常量使用了一个名称是 .rodata
的只读的 ARRAY bpf map 来管理。当 eBPF 程序运行起来后,可以使用 bpftool 查看 .rodata
eBPF map:
|
|
eBPF 程序对常量的访问也是在 5.2 内核的 bpf: implement lookup-free direct value access for maps 这次 patch 里,就实现了无需 bpf map 查询的访问方式,即直接访问常量所在的内存地址。
对于用户态应用程序而言,常量 是一个只读不可写的 ARRAY 类型的 bpf map,只可通过 LOOKUP 操作进行读取。
总结
在 eBPF 运行的时候直接访问全局变量、常量的内存。
在用户态应用程序中,可以 LOOKUP、UPDATE 全局变量,只可 LOOKUP 常量(ARRAY bpf map 不支持 DELETE)。
eBPF 程序里推荐使用 __sync_fetch_and_XXX()
原子性地操作全局变量,以此保障内存安全。
全局变量的用法建议:
- 要么用户态应用程序可读可写全局变量,而 eBPF 程序只读全局变量。
- 要么用户态应用程序只读全局变量,而 eBPF 程序可读可写全局变量。
文章作者 Leon Hwang
上次更新 2023-05-21