BPF本质上是一种操控内核的开发技术,所以要彻底明白BPF就要先了解内核的相关知识。
Linux两大运行空间
CPU将指令分为特权指令和非特权指令,对于那些危险的指令,只允许操作系统及其相关模块使用。操作系统的核心是内核(kernel),可以访问受保护的内存空间和硬件设备,也就是能执行这些特权指令,而用户进程不能直接操作内核,只能执行非特权指令。
出于内核安全方面的考虑,操作系统将虚拟地址空间划分为两部分,一部分为内核空间,另一部分为用户空间。通过区分内核空间和用户空间的设计,隔离了操作系统代码与应用程序代码。即便是单个应用程序出现错误也不会影响到操作系统的稳定性,这样可以最大限度减少用户的危险操作,增加系统稳定性。
用户进程如果要进行磁盘、内存、网络等读写时先要向内核发起“内核调用”的请求,由用户态切换到内核态,完成操作后如果要在内核空间获取数据则需要进行一次拷贝,把数据由内核空间拷贝到用户空间中,最后由内核态切换回用户态,读走数据。
BPF的出现
了解了上面两大空间的存在原因和工作原理后,那么我们遇到了个很棘手的问题。跟网络相关的进程要获取数据的话必须要对大量的数据包进行内核空间和用户空间的复制和切换,即便只是为了获取很少量的数据也要进行全部的拷贝,利用率和效率极低!BPF的出现正是为了解决这类问题。
解题思路也很简单,在内核中订阅一些过滤器,当内核读取到网络数据包后直接在内核中完成过滤,将少量信息拷贝到用户空间,可以显著的提高性能和利用率。
从出现的原因就可以推测出BPF的两大核心功能就是过滤和复制。
eBPF和cBPF的区别
上述BPF仍然有优化的空间。假如在内核空间和用户空间之间开辟一块“共享空间”,内核将数据直接存入,用户在内核外直接可以把数据取走,这样就没有了数据复制的过程。这就是eBPF的第一个进化的特性“Map空间”。随着BPF的成功,大家发现BPF这种架构设计不仅可以适用于网络,还可以适用于更多的应用场景,所以丰富了更多的事件类型用于支持更多的应用场景,这是eBPF的第二个进化。
早期的BPF为了和eBPF(extended)区分开来,被称为cBPF(classical)。本质上cBPF只是eBPF网络模块的一部分功能。
BCC的世界
BPF功能虽然强大,但是实现过程过于复杂,需要精通汇编语言,对于普通开发者来说理解其原理已经非常困难了,汇编的门槛也很高,即便有汇编能力还要处理编译、解析、加载、map空间分配等工作。
BCC是BPF Compiler Collection的简称,是围绕BPF功能的python库。使用BCC进行BPF开发只需要开发者利用C语言设计BPF能力,余下的工作都是由BCC一力承担,实现BPF的一站式编程。面向对象的C比起汇编门槛降低很多,而且一站式服务能力隐藏了内部的复杂性。
总结
为了减少空间交换所以有了BPF,BPF发展到一定阶段老的变成了cBPF、新的成了eBPF,为了方便BPF开发所以有了BCC。