背景

作为 Linux 开发者经常需要知道系统内部各个子系统的使用情况,可以使用 top、iostat、vmstat 等对各个子系统进行监控的工具,也有 sar、perf 等进行更细粒度采集分析的工具。但往往在解决系统实际问题的过程中,需要将多个工具组合分析,缺少针对于特定业务场景、具体细节的精准分析工具。eBPF 的出现,使根据业务灵活、安全地定制监控工具成为可能,但同时也带来了代码碎片化,重复的问题。

eBPF 在 2014 年引入内核后,逐渐演进成为一个通用执行引擎,可基于此开发性能分析工具,网络过滤器等诸多场景。并扩展到用户空间,使得应用不再局限于网络栈。eBPF 程序架构强调安全性和稳定性,看上去像内核模块,但却并不需要重新编译内核。而且在 eBPF 程序运行中,不会因为程序问题造成系统的崩溃。因此,eBPF 在网络、观测、安全和系统调优等领域发挥了重要作用。相比于现有工具的组合,针对业务特点的 eBPF 监控工具能够更有效的反应系统使用状况和暴露潜在问题。

eBPF 是什么

eBPF (Extended Berkeley Packet Filter) 扩展的伯克利数据包过滤器

BCC (BPF Compiler Collection)

eBPF 是一种数据包过滤技术,是从 BPF (Berkeley Packet Filter) 技术扩展而来的。顾名思义 BPF 来源于伯克利大学,最早应用于网络数据包过滤器,它比当时最先进的抓包技术快 20 倍,其主要得利于它的两个设计:

  1. 内核态引入一个新的虚拟机,所有指令都在内核虚拟机中运行。
  2. 用户态使用 BPF 字节码来定义过滤表达式,然后传递给内核,由内核虚拟机解释执行。

BPF 提供了一种在内核事件和用户程序事件发生时安全注入代码的机制,这就让非内核开发人员也可以对内核进行控制。

  • eBPF 则是对对 BPF 的一些扩展,将原本单一的数据包过滤事件逐步扩展到了内核态函数、用户态函数、跟踪点、性能事件(perf_events)以及安全控制等。
  • eBPF 不仅扩展了寄存器的数量,引入了全新的 BPF 映射存储。
  • eBPF 程序架构强调安全性和稳定性,看上去更像内核模块,但与内核模块不同,eBPF 程序不需要重新编译内核,并且可以确保 eBPF 程序运行完成,而不会造成系统的崩溃。
  • eBPF 程序并不像常规的线程那样,启动后就一直运行在那里,它需要事件触发后才会执行。这些事件包括系统调用、内核跟踪点、内核函数和用户态函数的调用退出、网络事件,等等。借助于强大的内核态插桩(kprobe)和用户态插桩(uprobe),eBPF 程序几乎可以在内核和应用的任意位置进行插桩。

eBPF 架构

eBPF 分为用户空间程序内核程序两部分:

  1. 用户空间程序负责加载 BPF 字节码至内核,如需要也会负责读取内核回传的统计信息或者事件详情。
  2. 内核中的 BPF 字节码负责在内核中执行特定事件,如需要也会将执行的结果通过 maps 或者 perf-event 事件发送至用户空间。
  3. 其中用户空间程序与内核 BPF 字节码程序可以使用 map 结构实现双向通信,这为内核中运行的 BPF 字节码程序提供了更加灵活的控制

bcc

通常借助 LLVM 把编写的 eBPF 程序转换为 BPF 字节码,然后再通过 bpf 系统调用提交给内核执行。内核在接受 BPF 字节码之前,会首先通过验证器对字节码进行校验(不能包含无限循环、不能导致内核崩溃、必须在有限时间内完成),只有校验通过的 BPF 字节码才会提交到即时编译器执行。BPF 程序可以利用 BPF 映射(map)进行存储,而用户程序通常也需要通过 BPF 映射同运行在内核中的 BPF 程序进行交互。如下图所示,在性能观测中,BPF 程序收集内核运行状态存储在映射中,用户程序再从映射中读出这些状态。

bcc2

eBPF 优势

  1. 速度和性能eBPF 可以将数据包处理这项工作从内核空间转移到用户空间。同时,eBPF 还支持即时(JIT) 编译器。在字节码被(JIT)编译完成后,会直接调用 eBPF,而不是对每个方法的字节码进行新的解释。
  2. 低侵入性。 当作为调试器时,eBPF 不需要停止程序来观察其状态。
  3. 安全。 程序被有效地沙箱化了,这意味着内核源代码仍然受到保护并保持不变。eBPF 程序的验证步骤确保资源不会被运行无限循环的程序阻塞。
  4. 方便。 创建 hook 内核函数的代码比构建和维护内核模块的工作要少。
  5. 统一追踪eBPF 提供了一个单一、强大且易于访问的流程跟踪框架,这增加了可见性和安全性。
  6. 可编程性。 使用 eBPF 有助于在不添加额外层的情况下增加环境的功能丰富性。由于代码直接在内核中运行,因此可以在 eBPF 事件之间存储数据,而不是像其他跟踪器那样转储数据。
  7. 表现力eBPF 具有丰富的表现力,能够执行通常只能在高级语言中才能找到的功能。

eBPF 相关工具

BCC

BCCBPF 的编译工具集合,前端提供 Python/Lua API,本身通过 C/C++ 语言实现,集成 LLVM/Clang 对 BPF 程序进行重写、编译和加载等功能,提供一些更人性化的函数给用户使用。BCC通常用在开发复杂的 eBPF 程序中,其内置的各种小工具也是目前应用最为广泛的 eBPF 小程序。

bcc3

bpftrace

bpftraceeBPFBCC 之上构建了一个简化的跟踪语言,通过简单的几行脚本,就可以实现复杂的跟踪功能。因此,在编写简单的 eBPF 程序,特别是编写的 eBPF 程序用于临时的调试和排错时,可以考虑直接使用 bpftrace ,而不需要用 C 或 Python 去开发一个复杂的程序。

bcc4

libbpf

libbpf 是从内核中抽离出来的标准库,用它开发的 eBPF 程序可以直接分发执行,这样就不需要每台机器都安装 LLVM 和内核头文件了。不过,它要求内核开启 BTF 特性,需要非常新的发行版才会默认开启(如 RHEL 8.2+ 和 Ubuntu 20.10+ 等)。

Refer