目录
- eBPF是什么?
-
eBPF是做什么的?可以解决什么问题?
-
eBPF可以带来的解决方案是什么?
- eBPF的技术点
- eBPF hook
- eBPF Map
- eBPF Helper Function
- eBPF有什么限制吗?
前言
21年因为项目需求而要开发一个工具,可以满足:故障排查,问题分析,统计数据等需要。开始实现的比较简单,就是针对抓取的log数据进行各个维测数据的解析和处理。具有如下几种缺陷:
1、log内容主要还是借助于简单的printf和printk的内容,这类打印信息量不足。主要是release的版本不可能有太多此类的信息,否则会严重影响系统的性能。
2、信息量少也意味着无法有效支撑问题问题的相关信息追溯,无法进行有效的数据统计。因此很多监控和买点方案就无法有效实现。
3、无法将工具有效整合到整个软件开发工程中去。所以无法得到各个方面的资源支撑。所以工具必然不能得到很好地推广而进行迭代优化。
后来发现eBPF技术能够很好地满足需求。因此开始接触了eBPF技术。趁着这段时间,准备针对这个技术做一个比较完整的学习和梳理记录。
1、eBPF是什么?
BPF:Berkeley Packet Filter,伯克利包过滤器
发明之初是一款网络过滤神器,tcpdump就是基于这个神器的神器
eBPF:extended Berkeley Packet Filter 扩展的伯克利包过滤器
演进成一套通用执行引擎,不再仅仅是网络分析,可以基于eBPF开发
性能分析,系统跟踪,网络优化等多种类型的工具平台。
原来的BPF被称为cBPF(classic BPF),当前Linux内核只运行eBPF,内核将会透明地转换成eBPF再执行
- 1)一个新的虚拟机设计,有效地工作再基于寄存器的CPU之上,基于栈的虚拟机和基于寄存器的虚拟机区别-CSDN博客
2)应用程序使用缓存只复制顾虑数据包相关的数据,不会复制数据包的所有信息,最大程度地减少处理的的数据,提高处理效率
eBPF目前发展成为一套通用的执行引擎,提供可基于系统或程序事件高效安全执行特定代码的通用能力,通用能力的使用者不再局限内核开发者。使用场景也不仅仅是网络分析。可以基于eBPF开发性能分析,系统追踪,网络优化等多种类型的工具和平台。
eBPF字节码指令在内核执行前必须通过BPF验证器的验证,同时在启用BPF JIT模式的内核中,会直接将指令转成内核可执行的本地指令运行,具有很搞笑的执行效率。
2、BPF是做什么的?可以解决什么问题?
1)linux内核开发目标:强安全,高性能,持续交付
强安全:就是不能允许不可信的代码运行在内核中。
高性能:作为承载千百服务的操作系统内核,需要有高性能的保障应用的快速发展需求。
持续交付:就是不断升级迭代更新。同时,升级动作本身对于用户而言尽量不会感知到。
2)linux内核开发的一般解决方案和缺陷
直接修改内核代码,通过API暴露能力,用户可能要等上n年才能更新到这个版本来使用。而且每次的功能更新都可能需要重新编译打包内核代码
开发新的可即时加载的内核模块,这个内核模块可能会随着每一个内核版本的发布而不可用了,因此你必须小心地为每一次的内核版本更新调整模块代码。不然就会让内核模块直接崩溃
3、BPF可以带来的解决方案是什么?
强安全:BPF验证器(verifier)通过沙盒机制检查每一个程序是否能够安全运行在内核中,拒绝不安全的代码。
高性能:JIT编译保障了程序本地运行的高性能
持续交付:程序可以被无缝替换,并不影响在运行的任务。
强安全:就是在检查不通过的,将拒绝这个程序被加载到内核中去,从而保证内核本身不会崩溃,有一个验证器进行验证这是不同于内核模块的。比如以下几种情况就是无法通过BPF验证器的:
没有实际加载BPF程序所需的权限的
访问任意内核空间内存数据的
将任意内核空间内存数据暴露给用户空间的
高性能:通过安全验证后,JIT编译生成的是通用字节码,它是完全可移植的,可以在任意CPU架构山加载这个字节码的。这样我们能获得本地编译后的程序运行速度了。
持续交付:通过了JIT编译后,就会把编译后的程序附加到各种系统调用的钩子(hook)上,而且可以在不影响系统运行的情况下,实时地在线地替换掉这些运行在Linux内核中的BPF程序。
4、eBPF的技术点
BPF钩子(hooks)
能够加快BPF程序的地方:
1)kenrel functions(kprobes)
2)userspace functions(uprobes)
3)system calls
4)fentry
5)fexit
6)Tracepoints
7)network devices(tc/xdp)
8)network routes
9)TCP congestion algorithms
10)sockets(data level)
这10种钩子就是内核中可以加载BPF程序的地方。
包括:文件打开,创建TCP链接,Socket链接到发送系统消息等几乎所有的系统调用。用户空间的各种动态信息都能加载BPF程序。
BPF Map:
BPF Map,存储数据,交换信息的桥梁
Map的类型:Hash tables,Arrays,LRU(Least Recently Used),Ring Buffer,Stack Trace,LPM(Longest Prefix match)
Map的功能:Program state,Program configuration,Share data between programs,share state,metrics,and statistics with user space
对于一个程序通常复杂的逻辑就必须要记录数据的状态的部分。BPF Map就是用来存储数据的状态信息,统计信息和指标信息的。
BPF Map是可以被用户空间访问并操作的
BPF Map是可以与BPF程序分离的,即当创建一个BPF Map的BPF程序运行结束后,该BPF Map还能存在,而不是随着程序一起消亡的。
基于上面的特点,我们可以利用BPF Map持久化数据,在不丢失重要数据的同时,更新BPF程序逻辑,实现在不同程序之间共享信息,在收集统计信息或指标等场景下,非常有用
BPF Helper Function:BPF的辅助函数
辅助函数的清单:
Random numbers
Get current time
Map access
Get process/cgroup context
Manipulate network packets and forwarding
Access socket data
Perform tail call
Access process stack
Access syscall arguments
它们是面向开发者的,提供操作BPF程序和BPF Map的工具类函数。就是一个中间件。适配内核的迭代更新,主要是内核函数的变化,而对开发者是透明的,形成稳定的API接口。
例如:BPF程序需要生成一个随机数,那么有一个辅助函数可以帮助检索并询问内核,让内核完成“给我一个随机数”的任务,或者“从BPF Map中读取某个值”等等。
只要与操作系统内核的交互都是通过BPF辅助函数来完成的,由于这些都是稳定的API,所以BPF程序可以跨内核版本进行移植。
eBPF技术有些什么限制?
eBPF程序不能调用任意的内核参数,只限于内核模块中列出的BPF Helper函数,函数支持列表也随着内核演进而不断增加
eBPF程序不允许包含无法达到的指令,防止加载无效代码,延迟程序的终止。
eBPF程序中循环次数限制且必须在有限时间内结束
eBPF堆栈大小被限制在MAXBPFSTACK。Linux5.8,被设置为512。可以改用BPF Map,大小是无限的。
eBPF字节码大小最初被限制在4096条指令,Linux5.8版本,已放宽至100万条指令(BPF_COMPLEXITY_LIMIT_INSNS),对于无权限的BPF程序,仍然保留4096条的限制(BPF_MAXINSNS)