前言
BPF是一项数据包过滤技术,它源于1992年。BPF是基于寄存器的过滤器,可以有效地工作在基于寄存器结构的CPU上;BPS使用简单无共享的缓存模型,数据包会经过BPF过滤再到内存,可以减少处理的数据量而提高性能
eBPF (extend BPF)
Linux 4.x加入了BPF对kprobes,uprobe,tracepoints和perf_events支持。成为内核级子系统,BPF看上去更像内核模块,eBPF可以在无需编译内核、无需加载内核模块的情况下,安全高效的附加代码到内核的各种事件上,对内核进行跟踪、监控(如开发性能分析工具,软件定义网络)
eBPF优势
传统Linux内核开发,每次新功能都需要修改内核代码并重新编译打包,内核工程师也可以开发及时加载的内核模块,在运行时加载到Linux内核中,但是每次内核版本更新,都会引起内核API的变化,因此不通的内核需要不同代码的内核模块,假设模块中有错误的代码还会导致内核崩溃
BPF具有安全性,BPF程序无需编译内核,并且会验证程序,确保内核本身不崩溃。BPF使用BPFJIT将BPF字节码编译成本地机器字节码,获得本地编译的运行速度
BPF支持的跟踪类型
kprobes:实现内核动态跟踪。 kprobes 可以跟踪到 Linux 内核中的函数入口或返回点,但是不是稳定 ABI 接口,可能会因为内核版本变化导致,导致跟踪失效。
uprobes:用户级别的动态跟踪。与 kprobes 类似,只是跟踪的函数为用户程序中的函数。
tracepoints:内核静态跟踪。tracepoints 是内核开发人员维护的跟踪点,能够提供稳定的 ABI 接口,但是由于是研发人员维护,数量和场景可能受限。
USDT:为用户空间的应用程序提供了静态跟踪点。
perf_events:定时采样和 PMC。
开发方式: BCC&BCC CO-RE(compile once, run everywhere)可移植性对比
BCC开发方式
开发部署: 将BPC C源码嵌入python编写的用户空间应用,然后将源码拷贝到目标机器 执行:目标机器安装LLVM&Clang, 在目标机器现场执行编译、加载、运行
缺点:
Clang和LLVM很庞大,分发BPF程序时,还必须分发这个库。这个两个还非常耗费资源,每次编译都要大量消耗系统资源,可能会导致生产问题;拖慢了测试和开发速度
BPF CO-RE开发方式(也是未来主流开发方式,因为移植性更强)
- CO-RE 引入了BPF加载程序libBPF,这个组件会将内核的BTP和BPF程序联系起来。将编译后的BPF代码适配到目标机器的特定的内核
BPF CO-RE 需要下列组件之间的紧密合作:
- BTF 类型信息:用于获取内核、BPF 程序类型及 BPF 代码的关键信息, 这也是下面其他部分的基础;
- 编译器(clang):给 BPF C 代码提供了表达能力和记录重定位(relocation)信息的能力;
- BPF loader (libbpf):将内核的 BTF 与 BPF 程序联系起来, 将编译之后的 BPF 代码适配到目标机器的特定内核;
- 内核:虽然对 BPF CO-RE 完全不感知,但提供了一些 BPF 高级特性,使某些高级场景成为可能。
为什么从BCC切换到libbpf,`https://www.pingcap.com/blog/why-we-switched-from-bcc-to-libbpf-for-linux-bpf-performance-analysis/
libBPF内核文档: https://docs.kernel.org/bpf/instruction-set.html