Skip to content

第一百九十二章:BPF 之道

大乘期

涉及内核源码:

林小源离开 ftrace 的峡谷,来到一片悬浮的大陆。大陆的边缘不断有数据包像瀑布一样倾泻而下,每一个数据包都闪着微光。

"小心脚下。"一个年轻的声音说。

林小源低头一看,发现自己正站在一块透明的晶板上。晶板下方是一台巨大的机器——无数齿轮咬合转动,每一个齿轮上都刻着指令。机器的中央有一块区域,光芒最为耀眼,上面刻着三个字母:BPF。

"这是……内核虚拟机?"林小源问。

"准确说是 eBPF——扩展的伯克利包过滤器。"那声音的主人从机器后方走出来。他看起来像个年轻的工匠,穿着沾满油污的工装,手里拿着一把扳手,"以前我只过滤网络包,现在已经进化成内核里的通用虚拟机了。"

"你能做什么?"

eBPF 工匠扳了扳手,指向机器的不同区域:"网络那边,XDP 在数据包进入协议栈之前就处理,速度极快;TC 控制流量。跟踪那边,kprobe 在任意内核函数入口插入探针,tracepoint 跟踪预定义事件。安全那边,seccomp 过滤系统调用。性能那边,perf 采集数据。"

林小源注意到机器的侧面有一排整齐的接口,每个接口上都标着类型名:

"这些是程序类型。"eBPF 工匠说,"不同类型的 eBPF 程序可以挂载到不同的钩子点。"

c
/*
 * BPF 进化:
 *
 * 经典 BPF (cBPF):
 *   包过滤
 *   tcpdump 使用
 *
 * 扩展 BPF (eBPF):
 *   内核虚拟机
 *   安全执行用户代码
 *   用途广泛
 *
 * eBPF 用途:
 *   网络 — XDP、TC
 *   跟踪 — kprobe、tracepoint
 *   安全 — seccomp
 *   性能 — perf
 *
 * eBPF 程序类型:
 *   BPF_PROG_TYPE_SOCKET_FILTER — 套接字过滤
 *   BPF_PROG_TYPE_KPROBE — 内核探针
 *   BPF_PROG_TYPE_TRACEPOINT — 跟踪点
 *   BPF_PROG_TYPE_XDP — 网络数据路径
 *   BPF_PROG_TYPE_CGROUP_SKB — cgroup 网络
 *
 * eBPF map:
 *   键值存储
 *   内核和用户空间共享
 *   类型: hash、array、ring buffer
 *
 * 工具:
 *   bpftrace — 高级语言
 *   BCC — Python 绑定
 *   libbpf — C 库
 */

/* 模拟 eBPF 程序 */
struct bpf_insn {
    int code;
    int dst_reg;
    int src_reg;
    int offset;
    int imm;
};

void print_bpf_insn(struct bpf_insn *insn) {
    printf("  code=%d dst=r%d src=r%d off=%d imm=%d\n",
           insn->code, insn->dst_reg, insn->src_reg,
           insn->offset, insn->imm);
}

printf("=== BPF 之道 — 内核虚拟机 ===\n\n");

printf("eBPF 是内核的虚拟机:\n\n");

printf("--- BPF 进化 ---\n");
printf("经典 BPF (cBPF):\n");
printf("  包过滤\n");
printf("  tcpdump 使用\n\n");
printf("扩展 BPF (eBPF):\n");
printf("  内核虚拟机\n");
printf("  安全执行用户代码\n");
printf("  用途广泛\n\n");

printf("--- eBPF 用途 ---\n");
printf("网络:\n");
printf("  XDP — 高性能网络\n");
printf("  TC — 流量控制\n\n");
printf("跟踪:\n");
printf("  kprobe — 内核探针\n");
printf("  tracepoint — 跟踪点\n\n");
printf("安全:\n");
printf("  seccomp — 系统调用过滤\n\n");
printf("性能:\n");
printf("  perf — 性能分析\n\n");

