2. eBPF简介

587 阅读8分钟

1. 参考资料

在eBPF的学习过程中,主要是跟着Linux内核之旅开源社区进行学习,在此非常感谢Linux内核之旅开源社区让我少走了很多的弯路,这种乐于分享的精神值得敬佩与学习。

EBPF学习——EBPF简介、安装和简单示例

高效入门eBPF

2. eBPF简介

eBPF,它的全称是“Extended Berkeley Packet Filter”,是一种可以在 Linux 内核中运行用户编写的程序,在指定事件发生时进行执行,而不需要修改内核代码或加载内核模块的技术。

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

  • 用户空间程序负责加载 BPF 字节码至内核,如需要也会负责读取内核回传的统计信息或者事件详情;
  • 内核中的 BPF 字节码负责在内核中发生特定事件时执行,如需要也会将执行的结果通过 maps 或者 perf-event 事件发送至用户空间;

其中用户空间程序与内核 BPF 字节码程序可以使用 map 结构实现双向通信,这为内核中运行的 BPF 字节码程序提供了更加灵活的控制。

用户空间程序与内核中的 BPF 字节码交互的流程主要如下:

  1. 我们可以使用 LLVM 或者 GCC 工具将编写的 BPF 内核代码程序编译成 BPF 字节码;

  2. 然后使用用户空间的加载程序 Loader 将字节码加载至内核;内核使用验证器(verfier) 组件保证执行字节码的安全性,以避免对内核造成灾难,在确认字节码安全后,然后内核根据跟踪方法,在有事件触发时执行BPF程序。BPF 观测技术相关的程序类型可能是 kprobes/uprobes/tracepoint/perf_events 中的一个或多个,其中:

    • kprobes:实现内核中动态跟踪。 kprobes 可以跟踪到 Linux 内核中的函数入口或返回点,但是不是稳定 ABI 接口,可能会因为内核版本变化导致,导致跟踪失效。
    • uprobes:用户级别的动态跟踪。与 kprobes 类似,只是跟踪的函数为用户程序中的函数。
    • tracepoints:内核中静态跟踪。tracepoints 是内核开发人员维护的跟踪点,能够提供稳定的 ABI 接口,但是由于是研发人员维护,数量和场景可能受限。
    • perf_events:定时采样和 PMC。
  3. 内核中运行的 BPF 字节码程序可以使用两种方式将测量数据回传至用户空间

    • maps 方式可用于将内核中实现的统计摘要信息(比如测量延迟、堆栈信息)等回传至用户空间;
    • perf-event 用于将内核采集的事件实时发送至用户空间,用户空间程序实时读取分析;

image.png

其中,bpf注入程序加入了更复杂的verifier 机制,在运行注入程序之前,先进行一系列的安全检查,最大限度的保证系统的安全。具体来说,verifier机制会对注入的程序做两轮检查:

  • 首轮检查(First pass,实现于check_cfg())可以被认为是一次深度优先搜索,主要目的是对注入代码进行一次 DAG(Directed Acyclic Graph,有向无环图)检测,以保证其中没有循环存在,除此之外,一旦在代码中发现以下特征,verifier 也会拒绝注入:
  1. 代码长度超过上限;
  2. 存在可能会跳出 eBPF 代码范围的 JMP,这主要是为了防止恶意代码故意让程序跑飞;
  3. 存在永远无法运行(unreachable)的 eBPF 指令,例如位于 exit 之后的指令;
  • 次轮检查(Second pass,实现于do_check())较之于首轮则要细致很多:在本轮检测中注入代码的所有逻辑分支从头到尾都会被完全跑上一遍,所有指令的参数(寄存器)、访问的内存、调用的函数都会被仔细的捋一遍,任何的错误都会导致注入程序失败。

3. eBPF的优势

