🔨 整理中 · 这一篇讲 eBPF(extended BPF,现在就叫 BPF)——内核里跑"用户态写的、经安全校验"的沙盒程序,能挂到 kprobe/tracepoint/socket/网络包/cgroup 等各种点做监控/过滤/统计/性能剖析。素材以 6.19 源码 + kernel.org BPF 文档 为权威。它是"近年内核最火的新能力",BCC/bpftrace 让"动态追踪内核"变得一句话就能写。
做什么
02 kprobes/13 perf 让你在"内核某些点"埋点、采样,但它们只提供"读"的能力——你看数据,不能干预。eBPF 更强:你写一段(经校验安全的)小程序,挂到内核的某个点(kprobe/tracepoint/socket 收包/XDP/cgroup),内核每次到那个点就调你的程序——程序可以读上下文、做统计、修改包、过滤、返回决策。因为程序经过 verifier 校验(无环、有界、内存安全)、JIT 成本机码,它安全又快,所以 eBPF 成了内核"可编程扩展"的统一载体:网络监控、性能追踪、安全过滤、调度可编程(sched-ext,见 sched-evolution-05)都基于它。这一篇拆 eBPF 的机制和用法。
要了解什么
一、bpf() 系统调用 + verifier + JIT
eBPF 的内核接口是 bpf(2) 系统调用(include/uapi/linux/bpf.h + kernel/bpf/syscall.c)。用户态把一段 BPF 字节码(用 clang 编译 C 子集得到)通过 bpf(BPF_PROG_LOAD, ...) 加载进内核,内核:
- verifier 校验:静态分析这段程序——保证它必然终止(无环、指令有界)、内存访问安全(不越界、不访问未授权内存)、**寄存器类型正确"。校验不过直接拒绝加载。这是 eBPF "安全"的根基——内核敢跑用户代码,因为 verifier 保证它不会搞挂内核。
- JIT:把 BPF 字节码编成当前架构的本机码(x86/ARM64 各有 JIT),运行时直接执行本机码,几乎无解释开销。
- attach:把程序挂到某个点(kprobe/tracepoint/socket filter/...),从此该点触发时调它。
二、BPF 程序类型(program type)
程序类型决定"这个 BPF 程序挂哪、能调哪些 helper"。常见类型:
BPF_PROG_TYPE_KPROBE/TRACEPOINT:挂 kprobe/tracepoint,做内核动态追踪(统计某函数被调多少次、参数分布)。BPF_PROG_TYPE_PERF_EVENT:挂 perf 事件,做采样剖析(类 perf 但可编程)。BPF_PROG_TYPE_SOCKET_FILTER:socket 收包过滤(早期 BPF 主战场,tcpdump 的底层)。BPF_PROG_TYPE_XDP:网络驱动收包最早路径(eXpress Data Path),做高性能包处理/过滤/负载均衡。BPF_PROG_TYPE_CGROUP_SKB/SOCK:cgroup 级网络/进程过滤。BPF_PROG_TYPE_STRUCT_OPS:实现某个内核 ops 表(如 sched-ext 用它实现可编程调度器)。
三、BPF maps:内核-用户态数据交换
BPF 程序需要"把统计结果暴露给用户态、或缓存状态",这靠 BPF maps——内核里的一块结构化存储(array/hash/percpu 等多种),用户态用 bpf(BPF_MAP_LOOKUP_ELEM/BPF_MAP_UPDATE_ELEM, ...) 读写,BPF 程序用 bpf_map_lookup_elem/bpf_map_update_elem helper 读写。比如统计系统调用次数:BPF 程序在 tracepoint 处 map[sycall_nr]++,用户态定期 dump map 看统计。maps 是 BPF "内核执行 + 用户态观测"的桥梁。
四、前端:BCC 与 bpftrace
直接写 BPF C + 用 bpf() 加载很底层。BCC(BPF Compiler Collection)提供 Python 封装 + 一堆现成工具(execsnoop/opensnoop/biolatency/tcplife...)。bpftrace 是更轻的"一行式" DSL,几行就能写个追踪:
# 统计每个进程发起 read 的次数
bpftrace -e 'tracepoint:syscalls:sys_enter_read { @[comm] = count(); }'
# 看哪个函数被调最多
bpftrace -e 'profile:hz:99 { @[kstack] = count(); }'bpftrace 的简洁让它成了"动态追踪"的瑞士军刀——想看啥现场写一行。
动手试试
- 宿主机装 bpftrace,
bpftrace -e 'BEGIN { printf("hello\n"); }'跑最小例子 bpftrace -e 'tracepoint:syscalls:sys_enter_* { @[comm] = count(); }'统计进程系统调用频率(Ctrl+C 看排行)- 装 BCC,跑
execsnoop(看进程创建)/biolatency(看磁盘 I/O 延迟分布) - 进阶:写一个 BPF 程序挂 kprobe 统计某函数调用次数,用 map 把结果给用户态
- 思考题:为什么 eBPF 要有 verifier?(提示:内核敢跑用户代码的前提)
延伸阅读
- 源码(本仓库
third_party/linux/,6.19.9):include/uapi/linux/bpf.h(bpf 命令/程序类型/map 类型/struct bpf_insn 指令集);kernel/bpf/(syscall.c的 bpf 系统调用、verifier.c校验器、core.c/helpers.c/各 map 类型);JIT 在arch/<arch>/net/(bpf_jit_comp.c);程序类型实现散在各处(kernel/trace/bpf_trace.c追踪类、net/core/filter.c网络类)。 - kernel.org:BPF Documentation、BPF helpers。
- 外部:BCC、bpftrace;Brendan Gregg 的 eBPF Tools 是实战权威。
- 关联本站:本篇是 00 调试全景图 "动态追踪→eBPF"的展开;eBPF 用 kprobe/tracepoint(02 kprobes/06 ftrace);sched-ext 可编程调度器用 eBPF(演进史 05)。