
什么是eBPF?
eBPF,Extended Berkeley Packet Filter,是一种强大而灵活的内核技术,允许用户在不更改内核源代码的情况下动态插入并执行自定义程序。最初设计为一个网络数据包过滤器。eBPF 如今已经发展成一个通用平台,使得用户可以在内核级别实现实时、安全且高性能的数据处理和监控。eBPF 提供了一个安全的沙箱环境,支持在内核中运行经过验证的程序,这样就可以确保内核的稳定性和安全性。eBPF 的概念源自于 Berkeley Packet Filter(BPF),后者是由贝尔实验室开发的一种网络过滤器,可以捕获和过滤网络数据包。
用户空间和内核空间
Linux 将其内存划分为两个不同的区域:内核空间和用户空间。内核空间是操作系统核心所在的位置。它可以完全且不受限制地访问所有硬件 — 内存、存储、CPU 等。由于内核访问的特权性质,内核空间受到保护,并且只允许运行最受信任的代码,其中包括内核本身和各种设备驱动程序。
用户空间是任何非内核进程运行的地方,例如常规应用程序。用户空间代码对硬件的访问有限,并且依赖于在内核空间中运行的代码来执行特权操作,例如磁盘或网络 I/O。例如,要发送网络数据包,用户空间应用程序必须通过称为“系统调用”的内核 API 与内核空间网卡驱动程序通信。
为什么要区分用户空间和内核空间
在 CPU 的所有指令中,有些指令是非常危险的,如果错用,将导致系统崩溃,比如清内存、设置时钟等。如果允许所有的程序都可以使用这些指令,那么系统崩溃的概率将大大增加。
所以,CPU 将指令分为特权指令和非特权指令,对于那些危险的指令,只允许操作系统及其相关模块使用,普通应用程序只能使用那些不会造成灾难的指令。比如 Intel 的 CPU 将特权等级分为 4 个级别:Ring0~Ring3。
其实 Linux 系统只使用了 Ring0 和 Ring3 两个运行级别(Windows 系统也是一样的)。当进程运行在 Ring3 级别时被称为运行在用户态,而运行在 Ring0 级别时被称为运行在内核态。
产生的背景
虽然在大多数情况下,系统调用接口已经足够,但开发人员可能需要更大的灵活性来添加对新硬件的支持、实现新的文件系统,甚至自定义系统调用。要做到这一点,程序员必须有一种方法可以扩展基本内核,而无需直接添加到内核源代码中。Linux 内核模块(LKM) 就是实现此功能的。与系统调用(请求从用户空间传到内核空间)不同,LKM 直接加载到内核中。LKM 最有价值的特性可能是它们可以在运行时加载,无需在每次需要新内核模块时重新编译整个内核并重新启动计算机。

Linux 内核模块(LKM, Linux Kernel Module)**是一种在不重新编译内核或重新启动系统的情况下向 Linux 内核动态添加或删除功能的方法。通过 LKM,用户可以在内核中加载新功能(如驱动程序、文件系统或安全模块),并根据需要卸载这些功能。LKM 的使用极大地增强了 Linux 系统的灵活性和扩展性,使得操作系统可以轻松适应不同的硬件和软件需求。
LKM的工作原理
LKM 以动态可加载的形式存在,编译成单独的 .ko 文件。当需要某个模块时,可以使用 insmod 或 modprobe 命令将模块加载到内核中。相应地,可以通过 rmmod 卸载模块。模块加载后成为内核的一部分,且具有与内核相同的权限和访问控制,可以直接操作硬件和系统资源。
LKM的常见用途
- 设备驱动:LKM 经常用于编写硬件设备驱动程序,使系统能够识别并与新硬件进行通信。例如,网卡、声卡、显示驱动等都是通过内核模块来实现。
- 文件系统:一些文件系统(如 NTFS、FAT)通常通过 LKM 加载,这样可以在不重启系统的情况下支持新的文件系统格式。
- 网络协议和安全模块:LKM 可用于实现新的网络协议或防火墙模块,如 Netfilter,可以动态添加网络过滤和安全功能。
- 调试和性能监控:通过 LKM,可以实现一些调试工具或监控工具,帮助开发者分析系统的运行状态、调试和优化性能。
LKM的优点
- 动态加载和卸载:LKM 可以根据需要动态加载或卸载,避免频繁重启系统,提高了系统的灵活性和开发效率。
- 节省资源:只有需要时才加载模块,可以减少内存占用和资源消耗。
- 模块化和可扩展性:LKM 提供了一种模块化的设计方法,不需要修改内核即可扩展功能。这对于开发和维护来说是非常高效的。
- 简化开发:通过使用 LKM,开发人员可以独立编写模块,而无需重新编译整个内核,大大减少了开发和测试的时间。
LKM的缺点
尽管 LKM 在扩展性上具有显著优势,但也存在一些缺点:
- 安全风险:加载到内核中的 LKM 具有内核权限,因此具有完全的系统访问权限。如果模块存在漏洞或被恶意篡改,会导致系统被攻击或崩溃。由于内核模块直接访问内存和硬件,因此任何错误可能导致严重的安全问题。
- 稳定性问题:LKM 的错误可能导致整个内核崩溃。内核模块在内核空间运行,一旦出错将直接影响系统稳定性,导致系统崩溃或内核崩溃(内核 panic)。
- 兼容性问题:内核模块依赖特定的内核版本或配置,内核升级后模块可能不再兼容,这使得模块的维护变得复杂。如果模块未及时更新或编译不兼容的版本,将无法正常使用。
- 调试难度:LKM 调试复杂度高。由于 LKM 直接在内核空间中运行,一旦出错可能会导致整个系统崩溃,调试过程较为困难,需要借助特定的工具和环境。
- 性能影响:加载和卸载模块会带来一定的性能开销,特别是在频繁加载和卸载的场景下,可能导致系统性能波动。
eBPF 有什么作用?
eBPF 是一种较新的机制,用于编写在 Linux 内核空间中执行的代码,该机制已被用于创建网络、调试、跟踪、防火墙等程序。
eBPF 诞生于对更好的 Linux 跟踪工具的需求,它从 dtrace中汲取了灵感,dtrace 是一种动态跟踪工具,主要用于 Solaris 和 BSD 操作系统。与 dtrace 不同,Linux 无法获得正在运行系统的全局概览,因为它仅限于系统调用、库调用和函数的特定框架。在 Berkeley Packet Filter (BPF)(一种使用内核 VM 编写打包过滤代码的工具)的基础上,一小群工程师开始扩展 BPF 后端以提供一组与 dtrace 类似的功能。eBPF 就此诞生。2014 年首次在 Linux 3.18 中有限发布,要充分利用 eBPF 至少需要 Linux 4.4 或更高版本。
eBPF原理
eBPF 的工作原理主要分为三个步骤:加载、编译和执行。

