Zwlin's Blog

[笔记] ebpf 指北(一)

Last updated: 2022/08/19     Published at: 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 并输出。

待续…