Zwlin's Blog

[笔记] ebpf 指北(一)

2022/08/19

ebpf 是什么?

对于我来说,最让我感到豁然开朗的解释的是 Brendan Gregg 大师在其博文中提到的:

eBPF does to Linux what JavaScript does to HTML. (Sort of.) … In reality, eBPF is more like the v8 virtual machine that runs JavaScript, rather than JavaScript itself. eBPF is part of the Linux kernel.

实际上我看了很多对于 ebpf 的“正式”释义依旧云里雾里,大师的解释让我对于 ebpf 一下子不再心怀畏惧:认为其需要高深的 Linux 内核知识,需要充分的内核知识储备。

之后我就开始了我的 ebpf 体验之旅,发现确实以我对于 Linux 内核粗浅的认识,一样可以使用 ebpf (当然也得益于 bcc 这一好用的工具),因此记录下我在了解和学习 ebpf / bcc 过程中踩的一些坑。

BPF Compiler Collection (BCC)

BCC 是一个 BPF 编译器集合,包含了用于构建 BPF 程序的编程框架和库,并提供了大量可以直接使用的工具。

BCC makes BPF programs easier to write, with kernel instrumentation in C (and includes a C wrapper around LLVM), and front-ends in Python and lua. It is suited for many tasks, including performance analysis and network traffic control.

如何安装以及环境配置等问题就不多提了,我使用的是 Arch Linux,可以在 Installing BCC 上查看如何在各种Linux 发行版上安装 BCC(对内核要求较高)。

一个 ebpf 程序

 1#!/usr/bin/env python3
 2from bcc import BPF
 3
 4ebpf_c_text = """
 5int kprobe__sys_clone(void *ctx)
 6{
 7    bpf_trace_printk("Hello, World!\\n");
 8    return 0;
 9}
10"""
11
12b = BPF(text=ebpf_c_text)
13print("Your first ebpf Program... Ctrl-C to stop")
14try:
15    b.trace_print()
16except KeyboardInterrupt as e:
17    exit()

这个程序稍微改造了一下 bcc Python Developer Tutorial 的第一个例程,在刚开始学 ebpf 时,我对这个例程感到非常疑惑,仿佛回到了刚学编程的那个时候。可以看出,这里内嵌了一个 C 代码,然后程序本体是Python 代码,然后这个 kprobe__sys_clone 函数的声明也很让人摸不着头脑。

实际上搞懂这些首先需要知道 ebpf 程序是如何开发然后载入内核并执行的,如图所示:

ebpf

  1. 使用 C 语言开发一个 eBPF 程序;
  2. 借助 LLVM 把 eBPF 程序编译成 BPF 字节码;
  3. 通过 bpf 系统调用,把 BPF 字节码提交给内核;
  4. 内核验证并运行 BPF 字节码,并把相应的状态保存到 BPF maps 中;
  5. 用户程序通过 BPF maps 查询 BPF 字节码的运行状态。

当然这几步你可以手动去完成,但是显然 BCC 抽象了这一过程,例程中只要完成第 1 步 和第 5 步即可。这里的 ebpf_c_text 就是 C 开发的一个 eBPF 程序,之后就是 BCC 来完成第2/3步,用 Python 完成数据的读取(当然这里还没有用到 bpf 的 maps)。到此为止,这个 Python 程序的大题框架我们已经认识。还有一些细节我们没理解。我们先看看运行结果:

1chmod +x first_ebpf.py
2./first_ebpf.py #需要root权限或 sudo ./first_ebpf.py

Output:

1Your first ebpf Program... Ctrl-C to stop
2b'             zsh-27651   [024] d..31 184376.953730: bpf_trace_printk: Hello, World!'
3b'             zsh-27651   [024] d..31 184377.212267: bpf_trace_printk: Hello, World!'
4...
5^C

我们知道,clone 是创建子进程系统调用,因此当有进程创建子进程,例如输入 ls 命令,就会触发这个 ebpf 程序。输出如上所示。

剩下的问题就是理解程序到底做了什么(文档链接):

这几个核心的函数解释完成,程序的逻辑也就不难理解了:每当 sys_clone 被调用,ebpf 程序输出一个Hello Worldtrace_pipe ,然后 Python 持续读取 trace_pipe 并输出。

待续…

Reference

eBPF Documentation: What is eBPF?

Learn eBPF Tracing: Tutorial and Examples

bcc Reference Guide