eBPF 需要在内核中运行。这通常是由用户态的应用程序完成的,它会通过系统调用来加载 eBPF 程序。在加载过程中,内核会将 eBPF 程序的代码复制到内核空间。
eBPF 程序需要经过编译和执行。这通常是由Clang/LLVM的编译器完成,然后形成字节码后,将用户态的字节码装载进内核,Verifier会对要注入内核的程序进行一些内核安全机制的检查,这是为了确保 eBPF 程序不会破坏内核的稳定性和安全性。在检查过程中,内核会对 eBPF 程序的代码进行分析,以确保它不会进行恶意操作,如系统调用、内存访问等。如果 eBPF 程序通过了内核安全机制的检查,它就可以在内核中正常运行了,其会通过通过一个JIT编译步骤将程序的通用字节码转换为机器特定指令集,以优化程序的执行速度。
在内核中运行时,eBPF 程序通常会挂载到一个内核钩子(hook)上,以便在特定的事件发生时被执行。例如,
系统调用——当用户空间函数将执行转移到内核时插入
函数进入和退出——拦截对预先存在的函数的调用
网络事件 – 在收到数据包时执行
最后 eBPF Maps,允许eBPF程序在调用之间保持状态,以便进行相关的数据统计,并与用户空间的应用程序共享数据。一个eBPF映射基本上是一个键值存储,其中的值通常被视为任意数据的二进制块。它们是通过带有BPF_MAP_CREATE参数的bpf_cmd系统调用来创建的,和Linux世界中的其他东西一样,它们是通过文件描述符来寻址。与maps的交互是通过查找/更新/删除系统调用进行的
总之,eBPF 的工作原理是通过动态加载、执行和检查无损编译过的代码来实现的。
eBPF的优势
- 高性能:由于 eBPF 程序运行在内核中,消除了用户态和内核态的频繁切换,极大地降低了延迟和资源消耗。
- 灵活性:eBPF 可以在不重新编译内核的情况下动态加载、执行代码,满足不同的需求场景,提供灵活的监控和处理功能。
- 安全性:eBPF 程序必须经过内核验证器的严格检查,确保其不会破坏系统稳定性。同时 eBPF 运行在一个沙箱中,防止不安全的操作。
- 可移植性:eBPF 程序可以跨不同版本的 Linux 内核运行,减少了对系统版本的依赖。
eBPF的应用实例
- 监控工具(如BCC和bpftrace):基于 eBPF 的开源工具,帮助开发者和运维人员更方便地使用 eBPF 功能。BCC 是一个集合库,提供了大量内置的分析脚本,而 bpftrace 是一个类似于 awk 的工具,可以方便地编写动态的 eBPF 程序。
- 网络工具(如Cilium):Cilium 是一个基于 eBPF 的网络和安全工具,用于 Kubernetes 等容器环境中,提供低延迟、高性能的服务。
- 安全工具(如Falco):Falco 是一个基于 eBPF 的实时监控工具,帮助检测不安全的行为和潜在的攻击事件。
未来发展
eBPF 的灵活性和性能优势使其成为 Linux 内核生态中不可或缺的技术。随着云原生、容器化和微服务架构的发展,eBPF 在可观察性、安全性、网络和性能优化等领域的作用将会越来越重要。未来,我们可能会看到更多基于 eBPF 的工具和平台,为开发者和运维人员提供更加全面、灵活的内核级别解决方案。