eBPF 内核实现深度拆解
2014 年 Alexei Starovoitov 把 cBPF 重写为 eBPF 合入 Linux 3.18,此后十年间,eBPF 从”包过滤虚拟机的升级版”演变为 Linux 内核的通用可编程平面——覆盖网络(XDP / TC / sockmap)、追踪(kprobe / tracepoint / fentry)、安全(LSM BPF)、调度(sched_ext),以及存储、cgroup 等几乎所有内核子系统。
中文互联网不缺”用 bpftrace 一行查慢请求”的实践教程——本站的 eBPF 系列、Linux 网络 和 可观测性 系列中已有大量应用级覆盖。但缺一份把 eBPF 内核实现讲透的系统性资料:验证器为什么拒绝你的程序?JIT 编译器如何把 BPF 字节码翻译成 x86-64 本地指令?BTF 的类型信息如何驱动 CO-RE 重定位?sched_ext 的内核接口怎么设计才能让 BPF 程序安全地参与调度决策?
本系列填补这个空白。从 BPF 指令集编码到 verifier 的抽象解释算法,从 Map 数据结构的内核实现到 libbpf 的加载器工程,逐层拆解 eBPF 成为”内核第二用户态”的机制基础。
定位声明:本系列是 eBPF 的内核实现剖析,不是应用教程。如果你想学”怎么用 bpftrace 排查问题”,请从 eBPF 系列 的 01 篇开始。如果你已经会写 BPF 程序,但想理解 verifier 为什么拒绝你、JIT 在做什么、CO-RE 怎么做到一次编译到处运行——这是为你写的。
推荐入口
- BPF 指令集解码:寄存器机器、调用约定与指令编码(第 01 篇):先建立 eBPF 虚拟机的精确心智模型——11 个寄存器的角色、64-bit 指令的字段语义、call 指令和尾调用的机制差异。
- 验证器(Verifier)内部实现:抽象解释与状态裁剪(第 03 篇):理解 verifier 如何通过形式化静态分析保证内核安全——这是 eBPF 与内核模块之间最根本的区别。
- JIT 编译器后端:x86-64 / ARM64 的 BPF→Native 翻译(第 05 篇):看懂 BPF_INSN 到 x86/ARM 机器码的翻译管线,以及为何某些 BPF 指令组合会生成低效本地代码。
- BTF
格式与 CO-RE 重定位引擎(第 12 篇):从 BTF
类型编码、
BTF.ext节、__builtin_preserve_access_index到 libbpf 在加载时修补指令——理解”一次编译到处运行”的完整机制。
一、这个系列要回答的五个问题
验证器(verifier)到底做了什么?它的”安全”边界在哪里? verifier 不是黑盒——它通过抽象解释(abstract interpretation)跟踪每一个寄存器的类型和值域,用状态裁剪(state pruning)控制搜索空间,用等价状态合并(precision tracking)避免路径爆炸。理解 verifier 的内部算法,才能写出被它接受的程序,而不是猜测”加了 if 为什么还报错”。详见第一部分(第 02–04 篇)。
eBPF 的 JIT 编译比内核模块的 AOT 编译差在哪?典型场景下 JIT 相对解释器能快多少? BPF JIT 编译器把 BPF 字节码翻译成本地指令,但翻译质量受限于 BPF ISA 的表达力——BPF 没有浮点、没有向量指令、寄存器数量固定、调用约定与 ABI 不直接映射。不同后端(x86-64 / ARM64 / RISC-V)的优化策略差异巨大;具体倍率取决于程序形态,需在本机 benchmark 验证。详见第一部分(第 05 篇)。
BPF Map 不是简单的 key-value store——每种类型的并发模型、内存布局和扩展行为完全不同,应该怎么选? hash map 的 per-CPU 预分配、array map 的零拷贝共享、ring buffer 的 mmap 双缓冲、bloom filter 的概率位图——每种 map 类型背后是不同的数据结构和并发策略。选错 map 类型在典型高并发场景下可能带来显著性能退化(具体幅度依 workload 而定)。详见第二部分(第 06–08 篇)。
CO-RE(Compile Once – Run Everywhere)不是魔法——它依赖 BTF 格式规范、clang 的
preserve_access_index内置函数、以及 libbpf 在加载时执行的重定位修补。这套链路的每一环怎么工作?什么情况下会失效? BTF 提供了类型和布局的完整元数据,CO-RE 重定位在加载时用 BTF 信息修补指令中的偏移量。但内核的 field offset 变化、类型改名、结构体重排等都会影响 CO-RE 的适用范围。详见第三部分(第 11–13 篇)。sched_ext 是 eBPF 进入调度器的一次范式实验——它暴露了什么内核接口?BPF 调度器能否替代 CFS/EEVDF? sched_ext 把调度策略从内核的编译期决定变成运行时可编程——但它不是”随便写个 BPF 就能调度”。sched_ext 的
struct_ops回调接口、调度类注册、CPU 独占、与 CFS 的共存策略,都需要系统理解。详见第四部分(第 18 篇)。
二、篇目依赖关系与推荐阅读路径
本系列共四部分、21 篇。每篇按「机制→源码→验证→边界」组织,源码以 Linux 6.6/6.8 LTS 为主线(verifier/JIT 等多用 6.6,map/helper 等多用 6.8),涉及最新特性时标注最低内核版本。
强依赖图
flowchart TD
A["01 BPF 指令集与寄存器机器"] --> B["02 Verifier 框架:从 prog_load 到 do_check"]
B --> C["03 Verifier 核心算法:抽象解释与状态裁剪"]
B --> D["04 与验证器共舞:生产级 BPF 程序的写法约束"]
A --> E["05 JIT 编译器后端:x86-64 / ARM64"]
E --> F["06 Map 内核实现(上):hash / array / per-CPU"]
F --> G["07 Map 内核实现(下):ringbuf / bloom / queue-stack / LPM"]
G --> H["08 Helper 函数子系统:注册、类型检查与参数传递"]
H --> I["09 程序生命周期:load / attach / detach / pin / refcount"]
I --> J["10 libbpf 加载器工程:skeleton / auto-attach / map pinning"]
J --> K["11 BTF 格式规范与内核类型系统"]
K --> L["12 CO-RE 重定位引擎:libbpf 的运行时指令修补"]
L --> M["13 BPF 编译工具链:clang 后端 / 目标文件布局 / 调试信息"]
M --> N["14 BPF 程序调试与测试:verifier log / bpftool / test runner"]
I --> O["15 蹦床(Trampoline)与 fentry / fexit:零开销内核追踪"]
O --> P["16 BPF 并发模型:spinlock / RCU / per-CPU 模式"]
P --> Q["17 eBPF 安全模型:capabilities / hardening / Spectre 缓解"]
Q --> R["18 sched_ext 深度:用 BPF 写内核调度器"]
R --> S["19 非 Linux eBPF:Windows eBPF、ubpf / rbpf 用户态运行时"]
S --> T["20 实战:构建微型 eBPF 可观测 Agent"]
T --> U["21 eBPF 生态与未来:标准化、社区动态、内核演进路线"]
推荐阅读路径
内核开发者想理解 eBPF 如何保证安全 01 → 02 → 03 → 04 → 05 → 17
基础设施工程师想深入 BPF 程序开发 01 → 02 → 04 → 06 → 07 → 08 → 09 → 10 → 13 → 14
平台工程师想做 CO-RE 可移植 BPF 01 → 02 → 04 → 10 → 11 → 12 → 13 → 14 → 20
对 eBPF 前沿(调度、安全、非 Linux)感兴趣 01 → 02 → 03 → 15 → 16 → 17 → 18 → 19
只想理解 eBPF 全景,不写代码 01 → 02 → 05 → 09 → 15 → 17 → 21
三、目录与每篇一句话价值
第一部分:eBPF 虚拟机核心
01. BPF 指令集解码:寄存器机器、调用约定与指令编码 把 eBPF 的 11 个 64-bit 寄存器、
struct bpf_insn的 64-bit 编码格式、ALU / 跳转 / 加载存储 / call 四类指令的字段语义讲清楚——这是后续所有 verifier 和 JIT 讨论的基础。02. 验证器(Verifier)框架:从
bpf(BPF_PROG_LOAD)到do_check()跟踪BPF_PROG_LOAD系统调用的内核执行路径,讲清 verifier 的入口、bpf_verifier_env的核心字段、do_check()的主循环结构,以及check_cfg()的控制流图构建——建立 verifier 执行的全景心智模型。03. 验证器核心算法:抽象解释、状态跟踪与路径裁剪 深入 verifier 的静态分析引擎——寄存器状态(
reg_state)的类型与值域表示、栈状态(stack_state)的初始化标记、explore_state()的深度优先搜索、states_equal()的等价状态判定、propagate_precision()的精度追踪——这是本系列最难也最重要的一篇。04. 与验证器共舞:生产级 BPF 程序的写法约束与常见拒绝模式 把 02–03 的理论落地为实战指南——
R1 != ctx错误、invalid variable-length read、pointer arithmetic on non-scalar、循环上界推断失败、helper 参数类型不匹配等 20+ 种常见 verifier 错误的根因与解法。05. JIT 编译器后端:x86-64 / ARM64 的 BPF→Native 翻译管线 拆解
bpf_jit_compile()的完整流程——从bpf_prog到bpf_binary_header的翻译、寄存器映射策略(BPF R0-R10 → x86 rax/rbx/rcx/…)、ALU 指令的一对多翻译、尾调用的 jmp 生成、ARM64 的bpf2a64特色处理,以及 JIT 的bpf_jit_limit和bpf_jit_kallsyms。
第二部分:eBPF 运行时机制
06. Map 内核实现(上):hash / array / per-CPU 的数据结构与并发模型 从
bpf_map_ops虚函数表出发,逐层拆解BPF_MAP_TYPE_HASH、BPF_MAP_TYPE_ARRAY、BPF_MAP_TYPE_PERCPU_HASH/PERCPU_ARRAY的内核实现——htab的 bucket 链表、bpf_array的一维连续内存、per-CPU 分配器的this_cpu_ptr()语义,以及bpf_map_update_elem()上的 RCU 与 preempt_disable 保护。07. Map 内核实现(下):ringbuf / perfbuf / bloom filter / queue-stack / LPM 拆解非平凡 map 类型的实现——ring buffer 的
mmap双缓冲与 record 提交语义、perf event array 的perf_event_output()路径、bloom filter 的N_HASH位图、queue/stack 的 linked list 辅助实现、LPM trie 的前缀树结构,以及 devmap / cpumap / xskmap 等重定向 map 的入口语义。08. Helper 函数子系统:注册、类型检查与参数传递 从
bpf_func_proto结构体出发,讲解 helper 函数的注册机制(BPF_CALL_n宏)、参数类型编码(ARG_PTR_TO_MAP_KEY/ARG_CONST_SIZE等枚举)、返回值策略,以及 verifier 如何在check_helper_call()中对每个参数做类型和边界检查——让你理解为什么有些 helper 只能在特定程序类型中调用。09. 程序生命周期:load / attach / detach / pin / refcount 完整跟踪一个 BPF 程序的生与死——
aux->refcnt引用计数模型、BPF_PROG_LOAD的验证与 JIT 之后发生了什么、各种 attach 类型(bpf_link/ raw tracepoint / perf event)的区别、BPF_OBJ_PIN的 bpffs 持久化、autodetach与 FD 泄露的坑。
第三部分:编译、加载与可移植基础设施
10. libbpf 加载器工程:skeleton / auto-attach / map pinning / ringbuf consumer 深入 libbpf 的加载生命周期——
bpf_object__open()的 ELF 解析、bpf_object__load()的程序批量加载与 map 创建、skeleton 的自动生成(bpftool gen skeleton)、auto-attach的SEC()注解解析、bpf_map__set_autocreate()的 map 复用、ring_buffer__new()的 mmap 消费者。11. BTF 格式规范与内核类型系统 从 BTF 的二进制编码格式(
struct btf_header+ type entries + string table)出发,讲清 BTF 如何编码基本类型、结构体、联合体、函数原型、typedef——以及BTF.ext节如何记录func_info和line_info用于 verifier 日志和 kallsyms。附带 BTF 的去重算法(btf_dedup)原理与内核 BTF 的生成流程(pahole/CONFIG_DEBUG_INFO_BTF)。12. CO-RE 重定位引擎:libbpf 的运行时指令修补 从 clang 的
__builtin_preserve_access_index讲起,追踪BPF_CORE_READ()/BPF_CORE_READ_STR()等宏如何生成BTF.extCO-RE 重定位记录,再到 libbpf 加载时bpf_core_apply_relo()如何根据目标内核的 BTF 计算正确的字段偏移量并修补 BPF 指令——这是可移植 BPF 的核心引擎。13. BPF 编译工具链:clang 后端、目标文件布局与调试信息 从
clang -target bpf的 LLVM BPF 后端出发,讲清 BPF 目标文件(.oELF)的 section 布局约定——license/maps/progsection 的作用、.BTF与.BTF.ext节的内容、DWARF 到 BTF 的转换、bpftool gen的工具链集成,以及 BPF 特有的 inline asm 与__attribute__((preserve_access_index))。14. BPF 程序调试与测试:verifier log / bpftool / test runner / 内核自测 从 verifier log 的级别控制(
log_level1 vs 2 vs 自选寄存器)、bpftool prog dump xlated/dump jited的反汇编、bpftool map dump的运行时检查、BPF selftests (tools/testing/selftests/bpf/) 的结构——完整的 BPF 程序调试工具箱。
第四部分:高级主题与前沿
15. 蹦床(Trampoline)与 fentry / fexit:零开销内核追踪 fentry/fexit 通过 BPF 蹦床机制(
BPF_TRAMP_FENTRY/BPF_TRAMP_FEXIT),在目标函数上直接替换nop为call指令进入 BPF,避免了 kprobe 的int3中断开销。本篇拆解bpf_trampoline的内核实现、arch_prepare_bpf_trampoline()的架构相关栈帧构造、struct_ops如何利用蹦床机制调度回调——以及什么情况下蹦床的”零开销”名不副实。16. BPF 并发模型:spinlock / RCU / per-CPU 模式 BPF 程序在内核上下文中并发执行——同一个 BPF 程序可能同时在多个 CPU 上运行。本篇讲清 BPF 环境下的内存模型(
__sync_fetch_and_add/BPF_ATOMIC指令)、bpf_spin_lock的实现限制(持有期间不能调用 helper)、RCU 保护的 map 读取语义(bpf_rcu_read_lock())、per-CPU map 的免锁读写,以及 BPF 中断上下文 vs 进程上下文的并发语义差异。17. eBPF 安全模型:capabilities / unprivileged BPF / Spectre 缓解 BPF 程序在内核态执行——安全不是 verifier 一个人的事。本篇讲清
CAP_BPF与CAP_SYS_ADMIN的权限梯度、kernel.unprivileged_bpf_disabled的控制、Spectre v2 的bpf_jit_harden缓解(常数盲化、retpoline)、Spectre v4 的speculation_barrier、以及BPF_LSM如何把安全策略的可编程性带到 LSM 层——附带非特权 BPF 的历史沿革与当前可用性评估。18. sched_ext 深度:用 BPF 写内核调度器的完整接口 sched_ext(6.12+)是 eBPF 进入调度领域的标志性事件。本篇拆解
struct sched_ext_ops中每个回调的语义——select_cpu()/enqueue()/dispatch()/tick()/runnable()/cpu_release()——以及scx_bpf_dispatch()/scx_bpf_kick_cpu()等 kfunc 的内核实现、调度类ext如何与CFS/EEVDF共存(SCX_OPS_SWITCH_PARTIAL)、scx_layered和scx_rustland的用户态调度器实现分析。19. 非 Linux eBPF:Windows eBPF 平台、ubpf / rbpf 用户态运行时 eBPF 不止于 Linux——Windows eBPF(
ebpf-for-windows)如何把 BPF 字节码挂载到 Windows 内核的 NetBufferList 和 System Call、ubpf的用户态 VM 实现(JIT 和 interpreter)、rbpf的 Rust 实现生态——以及跨平台 eBPF 的标准化进展(IETF BPF Instruction Set Architecture 草案)。20. 实战:构建微型 eBPF 可观测 Agent 把第 01–17 篇的知识串成一条实践线——从 libbpf skeleton 写第一个 BPF 程序、加载到内核、用 ring buffer 回传事件、用 CO-RE 实现跨内核版本兼容、加上 map pinning 实现热升级、配上半自动化的 verifier 错误排障流程——构建一个麻雀虽小五脏俱全的 eBPF 可观测 Agent。
21. eBPF 生态与未来:内核演进路线、标准化、社区动态 回顾 eBPF 从 Linux 3.18 到 6.x 十年演进的关键节点(kprobe → tracepoint → XDP → BTF → fentry → LSM → sched_ext),梳理内核 BPF 维护者的开发节奏与合并策略、eBPF Foundation 的标准化工作、
bpf()syscall 的能力扩展方向(动态函数、类型化指针、kfunc 生态)、以及 eBPF 与内核模块的长期竞争格局。
四、读者定位与先修要求
| 维度 | 说明 |
|---|---|
| 主要读者 | Linux 内核工程师、基础设施平台工程师、高性能网络/安全工程师、eBPF 工具链开发者 |
| 次要读者 | 对内核机制有深入兴趣的后端/SRE 工程师,已有 BPF 程序编写经验想深入理解原理的开发者 |
| 先修知识 | 熟悉 C 语言、基本数据结构与算法;了解 Linux 内核基础(syscall / 进程 / 内存 / 网络包收发路径);有 eBPF 基础概念(verifier / map / hook)更佳,非必须——本篇 01 从 BPF ISA 从头讲起 |
五、与其他系列的关联
本系列是 eBPF 的内核实现剖析,与以下已有的应用级内容互补:
| 已有系列/文章 | 与本系列关系 |
|---|---|
| eBPF 系列(7 篇实战) | 已有系列是 eBPF 应用大全(bpftrace / XDP / Cilium / 安全 / profiling),本系列是这些应用的内部实现 |
| Linux 网络(XDP / eBPF hooks / netfilter) | 已有系列的第 21–22 篇是 XDP 与 eBPF 网络钩子的源码解析,本系列的 Map / helper / verifier 部分是其底层依赖 |
| 操作系统百科(eBPF 核心 / RCU / 并发) | 已有系列第 89 篇已对 eBPF 做概览,第 67 篇做了 RCU 深度拆解——本系列的 Map 并发模型和 verifier 部分是这些内容的延伸 |
| 可观测性(eBPF 观测全景) | 已有系列第 14 篇覆盖了 bcc / bpftrace / libbpf 的应用路径,本系列的编译工具链和 CO-RE 部分是其底层基础设施 |
| K8s 网络(Cilium / CNI) | 已有系列覆盖 Cilium 的架构与应用,本系列的 Map(sockmap / devmap)和网络 hook 是其数据面基础 |
六、阅读建议
- 已在生产中使用 eBPF(Cilium / Pixie / DeepFlow):直接从第二部分(Map 实现)和第三部分(CO-RE)切入,理解你的工具在底层做了什么。
- 正在写 BPF 程序但被 verifier 折磨:先读 01 → 02 → 03 → 04,再跳到 14 学调试。
- 内核新手想系统性学 eBPF:按 01 → 02 → 05 → 06 → 09 → 10 → 13 → 20 的顺序,覆盖从 ISA 到实战的全路径。
- 追赶 eBPF 前沿(调度 / 安全 / 跨平台):先扫 01–04 理解核心约束,再跳到 15–19 + 21。
- 已有 eBPF 系列文章(eBPF 系列)的读者想深入:按 02 → 03 → 06 → 07 → 11 → 12 → 14 补足底层知识。
七、写作方法与证据策略
- 源码驱动:核心机制以 Linux 6.6/6.8 LTS
源码为准,关键函数写清路径和行号范围。涉及 6.12+ 新特性(如
sched_ext)时,标注最低内核版本并引用
bpf-next树的提交记录。 - 可复现验证:第 20 篇(微型 Agent)在
20-mini-ebpf-agent/mini-agent/提供完整可编译源码;verifier 行为描述配verifier log的真实输出(标注内核版本)。 - “问题→机制”叙述:每篇开篇先抛出实际工程痛点(“为什么 verifier 拒绝这段看起来没问题的代码?”),再引出内核实现解法。
- 图示策略:verifier 状态机、JIT 翻译流程、Map 内存布局等复杂机制优先配 Mermaid 流程图或独立 SVG。
- 与其他系列不重复:本系列不重复已有 eBPF 文章中的应用教程内容,交叉引用以”参见 某系列 第 N 篇”处理。
八、承诺与不承诺
承诺:
- 核心机制配内核源码路径和关键数据结构定义;
- verifier / JIT / CO-RE 三段核心链路给出可追踪的调用路径;
- Map 实现分析覆盖所有常用
BPF_MAP_TYPE_*的map_ops实现差异; - 第 20 篇提供完整可编译可运行的 BPF Agent 示例代码。
不承诺:
- 不替代内核源码阅读——本系列是导航和解读,不是
include/linux/bpf.h的全量翻译; - 不罗列所有 BPF 程序类型的所有 helper 函数——只挑有教学价值的核心路径;
- 不推荐商业 eBPF 产品选型——那是工程判断,不是技术分析;
- 不预测 sched_ext 能否进入主线或取代 CFS——那是社区政治,不是技术问题。
参考(写作时使用与遵循的核心资料)
内核源码(A 级)
- Linux 内核主线
kernel/bpf/、include/linux/bpf*.h、include/uapi/linux/bpf*.h - Linux 内核
tools/lib/bpf/(libbpf 源码) - Linux 内核
tools/testing/selftests/bpf/(BPF 自测) - Linux 内核
tools/bpf/bpftool/(bpftool 源码) arch/x86/net/bpf_jit_comp.c、arch/arm64/net/bpf_jit_comp.c(JIT 后端)
规范与标准(A 级)
- IETF BPF Instruction Set Architecture
草案(
draft-ietf-bpf-isa) - BTF 格式规范(内核文档
Documentation/bpf/btf.rst) - eBPF Linux 文档(
Documentation/bpf/下所有文件) - eBPF Foundation 标准化文档
论文与关键文章(A/B 级)
- Starovoitov, A. “BPF: the universal in-kernel virtual machine.”(eBPF 设计原论文)
- Gregg, B. “BPF Performance Tools.” Addison-Wesley, 2019.(B 级:权威实践参考)
- 内核邮件列表(LKML)上 BPF 相关的 RFC 与讨论
社区项目(B 级)
- libbpf — standalone libbpf
- bpftrace
- Cilium eBPF — Go eBPF library
- aya — Rust eBPF library
- ubpf — User-space BPF VM
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【eBPF 内核实现深度拆解】CO-RE 重定位引擎:libbpf 的运行时指令修补
从 clang 内置函数 __builtin_preserve_access_index 出发,追踪 BPF_CORE_READ 等宏如何生成 BTF.ext CO-RE 重定位记录,再到 libbpf 加载时 bpf_core_apply_relo() 根据目标内核 BTF 计算正确字段偏移量并修补 BPF 指令——可移植 BPF 的核心引擎。
【eBPF 内核实现深度拆解】BTF 格式规范与内核类型系统
从 BTF 的二进制编码格式(btf_header + type entries + string table)出发,讲清 BTF 如何编码基本类型、结构体、联合体、函数原型与 typedef——BTF.ext 节的 func_info/line_info 记录,以及内核 pahole 的 BTF 生成与去重算法 btf_dedup。
【eBPF 内核实现深度拆解】蹦床(Trampoline)与 fentry / fexit:零开销内核追踪
fentry/fexit 通过 BPF 蹦床机制在目标函数的 nop 位置直接替换为 call 指令进入 BPF,避免了 kprobe 的 int3 中断开销。本文拆解 bpf_trampoline 内核实现、arch_prepare_bpf_trampoline 的架构相关栈帧构造、struct_ops 与蹦床的协作——以及蹦床在什么条件下开销并不为零。
【eBPF 内核实现深度拆解】实战:构建微型 eBPF 可观测 Agent
把 01--17 的知识串成一条实践线——从 libbpf skeleton 写第一个 BPF 程序、加载到内核、用 ring buffer 回传事件、用 CO-RE 实现跨内核版本兼容、map pinning 实现热升级、配上半自动化的 verifier 错误排障流程——构建一个麻雀虽小五脏俱全的 eBPF 可观测 Agent。