第一百九十二章:BPF 之道
大乘期涉及内核源码:
一
林小源离开 ftrace 的峡谷,来到一片悬浮的大陆。大陆的边缘不断有数据包像瀑布一样倾泻而下,每一个数据包都闪着微光。
"小心脚下。"一个年轻的声音说。
林小源低头一看,发现自己正站在一块透明的晶板上。晶板下方是一台巨大的机器——无数齿轮咬合转动,每一个齿轮上都刻着指令。机器的中央有一块区域,光芒最为耀眼,上面刻着三个字母:BPF。
"这是……内核虚拟机?"林小源问。
"准确说是 eBPF——扩展的伯克利包过滤器。"那声音的主人从机器后方走出来。他看起来像个年轻的工匠,穿着沾满油污的工装,手里拿着一把扳手,"以前我只过滤网络包,现在已经进化成内核里的通用虚拟机了。"
"你能做什么?"
eBPF 工匠扳了扳手,指向机器的不同区域:"网络那边,XDP 在数据包进入协议栈之前就处理,速度极快;TC 控制流量。跟踪那边,kprobe 在任意内核函数入口插入探针,tracepoint 跟踪预定义事件。安全那边,seccomp 过滤系统调用。性能那边,perf 采集数据。"
林小源注意到机器的侧面有一排整齐的接口,每个接口上都标着类型名:、、、。
"这些是程序类型。"eBPF 工匠说,"不同类型的 eBPF 程序可以挂载到不同的钩子点。"
/*
* 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");#include <stdio.h>
/*
* 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);
}
int main() {
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");
return 0;
}二
林小源注意到机器的入口处站着一个严肃的老者,手持一卷长长的清单,每一个进入机器的程序都要在他面前接受检查。
"那是验证器。"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 之道中,在内核运行受验证小程序、用于观测和扩展的机制是什么?