一直以来Linux内核提供了各种各样的debug接口方便开发者和使用者观测内核内部运行信息,比如debugfs下的ftrace、kprobe等。但是仍然无法满足更细节的数据观测,比如某模块的参数传递、改变过程,或者某函数的抖动。通常根据问题的不同,观察重点也会不一样,通常需要修改内核源代码或加载hack的内核模块,非常不方便。

eBPF却可以在用户态将C语言编写的一小段“内核代码”注入到内核中运行,无需hack内核源码,而是运行在内核或应用的hook点,包括系统调用、函数入口和出口、内核tracepoints、网络events等等。

如果内核hook点不存在,可以支持创建kernel probe或user probe来运行eBPF程序。所以eBPF程序几乎可以运行在内核或用户态的任意地方,非常方便跟踪或提取数据。

随着linux内核对ebpf支持的完善,ebpf渐渐被各个公司应用在监控和性能等领域,效果卓越。

4. eBPF的应用场景

  1. 追踪和性能分析(Tracing & Profiling)

    将 eBPF 程序附加到跟踪点以及内核和用户应用探针点的能力,使得应用程序和系统本身的运行时行为具有前所未有的可见性。通过赋予应用程序和系统两方面的检测能力,可以将两种视图结合起来,从而获得强大而独特的洞察力来排除系统性能问题。先进的统计数据结构允许以高效的方式提取有意义的可见性数据,而不需要像类似系统那样,通常需要导出大量的采样数据。

  2. 观测和监控(Obervability & Monitoring)

    eBPF 不依赖于操作系统暴露的静态计数器和测量,而是实现了自定义指标的收集和内核内聚合,并基于广泛的可能来源生成可见性事件。这扩展了实现的可见性深度,并通过只收集所需的可见性数据,以及在事件源处生成直方图和类似的数据结构,而不是依赖样本的导出,大大降低了整体系统的开销。

  3. 网络(Network)

    可编程性和效率的结合使得 eBPF 自然而然地满足了网络解决方案的所有数据包处理要求。eBPF 的可编程性使其能够在不离开 Linux内核的包处理上下文的情况下,添加额外的协议解析器,并轻松编程任何转发逻辑以满足不断变化的需求。JIT 编译器提供的效率使其执行性能接近于本地编译的内核代码。

  4. 安全(Security)

    在看到和理解所有系统调用的基础上,将其与所有网络操作的数据包和套接字级视图相结合,可以采用革命性的新方法来确保系统的安全。虽然系统调用过滤、网络级过滤和进程上下文跟踪等方面通常由完全独立的系统处理,但 eBPF 允许将所有方面的可视性和控制结合起来,以创建在更多上下文上运行的、具有更好控制水平的安全系统

  5. 系统运行时分析

    学习操作系统和应用程序的学生,可以使用eBPF工具以新的和自定义的方式分析正在运行的系统。学生无需学习抽象的内核技术,而是可以对其进行跟踪并实时了解它们的运行方式。

5. eBPF编程

eBPF 提供多种使用方式:BCC、bpftrace、libbpf C/C++ Library、eBPF GO library等。

image.png

早期的工具使用 C 语言来编写 BPF 程序,使用 LLVM clang 编译成 BPF 代码,这对于普通使用者上手有不少门槛当前仅限于对于 eBPF 技术更加深入的学习场景。

对于大多数开发者而言,更多的是基于 BPF 技术之上编写解决我们日常遇到的各种问题。BCC 和 bpftrace 作为BPF的两个前端,当前这两个项目在观测和性能分析上已经有了诸多灵活且功能强大的工具箱,完全可以满足我们日常使用。

  • BCC 提供了更高阶的抽象,可以让用户采用 Python、C++ 和 Lua 等高级语言快速开发BPF程序
  • bpftrace 为了快速编写简单的eBPF程序

6 一些学习资料

网站

书籍

  • 《Linux内核观测技术BPF》
  • 《BPF之巅:洞悉Linux系统和应用性能》
  • 《Systems Performance》
  • 《BPF Performance Tools》