printf("--- eBPF 程序类型 ---\n");
printf("SOCKET_FILTER:\n");
printf("  套接字过滤\n\n");
printf("KPROBE:\n");
printf("  内核探针\n\n");
printf("TRACEPOINT:\n");
printf("  跟踪点\n\n");
printf("XDP:\n");
printf("  网络数据路径\n\n");

printf("--- eBPF map ---\n");
printf("键值存储:\n");
printf("  内核和用户空间共享\n\n");
printf("类型:\n");
printf("  hash — 哈希表\n");
printf("  array — 数组\n");
printf("  ring buffer — 环形缓冲区\n\n");

printf("--- 工具 ---\n");
printf("bpftrace:\n");
printf("  高级语言\n");
printf("  一行脚本\n\n");
printf("BCC:\n");
printf("  Python 绑定\n");
printf("  开发 eBPF 程序\n\n");
printf("libbpf:\n");
printf("  C 库\n");
printf("  加载 eBPF 程序\n\n");

printf("--- 示例 ---\n");
printf("bpftrace:\n");
printf("  bpftrace -e 'kprobe:sys_open { printf(\"open\\n\"); }'\n\n");
printf("BCC:\n");
printf("  b = BPF(text='...')\n");

林小源注意到机器的入口处站着一个严肃的老者,手持一卷长长的清单,每一个进入机器的程序都要在他面前接受检查。

"那是验证器。"eBPF 工匠说,声音压低了一些,"他比任何人都严格。"

林小源看到一个年轻人试图把一段程序送入机器。验证器展开清单,逐条核对:不能有无限循环——检查通过;不能访问非法内存——检查通过;不能崩溃内核——检查通过。

"每一条指令都要验证。"验证器头也不抬地说,"你写的程序会在内核里运行,内核崩溃意味着整个系统完蛋。我不能冒这个险。"

那年轻人被吓退了几步,修改了程序,重新提交。验证器再次展开清单,这次他点了点头,放行了。

"验证器确保 eBPF 程序是安全的。"eBPF 工匠说,"它做静态分析,在程序执行前就发现所有潜在的问题。不能死循环,不能越界访问,不能泄露内核指针。这是 eBPF 能在内核中安全运行的根本保障。"

机器开始运转,eBPF 工匠递给林小源一块 map——一个精致的键值存储盒,表面刻着 hash、array、ring buffer 三种纹路。

"拿着。"eBPF 工匠说,"这是 eBPF map,内核和用户空间通过它共享数据。"

林小源接过 map,感受到它内部数据的流动——键值对像流水一样在其中穿梭。

"最大的价值是什么?"林小源问。

eBPF 工匠放下扳手,认真地看着他:"你不需要修改内核代码,就能让内核做你想做的事。以前要跟踪一个系统调用,你得写内核模块,编译,加载,重启。现在用 bpftrace 一行脚本就够了。"

他指了指悬崖边那些倾泻而下的数据包:"网络那边更明显。XDP 程序直接在网卡驱动层处理数据包,性能比 iptables 快十倍。而且你随时可以卸载,不影响内核运行。"

林小源握紧手中的 map。他明白了——eBPF 的价值不在于它有多强大,而在于它让内核变得可编程,而不需要触碰内核本身。


道藏笔记

内核启示

eBPF 是内核的虚拟机。

进化:

  • cBPF — 包过滤
  • eBPF — 内核虚拟机

用途:

  • 网络 — XDP、TC
  • 跟踪 — kprobe、tracepoint
  • 安全 — seccomp
  • 性能 — perf

安全模型:

  • 验证器检查
  • 不会崩溃内核
  • 不会死循环

工具:

  • bpftrace — 高级语言
  • BCC — Python 绑定
  • libbpf — C 库

eBPF 是虚拟机——让内核可编程。


破关试炼

BPF 之试

BPF 之道中,在内核运行受验证小程序、用于观测和扩展的机制是什么?

答对后才能继续滑动和进入下一章。

以修仙之名,悟内核之道