无需重启,无需修改源码,让操作系统即刻拥有全新的安全、网络与可观测能力—— 这不是科幻,这是 eBPF 正在做的事。
Instantly grant the OS new security, networking, and observability capabilities without restarting or modifying source code — this isn't science fiction, this is eBPF.
手机在震动。你从沉睡中被拉回现实,屏幕上是刺目的红色:P0 — 生产环境 Kubernetes 集群核心服务雪崩。
你冲到电脑前,眼前的场景让你心跳加速——Grafana 仪表盘上,QPS 从 15,000 骤降到 300, 延迟从 12ms 飙升到 28 秒。 客户投诉如洪水涌入 Slack 频道,VP 在群里连发三个问号。 你的直觉告诉你,这不是普通的流量高峰——某些 Pod 正在做不该做的事。
传统路径:你需要 SSH 到数十个节点,手动安装 tcpdump、
strace、perf……挨个排查。每一步都需要进程重启才能生效。
而此刻每一秒都在烧钱——按照 SLA 协议,每多停机一分钟意味着 $12,000 的罚款。
但今天不同。
你打开 Hubble UI,一个命令都没有输入,三秒内, 实时的服务拓扑图已呈现眼前——你看到一个异常 Pod 正通过一个非标准端口大量外发数据。 Tetragon 的安全策略已经在内核层自动标记了该行为,并在告警中精确定位到 进程 PID、文件路径、完整的系统调用链。
你确认这是一次容器逃逸攻击。无需人工干预, Tetragon 在 内核级 直接向恶意进程发送了 SIGKILL 信号—— 延迟仅 82 纳秒,远快于任何用户态安全工具。
从告警到根因定位:47 秒。
从定位到威胁消除:自动完成。
传统方案的 MTTR(平均修复时间):3-4 小时。
这不是假想的未来场景。这是 eBPF 驱动的 Cilium + Tetragon 技术栈今天就能做到的事情。 这套技术的核心,正是本文要深入剖析的主角。
读完这篇文章,你将理解 eBPF 程序从 编写到卸载 的完整生命周期, 掌握它为何能在不修改内核源码、不重启系统的前提下,赋予 Linux 内核安全、网络与可观测性的全新超能力。 你会明白每一个关键组件——BTF、CO-RE、Verifier、JIT、BPF Maps、Hooks——在这条生命线上的精确位置和深层原理。 你还会看到为什么这条路"用手写"如此艰难,从而理解 Cilium 和 Tetragon 等上层平台存在的必然性。
Your phone buzzes violently. You snap awake to a wall of red: P0 — Production Kubernetes cluster core services cascading failure.
You stumble to your laptop. The Grafana dashboard tells a horror story — QPS has cratered from 15,000 to 300. Latency has spiked from 12ms to 28 seconds. Customer complaints are flooding Slack. Your VP has sent three consecutive question marks. Your gut says this isn't a traffic spike — something inside those Pods is doing something it shouldn't.
The traditional path: SSH into dozens of nodes, manually install tcpdump,
strace, perf, and investigate one by one. Each step requires process restarts.
Meanwhile, every second is burning money — per SLA, each extra minute of downtime means
$12,000 in penalties.
But today is different.
You open Hubble UI. Without typing a single command, within three seconds, a real-time service topology map appears — you see a rogue Pod sending massive data over a non-standard port. Tetragon's security policies have already flagged the behavior at the kernel level, pinpointing the exact PID, file path, and complete syscall chain.
You confirm: this is a container escape attack. Without manual intervention, Tetragon has already sent SIGKILL to the malicious process at the kernel level — with a latency of just 82 nanoseconds, far faster than any user-space security tool.
Alert to root cause: 47 seconds.
Root cause to threat elimination: Automatic.
Traditional MTTR: 3-4 hours.
This is not a hypothetical future. This is what eBPF-powered Cilium + Tetragon can do today. The core technology behind this is the protagonist of this article.
After reading this article, you will understand the complete lifecycle of an eBPF program — from writing to unloading — and grasp why it can grant the Linux kernel new superpowers in security, networking, and observability without modifying kernel source code or rebooting. You will learn the precise role of each key component — BTF, CO-RE, Verifier, JIT, BPF Maps, Hooks — and understand why doing this "the hard way" is so difficult, making platforms like Cilium and Tetragon inevitable.
要真正理解 eBPF 的革命性,我们不能从结论出发,必须回到最基本的问题: 操作系统内核是如何工作的?为什么给内核添加新功能如此困难? 只有建立了这个认知根基,eBPF 的设计哲学才会显得不仅合理,而且必然。
想象一座中世纪城堡。城堡内部(Kernel Space,内核空间)是王国的心脏—— 这里掌管着一切核心资源:军队调度(CPU 调度)、粮仓管理(内存管理)、城门通信(网络协议栈)、 档案室(文件系统)。城堡的管理者拥有最高权限——Ring 0, 可以直接操作任何硬件设备,访问全部物理内存。
城堡外面是繁华的集市(User Space,用户空间)。商人(应用程序)们在这里做生意、 交换信息、服务客户。他们很重要,但绝不被允许直接走进城堡。 为什么?因为一个恶意的(或仅仅是粗心的)商人如果能直接接触到粮仓, 可能导致整个王国的崩溃——这在计算机术语中叫做 Kernel Panic, 即内核崩溃导致整个系统宕机。
图 1-1:Linux 内核架构的双层结构 —— 城堡(内核空间)与集市(用户空间)之间,System Call 是唯一的合法通道
这座城堡的安全设计源于 CPU 硬件本身。现代处理器(x86-64、ARM64)提供了保护环(Protection Rings)机制: 内核运行在 Ring 0(最高特权),用户应用运行在 Ring 3(最低特权)。这不是软件约定,而是硅片级的物理隔离。 当 Ring 3 的代码试图执行只有 Ring 0 才能执行的指令(例如直接读写 I/O 端口),CPU 会立刻触发一个硬件异常, 操作系统随即终止该进程——这就是"保护"的物理含义。
既然用户态程序不能直接进入内核,它们如何请求内核服务?答案是 System Call(系统调用)—— 这是用户空间与内核空间之间唯一合法的通信通道。
把它想象成城堡的吊桥:集市的商人(应用程序)想要运送货物到远方(发送网络数据包),
不能自己翻越城墙,必须走到吊桥前,按下门铃(触发 syscall 指令),报上身份和请求,
等待城堡卫兵(内核)验证后放行。卫兵代为执行操作,再把结果通过吊桥递回来。
每一次系统调用都意味着一次完整的"边境检查"——术语叫做 Context Switch(上下文切换)。
上下文切换就像 F1 赛车进站(Pit Stop):赛车必须减速、驶入维修区、换胎、加油、再加速驶出。 即使进站团队训练有素,这个过程也至少需要 2-4 秒——而赛道上的对手可不会停下来等你。
在计算机中,一次 Context Switch 涉及:
单次上下文切换的直接成本约为 1-5 微秒——看似微不足道,但在高频场景下(如一台 Web 服务器每秒处理 10 万次请求), 这些微秒会累积成 显著的性能瓶颈。更关键的是间接成本:缓存被刷新后,CPU 需要重新"预热"数据, 这种缓存污染往往比直接开销还要昂贵数倍。
安全性与性能之间存在根本矛盾:内核把用户态隔离在城墙之外是为了安全, 但每次跨越城墙(System Call + Context Switch)都有性能代价。 任何想要获得内核级速度和权限的方案,都必须想办法在城堡内部运行代码, 同时又不威胁城堡的安全。这就是 eBPF 要解决的核心矛盾。
在 eBPF 出现之前,开发者想要给内核添加新功能,基本上只有三条路可走。 不幸的是,每一条都布满荆棘。
最"正统"的方案——直接修改 Linux 内核源代码。你可以 fork 内核仓库,在网络协议栈里加一个新 Hook, 在调度器里加一段优化逻辑,然后提交 patch 到上游社区。
问题是,Linux 内核是全世界最严格的开源项目之一。一个补丁从提交到合入主线(mainline)的典型周期是 3-5 年。 它需要经过社区评审、多轮修改、合入候选版本(RC)、在各大发行版中验证、最终才会出现在你的生产服务器上。 即便你的公司有能力维护一个自定义内核(如 Google 的 Prodkernel),这也意味着巨大的工程成本和 与上游永久分叉的风险。
对于"今晚就需要修复"的生产问题,这条路是完全走不通的。
Linux 提供了一种折中方案:Loadable Kernel Module (LKM),即可加载内核模块。
你可以编写一个 .ko 文件,通过 insmod 命令动态加载到内核中——
无需重新编译整个内核,也无需重启。
听起来很完美?问题在于:内核模块运行在 Ring 0, 拥有与内核本身完全相同的权限,但没有任何安全边界。 一个有 bug 的内核模块可以:
这就像允许一个未经安检的陌生人直接走进城堡的核心——他可能是友好的工匠, 也可能是伪装的间谍。内核模块没有验证机制、没有沙盒隔离、没有崩溃保护。 在生产环境中加载第三方内核模块,对许多企业的安全团队来说是不可接受的风险。
既然内核态太危险,那就全部在用户态做?这是许多传统安全和网络工具的选择—— 比如以 Sidecar 模式运行的服务网格(如 Istio/Envoy),或以 Agent 模式运行的安全工具。
用户态方案是安全的——它崩溃了最多影响自己的进程,不会拖垮内核。 但代价是性能:每一个网络包从内核到用户态再回到内核, 需要穿越两次 System Call 边界、两次 Context Switch、两次数据拷贝。 在高吞吐场景下(如 100Gbps 网络),这种开销可以吃掉 30-50% 的 CPU 资源。
更致命的是可见性盲区:用户态工具只能看到内核"愿意告诉它"的信息——
通过 /proc、/sys、netlink 等接口。
那些发生在内核深处的事件(如内核函数调用、调度决策、内存分配路径),
用户态工具根本看不到。就像试图通过城堡的窗户猜测里面发生了什么——
你看到的永远是冰山一角。
| 维度 | 修改内核源码 | 内核模块 (LKM) | 用户态方案 | eBPF ✨ |
|---|---|---|---|---|
| 交付速度 | 3-5 年 | 数周 | 数天 | 数秒 ⚡ |
| 安全风险 | 低(经社区审核) | 🔴 极高(无沙盒) | 低(进程级隔离) | 极低(Verifier 证明) |
| 需要重启? | ✅ 是 | ❌ 否 | ❌ 否 | ❌ 否 |
| 性能 | 原生(最优) | 原生 | 🔴 较差(Context Switch) | 接近原生(JIT) |
| 内核可见性 | 完整 | 完整 | 🔴 受限 | 完整 |
| 崩溃影响 | 🔴 全系统 | 🔴 全系统 | 仅自身进程 | 不可能崩溃 🛡️ |
| 适合场景 | 核心子系统重构 | 驱动开发 | 应用层工具 | 安全/网络/可观测 |
传统方案迫使你在三个属性中最多选两个:安全性、性能、灵活性。 修改源码保证了安全和性能,但失去了灵活性(要等 5 年)。 内核模块提供了性能和灵活性,但牺牲了安全。 用户态方案保障了安全和灵活性,但牺牲了性能和可见性。 在 eBPF 出现之前,没有方案能同时满足三者。
eBPF 打破了这个三难困境。它的核心思想可以用一句话概括:
"让用户的代码在内核中运行,但在一个数学证明安全的沙盒里。"
回到城堡类比:eBPF 的做法不是让商人翻墙进入城堡(内核模块的方式), 也不是让商人在城墙外面大喊(用户态的方式)。而是在城堡内部建立了一系列 "安全工作间"(沙盒)——商人可以进入工作间执行任务, 但工作间的每面墙壁都是由 Verifier(验证器) 用数学方法证明为坚固的。 商人无法凿穿墙壁,无法访问工作间之外的区域,更无法让整座城堡倒塌。
这个方案同时获得了三个此前不可兼得的属性:
代码直接在内核空间执行,经 JIT 编译为原生机器指令,零 Context Switch。 网络处理场景下可达到内核模块 95% 以上的性能。
Verifier 在加载前通过穷举路径分析,数学证明程序不会崩溃、 不会死循环、不会越界访问。这是"证明安全"而非"希望安全"。
无需重新编译内核、无需重启系统、无需中断服务。 新能力秒级上线,对正在运行的进程和连接透明。
eBPF 带来的不仅是技术层面的突破,更是认知范式的转移。
在传统模型下,内核是一个巨大的黑盒。你只能通过有限的接口(/proc/net/tcp、
netstat、ss)窥探它的状态——就像用听诊器试图理解一台发动机的内部运作。
出了问题,你只能猜测、推理、试错。
eBPF 则把内核变成了一个白盒。你可以在内核的任意位置放置探针—— 网络协议栈的每一层、系统调用的每一个入口和出口、调度器的每一次决策、内存分配器的每一次调用—— 然后以微秒级精度实时获取数据,而对系统性能的影响 <2%。
这不是"更好的监控工具",而是观测能力的质变:从"我们能看到什么取决于内核暴露了什么", 变成了"我们想看什么就能看什么"。
从第一性原理出发,我们推导出了一条清晰的逻辑链:
CPU 保护环要求内核和用户态严格隔离 →
System Call是唯一通道但有性能代价 →
需要在内核中运行代码才能获得极致性能和可见性 →
传统方案(改源码/LKM/用户态)要么太慢、要么太危险、要么太弱 →
eBPF 是唯一能让代码同时拥有内核级权限、原生性能和崩溃豁免权的方案。
理解了"为什么",接下来我们深入探索 eBPF 到底"是什么"——它的定义、历史和核心心智模型。
To truly understand eBPF's revolutionary nature, we cannot start from conclusions. We must return to the most fundamental question: How does the OS kernel work, and why is it so hard to add new capabilities to it? Only with this cognitive foundation will eBPF's design philosophy appear not just reasonable, but inevitable.
Imagine a medieval castle. Inside the castle (Kernel Space) lies the heart of the kingdom — it governs all critical resources: army dispatching (CPU scheduling), granary management (memory management), gate communications (networking stack), and the archives (file system). The castle's administrators hold supreme authority — Ring 0, with direct access to all hardware and physical memory.
Outside the castle walls is a bustling marketplace (User Space). Merchants (applications) trade, exchange information, and serve customers. They are important, but never permitted to walk directly into the castle. Why? Because a malicious — or merely careless — merchant with direct access to the granary could cause the entire kingdom to collapse. In computing terms, this is called a Kernel Panic — a kernel crash that brings down the entire system.
This security design originates from CPU hardware itself. Modern processors (x86-64, ARM64) provide Protection Rings: the kernel runs at Ring 0 (highest privilege), user applications at Ring 3 (lowest privilege). This isn't a software convention — it's silicon-level physical isolation.
Since user-space programs can't directly enter the kernel, how do they request kernel services? The answer is the System Call — the only legitimate communication channel between user space and kernel space.
Think of it as the castle's drawbridge: a merchant who wants to ship goods
(send a network packet) cannot climb the wall — they must approach the bridge,
ring the bell (trigger a syscall instruction), state their identity and request,
and wait for the guard (kernel) to verify and execute the operation.
Every system call means a full "border inspection" — technically called a Context Switch.
A context switch is like an F1 pit stop: the car must decelerate, enter the pit lane, change tires, refuel, and accelerate back out. Even with a world-class crew, this takes 2-4 seconds — while competitors on the track don't wait.
A single context switch costs approximately 1-5 microseconds. Seems negligible, but at 100K requests/second, these microseconds accumulate into significant performance bottlenecks. The indirect cost — cache pollution after TLB and L1/L2 flushes — is often several times worse.
There is a fundamental tension between security and performance: the kernel isolates user space behind walls for safety, but every wall-crossing (System Call + Context Switch) has a performance cost. Any solution that wants kernel-level speed and access must find a way to run code inside the castle without threatening the castle's safety. This is the core contradiction eBPF resolves.
Before eBPF, developers had three paths to add kernel capabilities. Unfortunately, each was fraught with peril.
The "canonical" approach — fork the kernel, add your changes, submit patches upstream. The typical cycle from submission to mainline inclusion is 3-5 years. For "fix it tonight" production issues, this path is completely unviable.
Loadable Kernel Modules run at Ring 0 with the same privileges as the kernel but no safety boundaries. A buggy module can write to arbitrary memory, enter infinite loops, or cause a Kernel Panic. Loading third-party kernel modules in production is an unacceptable risk for most security teams.
Safe but slow. Every packet traverses two System Call boundaries, two Context Switches, and two data copies. At 100Gbps, this overhead can consume 30-50% of CPU. Worse, user-space tools have visibility blind spots — they can only see what the kernel chooses to expose.
Traditional approaches force you to choose at most two of three attributes: Safety, Performance, Flexibility. Before eBPF, no solution could deliver all three.
"Run user code inside the kernel, but in a sandbox with mathematically proven safety."
eBPF doesn't let merchants climb the castle wall (the LKM approach), nor does it force them to shout from outside (user-space). Instead, it builds secure workrooms (sandboxes) inside the castle — merchants can enter and perform tasks, but every wall of the workroom is mathematically proven sound by the Verifier. They cannot breach the walls, access areas outside, or bring down the castle.
Code executes directly in kernel space, JIT-compiled to native instructions. Zero Context Switches. Achieves 95%+ of kernel module performance.
The Verifier exhaustively analyzes all paths before loading, mathematically proving the program cannot crash, loop forever, or access out-of-bounds memory.
No kernel recompilation, no system restarts, no service interruption. New capabilities go live in seconds.
eBPF transforms the kernel from an opaque black box into a transparent white box. You can place probes at any point in the kernel — every layer of the network stack, every syscall entry and exit, every scheduler decision — and retrieve data in real-time with microsecond precision at <2% overhead.
From first principles: CPU Protection Rings mandate kernel/user isolation → System Calls are the only channel but carry performance costs → Kernel-level speed requires running code inside the kernel → Traditional approaches are too slow, too dangerous, or too weak → eBPF is the only solution that grants kernel-level privilege, native performance, and crash immunity simultaneously.
在第一章中,我们通过第一性原理推导出了"为什么需要 eBPF"。现在让我们给出精确的定义, 追溯它的历史脉络,并建立一个强大的心智模型——帮助你在后续的技术深潜中始终保持方向感。
eBPF(extended Berkeley Packet Filter)是 Linux 内核中的一个 通用型、事件驱动的运行时执行引擎。
它允许用户在不修改内核源码、不加载内核模块、不重启系统的前提下, 将经过安全验证的自定义程序动态注入内核空间执行—— 在内核的网络、安全、追踪、调度等子系统中,以接近原生的性能实现按需编程。
让我们拆解这个定义中的每一个关键词:
尽管名字中带有"Packet Filter"(包过滤),现代 eBPF 早已超越了网络领域。 它可以用于安全策略执行、性能分析、系统调用追踪、CPU 调度优化等几乎任何内核子系统。 名字已成为历史遗留——就像 JavaScript 与 Java 毫无关系一样, eBPF 与传统的 BPF 包过滤器也已有本质区别。
eBPF 程序不是"一直在运行"的守护进程。它们被挂钩(hook)到内核的特定事件点上, 只有当该事件发生时(一个网络包到达、一个系统调用被触发、一个函数被进入), 对应的 eBPF 程序才会被激活执行。事件结束,程序休眠。零轮询开销。
eBPF 不是一个应用程序,而是嵌入在内核中的一套基础设施—— 包含字节码验证器(Verifier)、即时编译器(JIT)、数据存储(Maps)和挂钩框架(Hooks)。 它为内核提供了一个安全的可编程层,就像浏览器中的 JavaScript 引擎为网页提供了可编程层。
这是 eBPF 与内核模块的本质区别。每个 eBPF 程序在加载到内核之前, 必须通过 Verifier 的静态分析——它会遍历程序所有可能的执行路径, 数学证明程序不会崩溃、不会死循环、不会越界访问。 通不过验证的程序永远无法进入内核。
eBPF 的故事横跨三十多年,从一个简单的网络过滤工具进化为 Linux 内核中最强大的可编程基础设施。 理解这段历史,有助于理解为什么 eBPF 今天的形态看似复杂却每一处设计都有其必然性。
图 2-1:eBPF 进化时间线 —— 从 1992 年的包过滤工具到 2026 年的内核可编程操作系统
1992 年,加州大学伯克利分校的 Steven McCanne 和 Van Jacobson 发表了一篇开创性论文
《The BSD Packet Filter: A New Architecture for User-level Packet Capture》。
他们发明了 BPF(Berkeley Packet Filter)——一种在内核中过滤网络数据包的高效机制,
后来成为 tcpdump 的核心引擎。
经典 BPF(后来被称为 cBPF)是一台极简的虚拟机:只有 2 个 32 位寄存器(A 累加器和 X 索引寄存器), 一套有限的指令集,程序长度被限制在最多 4096 条指令。 它的设计目标很纯粹——在内核中快速判断"这个网络包是否匹配过滤规则",是就往上送,不是就丢弃。 简洁、高效,但能力极为有限。
2014 年,Alexei Starovoitov 和 Daniel Borkmann 向 Linux 内核提交了一系列革命性的补丁, 将 BPF 从一个网络过滤器重新架构为一个通用的内核虚拟机。 这次重写的变化之大,堪称从"计算器"到"计算机"的跨越:
"eBPF"中的"e"代表"extended"。但到了今天,eBPF 的能力已经远远超出了 任何"扩展"所能描述的范围。社区已经倾向于将 eBPF 作为一个独立术语使用, 不再展开为缩写——正如"TCP"不再需要解释为"Transmission Control Protocol"一样。 eBPF 就是 eBPF。
eBPF 的一个早期痛点是可移植性。不同内核版本中,数据结构的字段偏移量可能不同——
比如 struct task_struct 中 comm 字段在内核 5.4 和 5.10 中可能在不同位置。
这意味着一个为 5.4 编译的 eBPF 程序在 5.10 上可能会读取错误的数据。
2020 年,Andrii Nakryiko 等人推出了 CO-RE(Compile Once – Run Everywhere)框架, 配合 BTF(BPF Type Format)类型信息,彻底解决了这个问题。 我们将在第三章的"编写与编译"阶段详细剖析 CO-RE 的工作机制。
eBPF 正在从传统的安全/网络/可观测性领域向更底层、更广阔的方向扩张:
这些前沿进展将在第六章详细探讨。
理解 eBPF 最有效的方式是类比:eBPF 之于 Linux 内核,正如 JavaScript 之于浏览器。 这不是一个粗略的比喻,而是在架构层面惊人地精确。
图 2-2:eBPF ≈ 内核中的 JavaScript —— 两者在架构上惊人一致:事件驱动、沙盒执行、动态扩展
让我们逐层展开这个类比:
| 架构维度 | JavaScript + 浏览器 | eBPF + Linux 内核 |
|---|---|---|
| 🎯 触发机制 | 用户事件:click、scroll、keydown |
内核事件:数据包到达、系统调用触发、函数进入、定时器到期 |
| 📝 代码形式 | JS 脚本(.js 文件) |
eBPF 字节码(.o ELF 文件) |
| ⚙️ 执行引擎 | V8 / SpiderMonkey(JIT 编译 + 沙盒) | eBPF Verifier + JIT Compiler |
| 🛡️ 安全模型 | 同源策略、CSP、沙盒隔离 | Verifier 静态验证、Helper 白名单、内存边界检查 |
| 🔌 系统接口 | DOM API、Fetch API、Web Storage | Helper 函数、Kfuncs、BPF Maps |
| 💾 数据持久化 | LocalStorage / IndexedDB | BPF Maps(Hash / Array / Ring Buffer 等) |
| 🔄 生命周期 | 页面加载 → 事件监听 → 页面关闭 | 加载 → 挂钩 → 事件驱动 → 卸载 |
| 🚫 不能做的事 | 不能访问本地文件系统(除非用户授权) | 不能调用任意内核函数(只能使用 Helper/Kfunc) |
| 🎁 核心价值 | 让静态网页变成动态交互应用 | 让静态内核变成可动态编程的平台 |
这不是巧合。JavaScript 和 eBPF 面对的是同一类工程问题: 如何在一个不可信代码和受信任的核心系统之间建立安全的可编程层。 浏览器不能让网页 JS 访问你的硬盘——否则你打开一个恶意网站就会丢失所有文件。 Linux 内核不能让 eBPF 程序调用任意内核函数——否则一个 bug 就会导致系统崩溃。
两者的解法惊人一致:沙盒 + 受控 API + JIT 编译 + 事件驱动模型。 不同的是,JS 的安全边界如果被突破,影响的是一个浏览器标签页; eBPF 的安全边界如果被突破,影响的是整个操作系统。 这就是为什么 eBPF 的 Verifier 比 V8 的安全沙盒严格了一个数量级—— 它必须在程序运行之前就证明安全,而不是在运行时检测违规。
左边是传统的"出厂固化"内核——你只能使用编译时决定的功能。点击右边的 Ignite 按钮,看看 eBPF 如何让内核在运行时"活过来"。 On the left is a traditional "factory-sealed" kernel — you can only use features decided at compile time. Click the Ignite button on the right to see how eBPF brings the kernel to life at runtime.
5.15.0-genericiptables — 固定规则,逐条匹配syslog + ftrace(手动插桩)AppArmor / SELinux(静态配置文件)5.15.0-genericiptables — fixed rules, linear matchsyslog + ftrace (manual instrumentation)AppArmor / SELinux (static config files)🧬 eBPF 可编程内核 🧬 eBPF Programmable Kernel
任何类比都有其适用范围。eBPF 与 JavaScript 有几个重要差异值得注意:
我们现在已经知道了 eBPF 是什么(内核中的通用事件驱动执行引擎), 从哪里来(1992 年的包过滤器 → 2014 年的内核虚拟机 → 2020 年的 CO-RE 可移植框架 → 2025+ 的全域扩张), 以及如何理解它(内核中的 JavaScript)。
接下来进入本文最核心的章节——第三章 HOW: 我们将跟随一个 eBPF 程序走完它的完整生命周期, 从一行 C 代码开始,到它在内核中被卸载为止。 每一个关键组件——BTF、CO-RE、Verifier、JIT、Maps、Hooks—— 都将在它登场的那个精确阶段被深入剖析。
In Chapter 1, we derived from first principles why eBPF is needed. Now let's give a precise definition, trace its history, and build a powerful mental model.
eBPF (extended Berkeley Packet Filter) is a general-purpose, event-driven runtime execution engine embedded within the Linux kernel.
It allows users to dynamically inject safety-verified custom programs into kernel space — without modifying kernel source, loading kernel modules, or rebooting — achieving on-demand programmability across networking, security, tracing, and scheduling subsystems at near-native performance.
Let's unpack every keyword in this definition:
Despite "Packet Filter" in its name, modern eBPF extends far beyond networking — it handles security enforcement, performance profiling, syscall tracing, CPU scheduling, and virtually any kernel subsystem. The name is a historical artifact — much like JavaScript has nothing to do with Java.
eBPF programs are not long-running daemons. They are hooked to specific kernel events and only activate when those events fire — a packet arrives, a syscall triggers, a function is entered. Event ends, program sleeps. Zero polling overhead.
eBPF is not an application — it's kernel infrastructure: a bytecode Verifier, JIT compiler, data stores (Maps), and hook framework. It provides the kernel with a safe programmability layer, just as V8 provides the browser with one.
This is the fundamental distinction from kernel modules. Every eBPF program must pass the Verifier's static analysis before loading — it exhaustively proves the program cannot crash, loop forever, or access out-of-bounds memory. Programs that fail verification never enter the kernel.
Steven McCanne and Van Jacobson at UC Berkeley created BPF (Berkeley Packet Filter) —
a minimal in-kernel virtual machine with 2 × 32-bit registers and a
4,096 instruction limit, designed solely for tcpdump-style packet filtering.
Alexei Starovoitov and Daniel Borkmann re-architected BPF from a packet filter into a general-purpose kernel virtual machine: 11 × 64-bit registers (R0-R10), a RISC-like instruction set, JIT compilation, Maps, Helper functions, and the Verifier. eBPF transcended networking to become a universal programmability platform.
Andrii Nakryiko introduced CO-RE (Compile Once – Run Everywhere) with BTF, solving the cross-kernel-version compatibility nightmare. We'll dissect this mechanism in Chapter 3.
eBPF is expanding into Netkit (zero-copy container networking), sched_ext (custom CPU scheduling), gpu_ext (GPU management), and LLM observability (tracking TTFT and ITL metrics). Covered in detail in Chapter 6.
The most effective way to understand eBPF is through analogy: eBPF is to the Linux kernel what JavaScript is to the browser. This isn't a loose metaphor — it's architecturally precise.
| Dimension | JavaScript + Browser | eBPF + Linux Kernel |
|---|---|---|
| 🎯 Trigger | User events: click, scroll, keydown |
Kernel events: packet arrival, syscall, function entry, timer |
| 📝 Code Form | JS scripts (.js) |
eBPF bytecode (.o ELF) |
| ⚙️ Engine | V8 / SpiderMonkey (JIT + Sandbox) | eBPF Verifier + JIT Compiler |
| 🛡️ Security | Same-Origin Policy, CSP, Sandbox | Static Verification, Helper allowlist, bounds checking |
| 🔌 System API | DOM API, Fetch, Web Storage | Helper functions, Kfuncs, BPF Maps |
| 💾 Storage | LocalStorage / IndexedDB | BPF Maps (Hash / Array / Ring Buffer) |
| 🎁 Core Value | Static pages → Dynamic interactive apps | Static kernel → Dynamically programmable platform |
It's not coincidence. JavaScript and eBPF solve the same class of engineering problem: how to build a safe programmability layer between untrusted code and a trusted core system. Both arrive at the same solution: Sandbox + Controlled APIs + JIT + Event-Driven Model.
The difference: if a JS sandbox is breached, one browser tab is affected. If eBPF's safety boundary is breached, the entire operating system is at risk. This is why the Verifier is an order of magnitude stricter than V8's sandbox — it must prove safety before execution, not detect violations at runtime.
We now know what eBPF is (a general-purpose event-driven execution engine in the kernel), where it came from (1992 packet filter → 2014 kernel VM → 2020 CO-RE → 2025+ full-spectrum expansion), and how to think about it (JavaScript for the kernel).
Next comes the core chapter — Chapter 3: HOW — where we follow an eBPF program through its complete lifecycle, from a single line of C code to its unloading from the kernel.
这是本文的核心章节。我们将跟随一个 eBPF 程序走完它从"出生"到"终结"的每一步。 所有关键技术组件——BTF、CO-RE、Verifier、JIT、Maps、Hooks—— 都将在它们登场的精确时刻被深入剖析,而非作为孤立的百科词条。
想象你手里有一段 C 代码,你的目标是让它在 Linux 内核中安全运行、响应事件、收集数据。 这段代码需要经历七个阶段的淬炼,每一步都有严格的把关者。
图 3-0:eBPF 程序完整生命周期全景 —— 七个阶段跨越用户空间与内核空间的边界
一个 eBPF 程序的旅程从一段 C 或 Rust 源代码开始。 这段代码看起来和普通的 C 代码很像,但有两个重要的特殊之处: 它使用 SEC() 宏标注挂载点(告诉 loader 这段程序应该被挂到哪个内核事件上), 并且只能调用内核预先批准的 Helper 函数。
让我们从一个最简单但完整的例子开始:
C — minimal eBPF program (libbpf)// hello.bpf.c — 你的第一个 eBPF 程序 #include <linux/bpf.h> #include <bpf/bpf_helpers.h> // SEC() 宏:声明程序类型和挂载点 // "tp/syscalls/sys_enter_execve" 表示: // 挂载到 tracepoint → syscalls → sys_enter_execve 事件 // 即:每当有进程调用 execve() 执行新程序时触发 SEC("tp/syscalls/sys_enter_execve") int hello_exec(void *ctx) { // bpf_printk:内核 trace 管道输出(调试用) bpf_printk("Hello eBPF! A process called execve.\n"); return 0; } // 许可证声明:必须为 GPL 兼容,否则 Verifier 拒绝加载 char LICENSE[] SEC("license") = "GPL";
这不仅仅是法律合规要求。Linux 内核中许多 Helper 函数(如 bpf_probe_read_kernel、
bpf_get_current_task)被标记为 GPL-only。
如果你的 eBPF 程序不声明 GPL 兼容许可证,Verifier 会拒绝你调用这些函数——
这是内核社区通过技术手段强制执行的开源契约。
写好代码后,我们使用 Clang/LLVM 编译器将其编译为 BPF 字节码:
Shell# 编译为 BPF 目标的 ELF 文件 clang -O2 -target bpf -g -c hello.bpf.c -o hello.bpf.o # -O2 : 启用优化(必须,Verifier 对未优化代码很不友好) # -target bpf : 输出 BPF 架构的目标代码 # -g : 包含调试信息(生成 BTF 需要)
编译的输出是一个标准的 ELF(Executable and Linkable Format)文件, 但目标架构不是 x86 或 ARM,而是 BPF——eBPF 虚拟机的指令集架构。 根据 IETF RFC 9669 的定义,BPF 指令集有两种编码格式: 基本格式(64 位宽指令)和宽指令格式(128 位), 包含算术、跳转、内存访问和原子操作等指令类别。
这个 ELF 文件中包含了几个关键部分:
SEC("license") 中声明的内容要理解 BTF,先理解它解决的问题。
内核中充满了复杂的数据结构。例如 struct task_struct——它代表一个进程,
包含数百个字段(PID、进程名、内存映射、调度信息……)。eBPF 程序经常需要读取这些结构中的特定字段。
但问题来了:不同内核版本中,同一个结构体的内存布局可能完全不同——
某个字段在 5.4 内核中偏移量是 648 字节,到了 5.10 可能变成了 672 字节。
传统调试信息格式 DWARF 可以描述这些布局,但它的体积巨大—— 一个内核的完整 DWARF 信息可以达到 数百 MB。 这对于嵌入在内核中运行的 eBPF 来说完全不可接受。
BTF(BPF Type Format)应运而生。它是一种极度精简的类型描述格式,
仅记录 eBPF 程序可能需要的结构体名称、字段名、字段类型和偏移量。
同样的内核类型信息,BTF 的体积仅为 DWARF 的 约 1/100——
通常只有几 MB,可以直接嵌入内核镜像(vmlinux)中。
从 Linux 5.2 开始,内核编译时会自动生成一个 BTF 数据段,嵌入在
/sys/kernel/btf/vmlinux 中。这个文件完整描述了当前运行内核的所有数据结构布局。
开发者可以用 bpftool btf dump file /sys/kernel/btf/vmlinux 查看它,
或使用工具将其导出为 C 头文件(vmlinux.h),
在编译 eBPF 程序时直接引用——无需安装内核头文件包。
用类比来说:如果内核数据结构是一座复杂建筑的内部结构, DWARF 就像一套完整的建筑蓝图(数百页,精确到每根管线), 而 BTF 则是一张X 光透视图——只显示你关心的承重墙和走廊位置, 但足以让你安全地在建筑内穿行。体积小了两个数量级,信息却恰好够用。
有了 BTF 这张"X 光图",我们就可以解决跨版本兼容性的终极难题—— 这就是 CO-RE(Compile Once – Run Everywhere)的使命。
把 CO-RE 想象成一个万能电源适配器: 你买了一台笔记本电脑,需要在全世界使用——美国 110V/A 型插头、 日本 100V/A 型、欧洲 230V/C 型、英国 230V/G 型…… 如果你为每个国家编译一个专属充电器(即为每个内核版本单独编译 eBPF 程序), 那你出门就得带一箱充电器。
CO-RE 的做法是:编译时记录你访问了哪些字段(例如"task_struct 的 comm 字段"), 加载时读取目标机器上的 BTF 信息(得知"comm 字段在这个内核上的实际偏移是 672"), 然后在加载过程中自动重写字节码中的偏移量。
图 3-1:CO-RE 工作原理 —— 编译时记录字段引用,加载时根据目标内核 BTF 自动重写偏移量
在 CO-RE 出现之前,每次部署 eBPF 程序都需要在目标机器上现场编译—— 这意味着每台机器都必须安装 Clang、LLVM 和完整的内核头文件。 对于管理数万台服务器的企业来说,这是一场运维噩梦。
CO-RE 让你可以在 CI/CD 环境中编译一次,然后将同一个二进制文件分发到运行不同内核版本的所有机器—— 就像 Java 的"Write Once, Run Anywhere",但是在内核级别实现的。 这是 eBPF 从"研究工具"走向"企业级基础设施"的关键转折点。
ELF 文件编译好了。现在,它需要从用户空间进入内核空间——穿越第一章中描述的"城墙"。
这个过程有且只有一个入口:bpf() 系统调用。
C — bpf(2) system call// 内核暴露的系统调用接口 int bpf(int cmd, union bpf_attr *attr, unsigned int size); // 关键命令: // BPF_PROG_LOAD — 加载 eBPF 程序到内核 // BPF_MAP_CREATE — 创建 BPF Map // BPF_LINK_CREATE — 将程序挂载到 Hook 点 // BPF_PROG_ATTACH — 附着程序到目标 // ...
当用户态程序(Loader)调用 bpf(BPF_PROG_LOAD, ...) 时,
它将 ELF 中的 BPF 字节码传递给内核。但字节码不会直接执行——
它首先必须通过 eBPF 世界中最严厉的守门人:Verifier(验证器)。
如果 eBPF 的安全性有一个总设计师,那就是 Verifier。 它是整个 eBPF 架构中最关键、最复杂的组件—— 没有它,eBPF 就只是另一种内核模块,存在一切内核模块的风险。
把 Verifier 想象成一座城堡的最高法院: 每一段代码要进入城堡,必须先在法庭上接受逐行审判。 法官不听辩护词、不看推荐信——他只看证据(代码本身), 并亲自走遍程序所有可能的执行路径, 确认每一步都不会危害城堡的安全。只要有一条路径存在风险, 整个程序就会被驳回——没有例外,没有上诉。
图 3-2:Verifier 核心算法 —— 通过有界 DFS 遍历所有路径,追踪寄存器状态,剪枝避免指数爆炸
Verifier 的核心算法可以概括为五个步骤:
第一步:有界深度优先搜索(Bounded DFS)。
Verifier 从程序的入口指令开始,沿着控制流图的每一条分支递归遍历。
遇到 if-else,它会分别进入两条路径;
遇到循环,它会展开有限次迭代(在 Linux 5.3+ 中支持有界循环,但循环次数必须在编译期可确定)。
注意:这不是简单的 DAG(有向无环图)遍历——
eBPF 程序可以包含有界循环和复杂的条件分支,Verifier 必须处理这些情况。
第二步:寄存器状态追踪。
在每一步,Verifier 都维护着 11 个寄存器(R0-R10)的完整状态快照。
每个寄存器的状态不仅包括"当前值",还包括它的类型(标量值、Map 指针、栈指针、NULL……)、
值范围(例如 R3 ∈ [0, 1024])和对齐方式。
当代码执行 map_lookup_elem() 返回结果到 R0 时,
Verifier 将 R0 标记为 MAP_VALUE_OR_NULL——它可能是有效指针,也可能是 NULL。
如果后续代码不检查 NULL 就直接解引用(*R0),Verifier 会立刻拒绝。
第三步:状态剪枝(State Pruning)。 如果没有剪枝,路径数量会随分支数指数级增长。 剪枝的核心思想是:如果程序到达某个点时的寄存器状态是之前已验证过的某个状态的 子集(即更受限),那么这条路径一定也是安全的——无需重复验证。 这将指数级复杂度降低到可控范围。
第四步:逐指令安全性检查。对每条指令,Verifier 检查:
第五步:终止性保证。 程序的总指令复杂度不得超过 100 万条(自 Kernel 5.2 起, 此前为 4096 条)。所有循环必须有可证明的有限迭代次数。 这从数学上保证了程序必然终止——不会在内核中陷入死循环。
Verifier 保证的是结构安全(不崩溃、不越界、必终止), 而非逻辑正确。一个通过 Verifier 的程序可能包含逻辑 bug—— 例如错误地丢弃了正常网络包,或把数据写入了错误的 Map key。 Verifier 防止的是内核级灾难(Kernel Panic),而非应用级错误。
此外,Verifier 的静态分析天然是保守的: 它可能会拒绝一些实际上安全但它无法证明安全的程序。 这是安全领域的经典权衡——宁可误拒,绝不误放(false positive over false negative)。 有经验的 eBPF 开发者需要学会"讨好 Verifier"—— 通过显式的边界检查和类型断言帮助 Verifier 理解你的意图。
程序通过了 Verifier 的审判,证明是安全的。但此时它仍然是 BPF 字节码—— 一种中间表示(IR),类似 Java 的 bytecode。 如果直接解释执行这些字节码,每条指令都需要一个"指令分发循环"(fetch-decode-execute), 性能会比原生代码慢 5-10 倍。
这就是 JIT(Just-In-Time)编译器登场的时刻。 JIT 将 BPF 字节码一对一翻译为目标 CPU 的原生机器指令—— x86-64 上是 Intel/AMD 指令,ARM64 上是 ARM 指令。 翻译完成后,BPF 字节码被丢弃,后续每次执行的都是纯粹的原生代码, 没有任何解释开销。
eBPF 设计的精妙之处在于,它的虚拟寄存器 R0-R9 与现代 CPU 的物理寄存器之间 存在近乎完美的一一映射关系,如同为 JIT 量身定制。
| BPF 虚拟寄存器 | 语义 | x86-64 物理寄存器 | ARM64 物理寄存器 |
|---|---|---|---|
R0 | 函数返回值 | rax | x7 |
R1 | 第 1 参数 | rdi | x0 |
R2 | 第 2 参数 | rsi | x1 |
R3 | 第 3 参数 | rdx | x2 |
R4 | 第 4 参数 | rcx | x3 |
R5 | 第 5 参数 | r8 | x4 |
R6-R9 | 被调用者保存 | rbx, r13-r15 | x19-x22 |
R10 | 栈帧指针(只读) | rbp | x25 |
这意味着 JIT 在翻译时几乎不需要额外的寄存器搬移指令——
BPF 的 R1 直接就是 x86-64 的 rdi(函数第一参数寄存器),
R0 直接就是 rax(返回值寄存器)。
这种设计使得 JIT 编译后的代码与手写汇编几乎零性能差距。
你可以使用 bpftool 直接查看 JIT 编译后的机器指令:
Shell — bpftool JIT dump# 查看已加载的 eBPF 程序列表 $ bpftool prog list 42: tracepoint name hello_exec tag a1bc... gpl loaded_at 2026-04-07T14:30:00+0900 uid 0 xlated 96B jited 68B memlock 4096B # 查看 BPF 字节码(xlated = 经过 Verifier 后的中间表示) $ bpftool prog dump xlated id 42 0: (b7) r1 = 0x6f57206f6c6c6548 // "Hello Wo" 1: (6b) *(u16 *)(r10 - 4) = r1 2: (b7) r1 = 0x21646c72 // "rld!" ... # 查看 JIT 编译后的原生机器码 ← 这才是 CPU 实际执行的 $ bpftool prog dump jited id 42 0xffffffffc0750001: 0: push rbp // 保存栈帧 1: mov rbp, rsp // 建立新栈帧 4: sub rsp, 0x10 // 分配栈空间 8: movabs rdi, 0x6f57206f6c6c6548 12: mov QWORD PTR [rbp-16], rdi ... 52: call 0xffffffffc0123456 // bpf_trace_printk 57: xor eax, eax // return 0 59: leave 60: ret
注意上面输出中的 xlated 96B jited 68B——JIT 后的代码反而更小了。
这是因为 BPF 字节码的每条指令固定占 8 字节(64 位编码),
而 x86-64 是变长指令集(CISC),一些常见操作(如 xor eax, eax)只需要 2 字节。
JIT 编译器还会执行窥孔优化(peephole optimization),合并相邻指令。
最终结果是:JIT 后的代码不仅更快,还更紧凑。
到目前为止,我们的 eBPF 程序已经完成了三个关键蜕变:
C 源码 →(Clang/LLVM + BTF + CO-RE)→ BPF 字节码 →(Verifier 数学证明安全)→ 原生机器指令(JIT 零开销映射)
此刻,程序已经是一段经过安全认证的、高效的原生机器代码, 安静地存在于内核内存中——但它还没有"上岗"。 它需要被挂载到一个 Hook 点上,等待内核事件的触发。 这就是下一阶段的任务。
This is the core chapter. We will follow an eBPF program through every step from "birth" to "termination." All key components — BTF, CO-RE, Verifier, JIT, Maps, Hooks — will be dissected at the precise moment they appear in the lifecycle.
An eBPF program begins as C or Rust source code.
It uses the SEC() macro to declare its hook point and may only call
kernel-approved Helper functions.
C — minimal eBPF program// hello.bpf.c — Your first eBPF program #include <linux/bpf.h> #include <bpf/bpf_helpers.h> SEC("tp/syscalls/sys_enter_execve") int hello_exec(void *ctx) { bpf_printk("Hello eBPF! A process called execve.\n"); return 0; } char LICENSE[] SEC("license") = "GPL";
BTF (BPF Type Format) is an ultra-compact type description format —
roughly 1/100th the size of DWARF — that records struct names, field names,
types, and offsets. It's embedded in the kernel image at
/sys/kernel/btf/vmlinux since Linux 5.2.
Think of CO-RE as a universal power adapter: compile once on your dev machine, and the same ELF binary runs on any kernel version. At load time, libbpf reads the target kernel's BTF, identifies actual field offsets, and rewrites the bytecode's offsets in-place.
The compiled ELF enters the kernel via the bpf() system call —
the only gateway. But before execution, every program must pass the Verifier.
The Verifier is the most critical component in the eBPF architecture. Its core algorithm is Bounded Depth-First Search with State Pruning:
The Verifier guarantees structural safety (no crashes, no OOB, must terminate), not logical correctness. A verified program may still contain logic bugs. Additionally, its static analysis is inherently conservative — it may reject programs that are safe but unprovable. Better a false reject than a false accept.
After verification, the bytecode is translated to native machine instructions
by the JIT compiler. eBPF's virtual registers R0-R9 map almost 1:1
to physical CPU registers (e.g., R1 → rdi on x86-64),
resulting in near-zero overhead compared to hand-written assembly.
Shell# List loaded eBPF programs $ bpftool prog list 42: tracepoint name hello_exec tag a1bc... xlated 96B jited 68B memlock 4096B # Dump JIT-compiled native code $ bpftool prog dump jited id 42 0: push rbp 1: mov rbp, rsp ... 52: call 0xffffffffc0123456 // bpf_trace_printk 57: xor eax, eax 59: leave 60: ret
Our eBPF program has undergone three transformations: C source → (Clang/LLVM + BTF + CO-RE) → BPF bytecode → (Verifier mathematical proof) → Native machine instructions (JIT zero-overhead mapping).
It's now a certified, efficient native code sitting in kernel memory — but it hasn't been "deployed" yet. It needs to be attached to a Hook point, waiting for kernel events. That's the next stage.
经过编译、验证和 JIT 三道淬炼,我们的 eBPF 程序已经是一段安全的原生机器代码, 静静地存在于内核内存中。但它仍然是"待命状态"—— 就像一名已经通过所有背景调查的特工,拿到了进入城堡的许可证, 但还没有被分配到具体的岗位。
这一阶段要做两件事:加载(将程序和 Maps 注入内核)和挂钩(将程序绑定到内核事件点)。 这是 eBPF 从"存在"走向"生效"的关键一步,也是理解 eBPF 工作机制的核心所在。
用户态的 Loader(通常是 libbpf 库或 bpftool 命令行工具)
负责读取编译好的 ELF 文件,通过 bpf() 系统调用将程序和 Maps 加载到内核中。
Loader 的工作流程如下:
bpf(BPF_MAP_CREATE, ...),内核返回 Map 的文件描述符(fd)bpf(BPF_PROG_LOAD, ...),触发 Verifier → JIT → 返回程序 fd
现代 libbpf 开发中,你不需要手动执行上述每一步。
bpftool gen skeleton hello.bpf.o 会自动生成一个 C 头文件(hello.skel.h),
其中包含了所有加载、挂载和销毁逻辑的封装函数。用户态代码只需三行:
struct hello_bpf *skel = hello_bpf__open_and_load();
hello_bpf__attach(skel);
// ... 程序运行中 ...
hello_bpf__destroy(skel);
如果说 Verifier 是 eBPF 的"安全基石",那么 Hook 机制就是 eBPF 的"灵魂"。 没有 Hook,eBPF 程序只是内核中一段沉默的代码; 有了 Hook,它就变成了一个敏锐的感知器—— 能够在内核数据流的任意关键节点上拦截、观测、决策。
要真正理解 Hook,我们先追溯它的设计哲学。
在软件工程中有一条著名的 好莱坞原则(Hollywood Principle):
"Don't call us, we'll call you."(别打电话给我们,我们会打给你。)
在好莱坞,演员面试后不应该反复打电话催促——导演会在需要的时候主动联系你。 这个原则的技术表达叫做 控制反转(Inversion of Control, IoC): 不是你的代码去轮询(polling)内核"发生了什么事", 而是内核在事件发生时主动调用你的代码。
这形成了一条清晰的演化链条:
图 3-3:eBPF Hook 设计哲学演化链 —— 从好莱坞原则到控制反转到事件驱动到内核 Hook
在物理层面,Hook 的本质极为简洁:内核在关键代码路径上预留了函数指针插槽。 当你挂载一个 eBPF 程序时,实际上就是把程序的入口地址填入这个插槽。 当内核执行到该路径时,它会检查插槽——如果非空,就调用你的程序。 就像在一条高速公路上安装了一个收费站:车辆(数据)经过时, 收费站可以检查、记录、放行或拦截。卸载时,把插槽清空——收费站消失,高速公路恢复原样。
eBPF 的威力在很大程度上取决于它能挂载到多少种内核事件。 截至 Linux 6.12,eBPF 支持数十种程序类型,覆盖内核的几乎所有子系统。 以下是最重要的几类:
图 3-4:eBPF Hook 全景图 —— 从网卡驱动层(XDP)到应用层,以及安全(LSM)、调度(sched_ext)等非网络 Hook
在网络场景中,XDP 和 TC 是最常用的两个 Hook 点,它们的定位截然不同:
| 维度 | XDP (eXpress Data Path) | TC BPF (cls_bpf) |
|---|---|---|
| 位置 | 网卡驱动层(skb 分配之前) | 流量控制层(skb 分配之后) |
| 性能 | ⚡ 极致(单核可达 24 Mpps) | 🚀 很快(但低于 XDP) |
| 数据访问 | 原始数据包(xdp_md),无 skb 元数据 |
完整的 __sk_buff,含 L3/L4 元数据 |
| 修改能力 | 可修改包内容、头部封装/解封 | 可修改包 + 可修改 skb 元数据(mark/priority) |
| 动作 | XDP_PASS / DROP / TX / REDIRECT |
TC_ACT_OK / SHOT / REDIRECT / PIPE |
| 方向 | 仅入站(ingress) | 入站 + 出站(ingress & egress) |
| 驱动要求 | 需要网卡驱动支持(native mode) | 所有网卡通用 |
| 典型场景 | DDoS 防护、负载均衡、数据包过滤 | NAT、策略路由、QoS、包标记 |
秘密在于"跳过"。在传统的网络路径中,数据包从网卡 DMA 到达后,
内核会为其分配一个 sk_buff (skb) 结构体——这是一个相当复杂的对象,
包含几十个字段和各种指针。这个分配过程本身就有不小的 CPU 开销。
XDP 的关键创新是:在 skb 分配之前就拦截数据包。
它直接操作网卡 DMA 缓冲区中的原始数据。
如果判定为恶意流量(如 DDoS 攻击包),立刻 XDP_DROP——
数据包被丢弃,skb 从未被分配、协议栈从未被触发。
这就是 XDP 能达到每核每秒处理数百万包(Mpps)的根本原因。
eBPF 程序在沙盒中运行,不能调用任意的内核函数——它只能使用内核预先批准的一组函数, 称为 Helper Functions。这些函数是内核精心设计的安全 API, 每一个都经过严格审查,确保不会破坏内核状态。
Helper 函数覆盖了 eBPF 程序可能需要的几乎所有操作:
| Helper 函数 | 用途 | 典型场景 |
|---|---|---|
bpf_map_lookup_elem() |
查询 Map 中的值 | IP 黑名单查询、连接跟踪 |
bpf_map_update_elem() |
写入/更新 Map 中的值 | 计数器更新、状态记录 |
bpf_probe_read_kernel() |
安全读取内核内存 | 读取 task_struct 字段 |
bpf_get_current_pid_tgid() |
获取当前进程 PID | 安全审计、进程过滤 |
bpf_get_current_comm() |
获取当前进程名 | 追踪特定程序的行为 |
bpf_ktime_get_ns() |
获取纳秒级时间戳 | 延迟测量、性能剖析 |
bpf_trace_printk() |
输出调试信息到 trace 管道 | 开发调试(生产慎用) |
bpf_redirect() |
重定向数据包到其他接口 | 负载均衡、容器网络 |
bpf_send_signal() |
向进程发送信号 | Tetragon: SIGKILL 恶意进程 |
从 Linux 6.x 开始,内核引入了 Kfuncs(Kernel Functions)作为 Helper 的进化方向。 与 Helper 不同,Kfuncs 不需要在内核中硬编码注册—— 内核开发者可以将任何内核函数标记为可供 eBPF 调用, 并通过 BTF 自动暴露其签名和参数类型。 这大幅降低了扩展 eBPF 能力的门槛,新功能不再需要等待正式的 Helper 函数审批流程。
单个 eBPF 程序有指令限制(历史上 4096 条,现在 100 万条), 并且栈空间只有 512 字节。 对于复杂的处理逻辑(如多层协议解析、多阶段安全检查),一个程序可能不够用。
尾调用(Tail Call)解决了这个问题。它允许一个 eBPF 程序在执行结束时 跳转到另一个 eBPF 程序——不是函数调用(不占用栈空间), 而是完全替换当前执行上下文。就像接力赛中的交接棒: 第一棒跑完了,把棒子交给第二棒,第二棒接着跑。
尾调用通过一个特殊的 BPF_MAP_TYPE_PROG_ARRAY Map 实现:
Map 的键是整数索引,值是其他 eBPF 程序的文件描述符。
执行 bpf_tail_call(ctx, prog_array, index) 时,
当前程序终止,prog_array[index] 指向的程序接管执行。
为防止无限递归,尾调用深度被限制为 最多 33 层。
早期的 eBPF 挂载机制有一个问题:当用户态的 Loader 进程退出时(无论是正常退出还是崩溃), 已挂载的 eBPF 程序也会被自动卸载——因为内核通过文件描述符跟踪所有权, 进程退出后 fd 被关闭,引用计数归零,程序被释放。
bpf_link(Linux 5.7+)解决了这个问题。
它在 eBPF 程序和 Hook 点之间创建了一个持久化的连接对象。
即使创建它的进程退出,bpf_link 可以被固定(pin)到 BPF 文件系统
(/sys/fs/bpf/),使程序持续运行。
更重要的是,bpf_link 支持原子替换——
你可以在不中断服务的情况下将旧版程序替换为新版程序。
Shell — bpf_link pinning# 将 bpf_link 固定到 BPF 文件系统,实现持久化 $ bpftool link pin id 15 /sys/fs/bpf/my_xdp_link # 即使 loader 进程退出,eBPF 程序仍然挂载在 Hook 点上 $ bpftool link list 15: xdp prog 42 dev eth0 pinned /sys/fs/bpf/my_xdp_link
程序已经"就位"——通过 Loader 注入内核、通过 bpf_link 挂载到目标 Hook 点、 获得了 Helper/Kfunc API 的调用能力、必要时还能通过 Tail Call 构建处理管道。
现在,它正安静地等待—— 等待那个事件的到来。一旦事件触发,内核会通过函数指针瞬间唤醒它。 这就是下一个阶段——运行。
这是整个生命周期中最"安静"但最"关键"的阶段——因为它几乎不需要你做任何事情。
当挂载点对应的内核事件发生时——
一个网络数据包到达网卡(XDP)、
一个进程调用了 execve()(Tracepoint)、
一个文件被打开(LSM)、
一次 TCP 连接被建立(Kprobe on tcp_v4_connect)——
内核的执行流会自然地经过挂载点,检查函数指针插槽,
发现有 eBPF 程序注册在此,然后直接调用它。
让我们详细看看这个过程中发生了什么:
图 3-5:eBPF 运行时执行流程 —— 事件触发 → Hook 检查 → 原生代码执行 → 返回结果,全过程零 Context Switch
整个执行过程有三个核心特征值得强调:
① 零 Context Switch(零上下文切换)。 这是 eBPF 性能优势的根本来源。 传统的用户态工具(如 Sidecar Proxy)处理一个网络包需要: 内核 → 用户态(Context Switch #1)→ 处理 → 用户态 → 内核(Context Switch #2)。 eBPF 则完全在内核上下文中执行——数据包到达时,处理程序在同一个 CPU 核心上、同一个上下文中直接运行, CPU 缓存保持热态,无需任何保存/恢复寄存器的操作。 这就是为什么 eBPF 可以在网络处理中实现 4X 吞吐提升, 同时保持 <2% CPU 开销。
② 抢占式安全(Preemptive Safety)。 eBPF 程序在执行期间不会被抢占(在大多数 Hook 类型中以 RCU read-side critical section 或禁止抢占的方式运行)。 这确保了执行的原子性——程序要么完整执行完毕并返回结果, 要么因为指令限制而终止。不存在"执行到一半被中断"导致的不一致状态。
③ 按事件付费(Pay-per-Event)。
eBPF 程序只在事件发生时消耗 CPU 资源。
如果一台服务器 10 分钟内没有可疑的 execve() 调用,
挂载在该 Tracepoint 上的 eBPF 程序 10 分钟内的 CPU 消耗精确地为零。
这与传统的轮询式监控(如定期查询 /proc)形成鲜明对比。
每个 eBPF 程序在被调用时都会收到一个 ctx(上下文)参数,
它的类型取决于 Hook 类型:
struct xdp_md *ctx — 包含数据包起止指针、接收接口等struct __sk_buff *ctx — 包含完整的 skb 元数据(协议、长度、mark 等)struct trace_event_raw_xxx *ctx — 包含事件参数(如 execve 的文件名、参数列表)struct pt_regs *ctx — 包含被探测函数的寄存器状态(参数值)bprm_check_security 的参数)eBPF 程序通过这个 ctx 参数感知世界, 再通过 Helper 函数和 BPF Maps 与外部交换信息。
After compilation, verification, and JIT, our program is certified native code in kernel memory — but still in standby. This stage loads the program and attaches it to a kernel event hook.
"Don't call us, we'll call you."
This is Inversion of Control (IoC) applied to the kernel: your code doesn't poll for events — the kernel calls your code when events occur. Physically, a Hook is a function pointer slot in a kernel code path. Attaching an eBPF program fills that slot with the program's entry address.
| Hook Type | Layer | Key Use Cases |
|---|---|---|
| XDP | NIC Driver (pre-skb) | DDoS mitigation, load balancing (24 Mpps/core) |
| TC BPF | Traffic Control (post-skb) | NAT, policy routing, QoS, packet marking |
| Kprobes | Any kernel function | Dynamic tracing, debugging (unstable ABI) |
| Tracepoints | Predefined kernel points | Long-term monitoring (stable ABI) |
| LSM | Security module hooks | Security policy enforcement (Kernel 5.7+) |
| Uprobes | User-space functions | Application tracing (e.g., SSL_write) |
| sched_ext | CPU scheduler | Custom scheduling policies (Kernel 6.12+) |
eBPF programs can only call pre-approved Helper Functions — carefully audited kernel APIs covering map operations, memory reads, time queries, packet manipulation, and signal sending. From Linux 6.x, Kfuncs extend this model by allowing any kernel function to be exposed to eBPF via BTF annotations.
Tail Calls allow one eBPF program to jump to another at the end of execution — like a relay baton handoff. This enables complex multi-stage processing within the 512-byte stack limit. Maximum chain depth: 33 levels.
bpf_link (Linux 5.7+) creates a persistent connection between an eBPF program and its hook point.
It can be pinned to /sys/fs/bpf/ to survive loader process exits,
and supports atomic replacement for zero-downtime upgrades.
This is the "quietest" yet most critical stage — because it requires nothing from you.
When the corresponding kernel event occurs, the kernel's execution flow naturally reaches the hook point, finds the registered eBPF program, and directly invokes the JIT-compiled native code.
Three core properties define this execution:
The program runs on the same CPU core, in the same context as the triggering event. CPU caches stay hot. No register save/restore. This is why eBPF achieves 4X throughput.
Verified safe → cannot crash. Instruction limit → must terminate. Bounds checked → cannot access out-of-bounds memory. Even a buggy program won't Kernel Panic.
If no events fire for 10 minutes, the eBPF program consumes exactly zero CPU. This is fundamentally different from polling-based monitoring.
Each program receives a ctx parameter whose type depends on the hook:
XDP gets xdp_md (packet pointers), TC gets __sk_buff (full skb metadata),
Tracepoints get event-specific structs, Kprobes get pt_regs (register state).
Through ctx, the program perceives the world;
through Helpers and Maps, it communicates with the outside.
eBPF 程序在内核中运行、收集数据、做出决策——但这些信息如果无法传递到用户空间, 就像一个在前线侦察的特工无法向总部汇报——再好的情报也毫无意义。
eBPF 生态中解决这个问题的核心机制叫做 BPF Maps—— 它是内核态和用户态之间的高速数据通道。
BPF Map 是一种驻留在内核内存中的键值存储结构。
它同时对 eBPF 程序(内核态)和用户态程序可见——
eBPF 程序通过 Helper 函数读写 Map,用户态程序通过 bpf() 系统调用读写同一个 Map。
这就建立了一条绕过传统 System Call 数据路径的高效通道。
Linux 内核提供了多种 Map 类型,每种针对不同的使用场景:
| Map 类型 | 数据结构 | 典型用途 |
|---|---|---|
BPF_MAP_TYPE_HASH |
哈希表(任意 key → value) | IP 黑名单、连接跟踪表、计数器 |
BPF_MAP_TYPE_ARRAY |
固定大小数组(整数索引) | 配置参数、统计直方图 |
BPF_MAP_TYPE_PERF_EVENT_ARRAY |
Per-CPU 环形缓冲区 | 事件流推送(旧方案) |
BPF_MAP_TYPE_RINGBUF |
共享环形缓冲区(5.8+) | ⭐ 事件流推送(2025 最佳实践) |
BPF_MAP_TYPE_LRU_HASH |
LRU 淘汰的哈希表 | 缓存、会话状态(自动淘汰最旧条目) |
BPF_MAP_TYPE_PROG_ARRAY |
eBPF 程序引用数组 | Tail Call 跳转表 |
BPF_MAP_TYPE_PERCPU_HASH |
每 CPU 独立的哈希表 | 高并发计数器(无锁) |
BPF_MAP_TYPE_SOCKMAP |
Socket 引用 Map | Socket 级重定向、零拷贝数据转发 |
当 eBPF 程序需要将事件数据流(如每次 syscall 的参数、每个网络连接的元数据) 推送到用户态时,有两种主要的 Map 类型可选: 传统的 Perf Buffer(Linux 4.4+)和新一代的 Ring Buffer(Linux 5.8+)。
图 3-6:Ring Buffer vs Perf Buffer —— Ring Buffer 用共享单环取代 Per-CPU 多环,更高效、更有序、更省内存
除非你需要支持 Linux 5.8 之前的内核,否则始终优先选择 Ring Buffer。 它在内存效率(单一缓冲区 vs Per-CPU 多份)、数据有序性(FIFO vs 多路归并)、 和读取效率(mmap 零拷贝 vs 数据拷贝)上全面优于 Perf Buffer。
Liz Rice 在《Learning eBPF》中也明确建议: "For new code, you should prefer to use BPF ring buffers over BPF perf buffers."
现在让我们把生命周期中学到的所有概念串联起来,通过两个渐进式的代码示例来实践。
BCC(BPF Compiler Collection)是入门 eBPF 最快的方式—— 它在运行时编译 C 代码,隐藏了 ELF 解析、Verifier 交互、Map 管理等所有底层细节。 代价是每次运行都需要 LLVM 和内核头文件(适合开发调试,不适合生产部署)。
Python — BCC (最简版)#!/usr/bin/env python3 # trace_execve.py — 追踪所有 execve 系统调用 from bcc import BPF # 内嵌的 eBPF C 代码 prog = """ int hello(void *ctx) { bpf_trace_printk("exec called\\n"); return 0; } """ # 三行核心代码:编译 → 挂载 → 读取 b = BPF(text=prog) # ① 编译 C → BPF 字节码 b.attach_kprobe(event="__x64_sys_execve", fn_name="hello") # ② 挂载到 execve b.trace_print() # ③ 读取 trace 管道输出
BCC 简单但不适合生产环境:每次运行都在目标机器上编译(需要 LLVM + 头文件), 启动慢、资源消耗大、没有 CO-RE(跨版本不兼容)。 生产环境请使用下面的 libbpf + CO-RE 范式。
这是现代 eBPF 开发的标准范式——分为内核态 C 程序和用户态 C 程序,
通过 libbpf skeleton 自动关联。这个示例追踪 execve() 调用,
通过 Ring Buffer 将进程信息推送到用户态。
C — 内核态 eBPF 程序 (execmon.bpf.c)// execmon.bpf.c — 监控进程执行(内核态) #include "vmlinux.h" // BTF 生成的完整内核类型定义 #include <bpf/bpf_helpers.h> #include <bpf/bpf_core_read.h> // CO-RE 读取宏 // 定义用户态/内核态共享的事件结构体 struct event { u32 pid; u32 uid; char comm[16]; // 进程名 char filename[128]; // 执行的文件路径 }; // 定义 Ring Buffer Map(用于内核→用户态的事件推送) struct { __uint(type, BPF_MAP_TYPE_RINGBUF); __uint(max_entries, 256 * 1024); // 256KB 缓冲区 } events SEC(".maps"); // 挂载到 sys_enter_execve tracepoint SEC("tp/syscalls/sys_enter_execve") int trace_execve(struct trace_event_raw_sys_enter *ctx) { // 在 Ring Buffer 中预留空间 struct event *e = bpf_ringbuf_reserve(&events, sizeof(*e), 0); if (!e) return 0; // 缓冲区满,放弃本次事件 // 填充事件数据 e->pid = bpf_get_current_pid_tgid() >> 32; e->uid = bpf_get_current_uid_gid() & 0xFFFFFFFF; bpf_get_current_comm(&e->comm, sizeof(e->comm)); // CO-RE 安全读取:从 ctx 中获取 execve 的文件名参数 const char *filename_ptr = (const char *)ctx->args[0]; bpf_probe_read_user_str(e->filename, sizeof(e->filename), filename_ptr); // 提交事件到 Ring Buffer(用户态可立即读取) bpf_ringbuf_submit(e, 0); return 0; } char LICENSE[] SEC("license") = "GPL";
C — 用户态加载器 (execmon.c)// execmon.c — 用户态:加载、挂载、读取事件 #include <stdio.h> #include <signal.h> #include <bpf/libbpf.h> #include "execmon.skel.h" // bpftool gen skeleton 自动生成 static volatile bool running = true; static void sig_handler(int sig) { running = false; } // Ring Buffer 回调:每当内核提交一个事件时调用 static int handle_event(void *ctx, void *data, size_t len) { struct event *e = data; printf("%-8d %-8d %-16s %s\n", e->pid, e->uid, e->comm, e->filename); return 0; } int main(void) { // ① 打开 & 加载(CO-RE 重定位 + Verifier + JIT 全自动完成) struct execmon_bpf *skel = execmon_bpf__open_and_load(); if (!skel) { fprintf(stderr, "Failed to load BPF\n"); return 1; } // ② 挂载到 Hook 点 if (execmon_bpf__attach(skel)) { fprintf(stderr, "Failed to attach\n"); return 1; } // ③ 创建 Ring Buffer 消费者 struct ring_buffer *rb = ring_buffer__new( bpf_map__fd(skel->maps.events), handle_event, NULL, NULL); printf("%-8s %-8s %-16s %s\n", "PID", "UID", "COMM", "FILENAME"); signal(SIGINT, sig_handler); while (running) ring_buffer__poll(rb, 100); // 每 100ms 轮询一次 // ④ 清理(卸载程序、释放 Maps、关闭 fd) ring_buffer__free(rb); execmon_bpf__destroy(skel); return 0; }
Shell — 编译 & 运行# 1. 编译 eBPF 程序 $ clang -O2 -target bpf -g -c execmon.bpf.c -o execmon.bpf.o # 2. 生成 skeleton 头文件 $ bpftool gen skeleton execmon.bpf.o > execmon.skel.h # 3. 编译用户态程序 $ gcc -o execmon execmon.c -lbpf -lelf -lz # 4. 运行(需要 root 或 CAP_BPF) $ sudo ./execmon PID UID COMM FILENAME 18542 1000 bash /usr/bin/ls 18543 1000 bash /usr/bin/grep 18544 0 cron /usr/sbin/logrotate 18545 33 www-data /usr/bin/php ^C
回顾一下我们学过的七个阶段,这个例子精确对应:
clang -target bpf 编译 → ELF + BTFopen_and_load() 内部调用 bpf(BPF_PROG_LOAD) → Verifier 验证attach() 将程序挂载到 tracepointexecve() 触发 → eBPF 程序执行bpf_ringbuf_submit() 推送 → ring_buffer__poll() 读取destroy() 释放所有资源万物皆有终。当 eBPF 程序完成使命(或不再被需要),它需要被安全地卸载。
Linux 内核通过引用计数(Reference Counting)管理 eBPF 程序的生命周期。 每一个引用——用户态的文件描述符、bpf_link、被其他程序 Tail Call 引用—— 都会将计数加一。当所有引用被关闭,计数归零时, 内核会自动执行清理:
整个过程是优雅且原子的——不会出现"程序卸了一半,Hook 还在"的不一致状态。
这也是为什么 eBPF 适合生产环境的重要原因之一:你可以安全地热更新——
先加载新版本程序,通过 bpf_link 原子替换旧版本,旧版本引用归零后自动清理。
全程零中断、零数据包丢失。
Shell — 查看引用计数与清理# 查看程序当前引用 $ bpftool prog show id 42 42: tracepoint name trace_execve ref_cnt 2 // 两个引用:一个 fd + 一个 bpf_link loaded_at 2026-04-07T14:30+0900 # 关闭 loader 进程后(fd 释放),如果 bpf_link 未 pin: $ bpftool prog show id 42 Error: get by id (42): No such file or directory // 已自动清理 ✓ # 如果 bpf_link 已 pin 到 /sys/fs/bpf/,需要手动 unpin: $ rm /sys/fs/bpf/my_xdp_link # 引用归零 → 内核自动清理程序和 Maps
到这里,你已经跟随一个 eBPF 程序走完了它从诞生到终结的完整生命周期。 恭喜——你现在对 eBPF 的理解,已经超过了 95% 的技术从业者。
但我必须诚实地说:这条路极其艰难。
让我们回顾一下,要手动开发和部署一个生产级 eBPF 解决方案,你需要:
图 3-7:"The Hard Way" —— 手写 eBPF 的七大痛点,以及为什么 Cilium 和 Tetragon 是必然的进化方向
想象一下:你不只是要写一个简单的 execve 追踪器——
你要在一个拥有 500 个微服务、10,000 个 Pod 的 Kubernetes 集群中实现
L3/L4/L7 网络策略、透明加密、负载均衡、
DNS 感知策略、运行时安全监控和全栈可观测性。
你需要编写和维护数十个相互协作的 eBPF 程序、
管理数百个 BPF Maps、处理版本兼容性、
构建遥测管道、实现策略编排引擎……
这不是一个人或一个小团队能承受的工程量。这需要的是: 一个经过数百万节点生产验证的平台。
Isovalent(现为 Cisco 的一部分)正是为了解决这些痛点而生的。 他们创建了 Cilium(云原生网络与安全)和 Tetragon(运行时安全), 将底层 eBPF 的复杂性封装为声明式 API—— 你不再需要写 C 代码与 Verifier 搏斗, 只需要写一段 YAML 或 Kubernetes NetworkPolicy, Cilium 就会在幕后自动编排所需的 eBPF 程序。
这正是三部曲中 Part 2(Cilium)和 Part 3(Tetragon) 将要深入探讨的主题。
让我们用一条线串起整个旅程:
C 源码 →(Clang/LLVM + BTF + CO-RE)→ BPF 字节码(ELF) →(bpf() syscall → Verifier 数学证明)→ 已验证程序 →(JIT → 原生机器指令)→ 挂载到 Hook 点 →(事件触发 → 零 Context Switch 执行)→ BPF Maps 通信 →(Ring Buffer → 用户态消费)→ 引用归零 → 自动清理
每一步都有严格的安全把关。每一步都经过精心设计。 这就是 eBPF 能够让代码安全地、高性能地、动态地运行在 Linux 内核中的完整秘密。
An eBPF program collecting data inside the kernel is useless if it can't report back to user space. BPF Maps are the high-speed data channel that bridges this gap — kernel-resident key-value stores accessible from both eBPF programs and user-space applications.
For streaming events to user space, Ring Buffer (Linux 5.8+) is the clear winner over the legacy Perf Buffer (Linux 4.4+):
| Dimension | Perf Buffer | Ring Buffer ⭐ |
|---|---|---|
| Architecture | Per-CPU separate buffers | Single shared buffer |
| Memory | Size × CPU count (wasteful) | Single allocation (efficient) |
| Ordering | Requires user-space sorting | Natural FIFO order |
| Overflow | Busy CPU can overflow while others idle | Shared space — naturally balanced |
| Read Path | Data copy to user space | mmap zero-copy read |
| Records | Fixed-size only | Variable-length supported |
The complete production-grade example (shown in the Chinese section above) demonstrates the full lifecycle:
execmon.bpf.c): SEC macro, Ring Buffer Map, bpf_ringbuf_reserve/submitexecmon.c): open_and_load → attach → ring_buffer__poll → destroyThe kernel manages eBPF program lifecycle through reference counting. When all references (user-space fds, bpf_links, tail call references) are released and the count reaches zero, the kernel automatically cleans up: unhooks the program, frees JIT code, destroys Maps, and reclaims BTF metadata. The process is graceful and atomic.
You've now followed an eBPF program through its complete lifecycle. But I must be honest: this path is extraordinarily difficult.
To build a production-grade eBPF solution manually, you must contend with:
bpf_printk is your only friendThis is precisely why Isovalent (now part of Cisco) created Cilium (cloud-native networking & security) and Tetragon (runtime security) — platforms that encapsulate eBPF's complexity behind declarative APIs. Instead of writing C and fighting the Verifier, you write YAML policies and Kubernetes NetworkPolicies.
C source → (Clang/LLVM + BTF + CO-RE) → BPF bytecode (ELF) → (bpf() syscall → Verifier proof) → Verified program → (JIT → native code) → Hooked to event → (event fires → zero-switch execution) → BPF Maps communication → (Ring Buffer → user-space consumer) → Refcount zero → auto cleanup
在第三章中,我们深入理解了 eBPF 程序"如何"在内核中运行。 现在让我们转向更实际的问题:eBPF 能为我们做什么? 它正在三个核心领域引发深刻的变革——安全、可观测性和网络。 每个领域都不是简单的"改进",而是范式级的颠覆。
传统的安全工具(EDR、HIDS、WAF)大多运行在用户空间,
通过系统调用钩子(如 ptrace、audit 框架)来监控内核行为。
这种架构有三个致命弱点:
攻击者可以通过内核漏洞(如 dirty pipe、dirty cow)
直接在内核态执行恶意代码,完全绕过用户态的安全监控。
如果安全工具不在内核中,它就看不到内核中发生的事情。
用户态安全工具检测到威胁后,需要通过系统调用请求内核采取行动(如终止进程)。 从检测到响应,可能有数毫秒到数秒的延迟—— 足够攻击者完成数据外泄或横向移动。
传统工具通过 audit 子系统收集日志,产生海量数据,
需要发送到用户态解析。在高频系统调用场景下,
仅日志收集就可能消耗 5-15% 的 CPU。
eBPF 彻底改变了这个格局。因为 eBPF 程序运行在内核中, 它获得了传统工具不可能拥有的三个关键能力:
① 不可绕过性(Unbypassable)。 eBPF 程序挂载在内核的核心代码路径上——例如 LSM Hook、syscall tracepoint、网络协议栈。 所有在内核中执行的操作都必须经过这些路径,无论攻击者多么高超。 这就像在城堡的每一扇门上都安装了监控——不是在城墙外面竖一面镜子。
② 同步内核级执行(In-Kernel Enforcement)。
以 Tetragon 为例,当它检测到恶意行为时,不需要"向用户态报告,然后等用户态决策"——
它在内核中同步执行拦截动作。通过 bpf_send_signal(SIGKILL),
恶意进程在纳秒级被终止——这发生在系统调用返回之前,
攻击者甚至没有机会执行下一条指令。
③ CISSP 纵深防御(Defense in Depth)。 eBPF 不是要取代现有的安全层,而是在每一层都增加一道防线:
| 防御层 | eBPF Hook 点 | 能力 |
|---|---|---|
| 网络边界 | XDP / TC | DDoS 防护、恶意 IP 封禁(Mpps 级) |
| 系统调用层 | Tracepoint / Kprobe | 异常 syscall 模式检测、参数审计 |
| 安全策略层 | LSM-BPF | 文件访问控制、进程执行策略、能力限制 |
| 进程行为层 | Kprobe / Uprobe | 进程族谱追踪、容器逃逸检测、文件完整性 |
| 数据层 | Uprobe on SSL libs | TLS 明文捕获(加密前/解密后) |
容器逃逸是云原生安全的头号威胁。攻击者通常通过内核漏洞(如 CVE-2022-0185)
从容器内部突破 namespace 隔离,获得宿主机的 root 权限。
传统安全工具发现逃逸时,攻击者通常已经在宿主机上建立了持久化后门。
而 eBPF + Tetragon 的方案是:在 LSM Hook 和 syscall tracepoint 上实时监控
容器内进程的行为,当检测到特征模式(如尝试挂载宿主机文件系统、访问 /proc/1/root、
调用 unshare(CLONE_NEWNS))时,在系统调用返回之前直接 SIGKILL——
攻击者的代码从未有机会在宿主机上执行一条指令。
可观测性(Observability)是理解系统行为的能力——不是"它告诉了你什么", 而是"你能问它什么"。eBPF 将这种能力提升到了前所未有的水平。
传统方案需要修改应用代码——加入 SDK、埋入追踪点(tracing instrumentation)、 引入 Sidecar Proxy 来采集网络指标。每一步都意味着代码侵入、性能开销 和维护负担。更关键的是,这些方案只能看到应用层的行为—— 内核中发生了什么(调度延迟、内存压力、网络协议栈拥塞),它们完全看不到。
eBPF 可以在不修改应用代码、不重启服务的前提下, 从网卡驱动层到用户态应用,采集每一层的详细指标。 无需 SDK 集成、无需重新编译、无需维护复杂的 Agent 配置。
场景:你新接手一个遗留系统,没有文档、没有追踪代码—— 通过 eBPF,你可以在几分钟内获得完整的系统调用画像、网络连接图谱和函数级别的延迟剖析。
传统 profiling 是"采样式"的——你在问题发生时手动运行 perf record,
希望能捕获到问题瞬间。eBPF 实现了7×24 小时持续剖析,
以 <2% CPU 开销持续采集 CPU 栈、内存分配、锁竞争等信息。
结果:生成实时火焰图(Flame Graph),精确到函数级定位性能瓶颈。 问题发生后回溯历史数据,而不是"等问题再现时手动抓取"。
在 TLS/mTLS 无处不在的云原生环境中,传统网络抓包只能看到加密后的乱码。
eBPF 通过 Uprobe 挂钩 SSL 库(如 OpenSSL 的 SSL_write
和 SSL_read),在加密之前/解密之后捕获原始明文数据。
意义:无需部署 mTLS 解密代理、无需分发私钥—— 在安全合规的前提下实现对加密流量的完整可见性。
通过观测内核网络栈中的实际连接行为(TCP connect / accept / close), eBPF 可以自动绘制服务之间的依赖关系图—— 无需人工配置、无需服务注册中心、无需修改服务代码。
代表工具:Hubble(Cilium 的可观测层)、Pixie、Parca、SkyWalking Rover。
Kubernetes 网络的核心问题是:如何在动态变化的容器拓扑中高效地实现
服务发现、负载均衡、网络策略和流量加密。
传统方案依赖 iptables(或其后继 nftables)和 Sidecar Proxy——
但它们在大规模集群中正在达到性能和可维护性的极限。
iptables 使用线性链式规则匹配——每个数据包到达时,
从第一条规则开始逐条匹配,直到找到匹配项。在一个拥有 10,000 个 Service 的集群中,
kube-proxy 会生成数万条 iptables 规则。
规则更新时需要全量重写(O(n) 复杂度),
每次 Pod 变更都会导致数秒的规则刷新延迟。
eBPF 用哈希表查找(O(1))替代 iptables 的线性匹配(O(n))。 Cilium 将 Service → Endpoint 映射存储在 BPF Map 中, 数据包到达时直接查表转发——无论有多少条规则,查找时间恒定。 规则更新是增量式的:只修改变更的条目,而非全量重写。
传统服务网格(如 Istio)为每个 Pod 注入一个 Sidecar Proxy(Envoy), 增加了内存开销(每 Pod 100-200MB)和延迟(额外 2-5ms)。 Cilium 的 eBPF 方案将 L3/L4 处理下沉到内核, 资源消耗降低 70%+,延迟降低到 <1ms。
Cilium 利用 eBPF + IPsec/WireGuard 实现节点间流量透明加密—— 应用层完全无感知、无需修改代码、无需管理证书。 对于合规要求严格的行业(金融、医疗), 这是一项杀手级能力。
以下是一个模拟的 eBPF 操作终端。点击预设按钮或输入命令,体验 eBPF 的核心能力:
Now we turn to the practical question: What can eBPF actually do? It is driving paradigm-level disruption across three domains: Security, Observability, and Networking.
Traditional security tools (EDR, HIDS) run in user space — making them bypassable (kernel exploits evade user-space monitoring), slow (milliseconds to seconds from detection to response), and expensive (5-15% CPU for audit log collection).
eBPF changes the paradigm fundamentally:
eBPF hooks sit on kernel code paths that all operations must traverse. Attackers cannot bypass monitoring that lives inside the kernel itself.
Tetragon uses bpf_send_signal(SIGKILL) to terminate malicious processes
before the syscall returns — nanosecond-level response,
no user-space round-trip.
eBPF adds enforcement at every layer: network (XDP/TC), syscall (Tracepoint), security policy (LSM), process behavior (Kprobe), and data (Uprobe on SSL).
Collect metrics from NIC driver to application layer without modifying code, restarting services, or deploying SDKs. Ideal for legacy systems with no documentation.
24/7 profiling at <2% CPU overhead: CPU flame graphs, memory allocation tracing, lock contention analysis. Retroactively investigate issues instead of waiting for reproduction.
Hook SSL_write/SSL_read via Uprobes to capture
plaintext data before encryption — no decryption proxies,
no private key distribution.
Observe actual kernel-level connections (TCP connect/accept/close) to automatically map service dependencies — no manual config needed.
eBPF replaces iptables' linear chain matching (O(n)) with hash table lookups (O(1)). Incremental updates vs. full-table rewrites.
Cilium pushes L3/L4 processing to kernel, reducing resource consumption by 70%+ and latency to <1ms vs. Sidecar Proxy models.
eBPF + IPsec/WireGuard for node-to-node traffic encryption — fully transparent to applications, no certificate management required.
Click the buttons or type commands in the terminal above to explore eBPF capabilities interactively.
任何技术的真正力量不仅在于其原理的精妙,更在于生态系统的繁荣。 eBPF 之所以能从学术研究走向全球基础设施的核心, 是因为一个由开源项目、商业公司和全球巨头共同构建的正向飞轮正在加速旋转。
图 5-1:eBPF 开源生态全景 —— 以 Cilium、Tetragon、bpftrace 和可观测性平台为四大核心
| 项目 | 定位 | 核心能力 | 成熟度 |
|---|---|---|---|
| Cilium | 云原生 CNI + 服务网格 | 身份感知网络策略、替代 kube-proxy、ClusterMesh 多集群、Gateway API | CNCF Graduated |
| Tetragon | 运行时安全 | TracingPolicy、内核级 SIGKILL、进程族谱、文件完整性监控 | CNCF Sandbox |
| bpftrace | 动态追踪工具 | 类 AWK 语法的一行式内核追踪脚本,性能分析首选 | Production |
| Hubble | 网络可观测性 | L3-L7 网络流可视化、服务依赖图、DNS 监控 | Cilium 内置 |
| Falco | 运行时威胁检测 | 基于规则的异常行为检测(syscall 驱动) | CNCF Graduated |
| Katran | L4 负载均衡器 | XDP 驱动的 10M+ pps 高性能负载均衡(Meta 开源) | Production @ Meta |
| Pixie (px.dev) | K8s 原生可观测性 | 零侵入 auto-instrumentation、eBPF 驱动的 APM | CNCF Sandbox |
| Parca | 持续性能剖析 | eBPF 驱动的 7×24 火焰图、极低开销 | CNCF Sandbox |
在 eBPF 生态中,Isovalent 扮演着独特且关键的角色。 它不仅是 Cilium 和 Tetragon 的主要创建者和维护者, 更是推动 eBPF 从"内核开发者的工具"走向"企业级基础设施"的核心力量。
图 5-2:Isovalent 飞轮模型 —— 开源引领 → 标准制定 → 降低门槛 → 企业级平台,形成正向循环
2024 年初,Cisco 完成了对 Isovalent 的收购—— 这是网络与安全行业过去十年中最具前瞻性的技术并购之一。 其战略逻辑清晰可见:
Cisco 传统的网络方案以硬件为核心(交换机、路由器、防火墙)。 但在云原生时代,流量的关键决策点从物理设备转移到了 Linux 内核。 eBPF 让 Cisco 能够在每个 Linux 节点上部署智能的网络策略引擎, 将控制平面延伸到以前无法触及的地方。
Tetragon 为 Cisco 的安全产品组合增加了内核级运行时安全能力—— 这是传统 NDR(Network Detection & Response)无法触及的领域。 结合 Cisco 的 XDR(Extended Detection & Response)平台, 形成了从网络到主机到内核的全栈安全闭环。
根据 2025 年 Kubernetes 网络报告,60.8% 的受访者正在使用 Cilium/Isovalent 作为其 CNI——远超第二名。这个数字背后是 eBPF 技术选择的胜利, 也是 Isovalent 长期开源投入的回报。
eBPF 已经不是实验室技术——它正在全球最大的基础设施中运行, 每天处理着数万亿个事件。以下是最具代表性的部署案例:
Google 在其生产基础设施中大规模使用 eBPF。GKE(Google Kubernetes Engine) 将 Cilium 作为默认 CNI 可选项。Google 的安全团队使用 eBPF 进行 内核级安全监控和异常检测。Prodkernel 团队持续贡献 eBPF 上游代码。
规模:数百万节点 · 场景:CNI / 安全 / 性能分析
Meta 是 eBPF 最早和最大规模的采用者之一。Katran(XDP L4 负载均衡器) 每秒处理超过 1000 万个连接,替代了传统的 IPVS 方案。 Meta 还深度参与了 BTF、CO-RE 等核心基础设施的开发。
规模:数十万服务器 · 场景:L4 LB / DDoS / 性能追踪
Cloudflare 全球边缘网络(300+ 城市)使用 XDP 进行DDoS 防护,
在 Tbps 级攻击下保持服务可用。其 bpf_xdp 程序
在网卡驱动层直接丢弃恶意流量,保护上游服务完全不受影响。
规模:300+ 全球 PoP · 场景:DDoS / 边缘安全 / Magic Firewall
Netflix 使用 eBPF 进行深度性能分析(Brendan Gregg 是其首席性能工程师, 也是 bpftrace/BCC 的核心贡献者)。他们通过 eBPF 实时追踪 内存分配模式、CPU 调度延迟和网络栈瓶颈,支撑每秒数百万流的视频分发。
规模:万级实例 · 场景:性能剖析 / FlameScope / 延迟分析
Azure 在 AKS(Azure Kubernetes Service)中提供 Cilium 作为高级 CNI 选项。 Microsoft 还贡献了 eBPF for Windows 项目, 将 eBPF 的可编程性带入 Windows 操作系统——这标志着 eBPF 正在超越 Linux 的边界。
规模:全球 Azure 集群 · 场景:AKS CNI / eBPF for Windows
Alibaba:在大规模容器集群中使用 eBPF 优化网络性能。 ByteDance:自研 eBPF 安全与可观测性工具。 Datadog:eBPF 驱动的无侵入 APM。 OpenAI:使用 eBPF 监控 GPU 训练集群的网络和安全。
趋势:从互联网巨头扩展到金融、电信、政府等传统行业
eBPF 的生态系统已经形成了一个自我强化的飞轮: 内核社区提供稳定的底层能力 → 开源项目(Cilium/Tetragon/bpftrace)将能力封装为易用的工具 → 全球巨头在生产中大规模验证 → 验证反哺社区推动更多内核特性 → Isovalent/Cisco 将整条链路产品化。
这个飞轮正在加速旋转。接下来,让我们看看 2026 年的技术前沿—— eBPF 正在向哪些全新的领域扩张。
The true power of any technology lies not only in its principles but in the thriving ecosystem around it. eBPF's journey from academic research to the core of global infrastructure is driven by a powerful flywheel of open-source projects, commercial companies, and global hyperscalers.
| Project | Focus | Core Capabilities | Maturity |
|---|---|---|---|
| Cilium | Cloud-native CNI + Service Mesh | Identity-aware policies, kube-proxy replacement, ClusterMesh, Gateway API | CNCF Graduated |
| Tetragon | Runtime Security | TracingPolicy, in-kernel SIGKILL, process ancestry, file integrity | CNCF Sandbox |
| bpftrace | Dynamic Tracing | AWK-like one-liner scripts for kernel/user-space tracing | Production |
| Hubble | Network Observability | L3-L7 flow visualization, service dependency maps, DNS monitoring | Built into Cilium |
| Katran | L4 Load Balancer | XDP-driven 10M+ pps load balancing (Meta open source) | Production @ Meta |
Isovalent plays a uniquely critical role in the eBPF ecosystem — as the primary creator of Cilium and Tetragon, and the driving force behind eBPF's industrialization. Their flywheel model:
Cisco's acquisition of Isovalent in early 2024 was one of the most forward-looking technology acquisitions of the decade. It extends Cisco's control plane from physical devices to every Linux node via eBPF, and adds kernel-level runtime security to Cisco's XDR portfolio.
Per the 2025 Kubernetes Networking Report, 60.8% of respondents use Cilium/Isovalent as their CNI — far ahead of second place.
GKE offers Cilium as default CNI option. Millions of nodes. Deep eBPF upstream contributions from Prodkernel team.
Katran XDP load balancer handles 10M+ connections/sec. Key contributor to BTF and CO-RE infrastructure.
XDP-based DDoS protection across 300+ global PoPs. Withstands Tbps-scale attacks at the driver layer.
Deep performance analysis with bpftrace/BCC. Brendan Gregg pioneered eBPF-based flame graphs here.
Cilium as advanced CNI in AKS. Also leading eBPF for Windows, extending eBPF beyond Linux boundaries.
Alibaba · ByteDance · Datadog · OpenAI: From internet giants to finance, telecom, and government sectors.
The eBPF ecosystem has formed a self-reinforcing flywheel: Kernel community provides stable foundations → Open-source projects package capabilities into tools → Hyperscalers validate at scale → Validation feeds back to drive more kernel features → Isovalent/Cisco productizes the full chain.
eBPF 正在从其传统的"安全/网络/可观测性"三角地带, 向更底层、更广阔、更出人意料的方向扩张。 本章探讨三个正在重新定义 eBPF 边界的前沿领域—— 每一个都可能在未来两年内深刻改变我们构建和运维系统的方式。
在 Kubernetes 网络中,每个 Pod 都需要一个虚拟网络接口来与宿主机通信。 传统方案使用 veth pair(虚拟以太网对)—— 这是 Linux 内核中一种创建虚拟网络管道的经典机制。 但在高密度容器场景(单节点 100+ Pod)下,veth 的性能瓶颈日益凸显。
veth pair 的本质是一对互联的虚拟网卡——从一端写入的数据会从另一端出来。 问题在于,数据包在穿越 veth pair 时需要经历多次冗余处理:
图 6-1:传统 veth pair vs Netkit —— Netkit 通过 bpf_redirect_peer 跳过冗余的网络栈重入,实现零拷贝容器网络
Netkit(Linux 6.7+,由 Cilium 核心开发者 Daniel Borkmann 提出) 是专门为 BPF 设计的新型虚拟网络接口。它的核心创新是:
netdev_rx 完整路径:
传统 veth 数据包到达对端时,会触发完整的接收处理流程(就像数据从物理网卡到达一样)。
Netkit 通过 bpf_redirect_peer() 直接将数据包"跳跃"到对端的 TC 层——
跳过了整个 netdev 接收层的冗余处理。① 吞吐量提升约 40%(消除了 netdev_rx 重入开销)。 ② 延迟降低约 60%(从 ~15μs 降至 ~5μs per hop)。 ③ CPU 开销降低约 30%(减少了 skb 操作和锁竞争)。 ④ 专为 BPF 优化的接口,避免传统 TC qdisc 的通用性开销。 这些改进在高密度容器场景(单节点 100+ Pod)下效果尤为显著。
如果说 XDP 让 eBPF 接管了网络数据路径, 那么 sched_ext(Linux 6.12+)则让 eBPF 触及了操作系统最核心的领域—— CPU 调度。这是一个足以改写操作系统教科书的突破。
Linux 内核的默认调度器(CFS/EEVDF)是为通用负载设计的—— 它尝试在所有进程之间公平分配 CPU 时间。这对大多数场景足够好, 但在特定工作负载下会产生系统性的低效:
GPU 训练任务需要 CPU 在精确的时间窗口内准备好数据批次。 通用调度器不了解 GPU pipeline 的时序要求, 可能在关键时刻将 CPU 时间分配给无关进程—— 导致 GPU 空等(GPU starvation)。
实时音视频、在线游戏服务器需要微秒级的调度响应。 CFS 的公平性算法在负载突增时可能产生 数毫秒的调度延迟尖峰——对这些场景是不可接受的。
云服务商需要确保租户 A 的突发负载不影响租户 B 的性能。 CFS 的 cgroup 带宽控制是粗粒度的, 无法根据实时负载动态调整策略。
sched_ext(由 Meta 的 Tejun Heo 和 David Vernet 主导开发) 在调度器框架中引入了一组BPF 可编程的回调点:
图 6-2:sched_ext 架构 —— 在调度器核心框架中插入 BPF 可编程回调,实现自定义调度策略
sched_ext 的关键设计原则:
现代 LLM 训练任务(如 GPT 级别的模型)的 CPU 负载模式是高度周期性的: CPU 在准备数据批次时突发忙碌,数据送入 GPU 后 CPU 又进入等待。 CFS 的公平算法在 CPU "等待"期间会将时间片分配给其他进程, 但当 GPU 需要下一批数据时,CPU 可能需要数百微秒才能重新获得调度—— 这段时间 GPU 完全空闲(idle),等于浪费了数千美元/小时的 GPU 算力。
sched_ext 允许你编写一个"AI 感知"的调度策略:
在 ops.select_cpu() 中优先将数据准备线程调度到 GPU 近端的 NUMA 节点,
在 ops.dispatch() 中给这些线程更高的优先级。
Meta 的实验表明,这种方案可以将训练吞吐提升 5-15%——
在动辄数百万美元的训练任务中,这是巨大的成本节省。
2024-2026 年,大语言模型(LLM)的部署规模爆炸式增长。 但 AI 推理服务的安全性和可观测性却远远落后于其功能发展—— 这为 eBPF 开辟了一个全新的应用领域。
LLM 推理服务几乎都通过 HTTPS/gRPC(TLS 加密)提供 API。 传统的网络监控工具只能看到加密后的密文—— 无法得知请求中包含了什么 prompt、返回了什么内容、 是否存在 prompt injection 攻击。
eBPF 通过 Uprobe 挂钩用户态 SSL 库(如 OpenSSL 的 SSL_write
和 SSL_read 函数),在加密之前和解密之后捕获原始明文数据。
这意味着安全团队可以:
eBPF 能够追踪 LLM 推理服务的两个最关键性能指标:
从用户发送请求到第一个 Token 开始返回的时间。 这是衡量 LLM 响应速度的最核心指标——直接决定了用户感知到的"延迟"。
影响因素:模型大小、KV Cache 命中率、Prefill 阶段的计算量、GPU 队列深度。
典型值:优质服务 <500ms;用户可感知阈值 ~2s。
相邻两个 Token 之间的生成间隔时间。 它决定了用户在 Streaming 模式下感知到的"流畅度"—— ITL 过高意味着文字输出卡顿。
影响因素:Decode 阶段效率、Batch Size、内存带宽。
典型值:优质服务 <50ms/token;流畅阈值 ~100ms/token。
TTFT 的正确全称是 Time To First Token—— 这是 LLM 推理领域的标准术语,指从请求发出到首个 Token 返回的延迟。 请注意区分:它与网络领域的 TTFB(Time To First Byte)虽然概念类似, 但测量的粒度不同——TTFT 是应用语义级的(Token),TTFB 是传输级的(Byte)。
通过 Uprobe 挂钩推理框架(如 vLLM、TensorRT-LLM、Triton)的关键函数, eBPF 可以在不修改推理代码的前提下精确测量:
Pseudo — eBPF LLM Observability// 伪代码:eBPF 追踪 LLM 推理性能 // Uprobe: 挂钩推理请求入口 SEC("uprobe/vllm/engine/process_request") int trace_request_start(ctx) { u64 ts = bpf_ktime_get_ns(); bpf_map_update_elem(&req_start_map, &req_id, &ts, 0); } // Uprobe: 挂钩第一个 Token 输出 SEC("uprobe/vllm/engine/first_token_out") int trace_first_token(ctx) { u64 now = bpf_ktime_get_ns(); u64 *start = bpf_map_lookup_elem(&req_start_map, &req_id); if (start) { u64 ttft = now - *start; // TTFT = Time To First Token emit_metric("ttft_ns", ttft); } } // Uprobe: 挂钩每个后续 Token 输出 SEC("uprobe/vllm/engine/token_out") int trace_token(ctx) { u64 now = bpf_ktime_get_ns(); u64 *prev = bpf_map_lookup_elem(&last_token_map, &req_id); if (prev) { u64 itl = now - *prev; // ITL = Inter-Token Latency emit_metric("itl_ns", itl); } bpf_map_update_elem(&last_token_map, &req_id, &now, 0); }
应用层 SDK(如 OpenTelemetry)可以追踪 TTFT 和 ITL,但有三个局限:
例如,当 TTFT 突然飙升时,eBPF 可以同时告诉你: "Prefill 阶段的 CUDA kernel 等待了 800μs(Uprobe on CUDA runtime)+ 数据准备线程被调度延迟了 1.2ms(sched tracepoint)+ KV Cache 加载触发了 NUMA 远端内存访问(perf event)"—— 这种跨层关联分析是应用层 SDK 永远无法实现的。
eBPF 的边界正在被重新定义:
还有研究阶段的 gpu_ext(将 eBPF 扩展到 GPU 资源管理, 通过 SIMT 感知的验证器实现 GPU 上的安全可编程性)—— 这预示着 eBPF 的未来可能远超 Linux 内核本身的边界。
eBPF is expanding from its traditional security/networking/observability triangle into deeper, broader, and more unexpected directions.
Netkit (Linux 6.7+) is a new virtual network interface designed exclusively for BPF.
It replaces veth pairs by using bpf_redirect_peer() to skip the entire
netdev_rx re-entry path — eliminating redundant skb allocation,
duplicate TC processing, and CPU cache pollution.
Results: ~40% throughput improvement, ~60% latency reduction, ~30% CPU savings in high-density container environments (100+ Pods/node).
sched_ext (Linux 6.12+) introduces BPF-programmable callbacks into the CPU scheduler:
ops.select_cpu(), ops.enqueue(), ops.dispatch(), ops.running().
Key design principles:
AI training use case: An "AI-aware" scheduler prioritizes data-preparation threads on GPU-local NUMA nodes, reducing GPU starvation and improving training throughput by 5-15%.
eBPF hooks SSL_write/SSL_read via Uprobes to capture
plaintext data before encryption / after decryption — enabling
prompt injection detection, PII leak auditing, and compliance monitoring
without modifying inference code.
Time from request submission to first token returned. The primary metric for perceived LLM latency. Target: <500ms for quality service; user perception threshold ~2s.
Time between consecutive tokens in streaming mode. Determines perceived "fluency." Target: <50ms/token; fluency threshold ~100ms/token.
TTFT = Time To First Token (not "Time To First Byte"). While conceptually similar to TTFB, TTFT measures at application semantics level (tokens), not transport level (bytes).
eBPF's boundaries are being redefined: Netkit pushes into container data paths (zero-copy); sched_ext pushes into CPU scheduling (the OS's most sacred domain); LLM observability pushes into AI infrastructure (token-level metrics). Research projects like gpu_ext hint at a future where eBPF extends beyond the Linux kernel itself.
你已经走完了一段不平凡的旅程。
从第一性原理出发,你理解了为什么操作系统内核需要一种安全的可编程机制; 你掌握了 eBPF 是什么——内核中的 JavaScript; 你跟随一个 eBPF 程序走完了它从 C 源码到内核执行再到卸载的完整生命周期—— 编写、编译(BTF + CO-RE)、验证(Verifier 的有界 DFS + 状态剪枝)、 加速(JIT 零开销映射)、挂钩(好莱坞原则 → IoC → 事件驱动)、 运行(零 Context Switch)、通信(Ring Buffer)、终结(引用计数自动清理); 你看到了 eBPF 在安全、可观测性和网络三大领域引发的范式颠覆; 你了解了从 Google 到 Meta 到 Cloudflare 的全球规模验证; 你探索了 Netkit、sched_ext 和 LLM 可观测性等2026 前沿。
更重要的是,你理解了为什么"The Hard Way"如此艰难—— 以及为什么 Isovalent/Cisco 的 Cilium 和 Tetragon 是将 eBPF 超能力转化为可落地的企业级方案的必经之路。
Linux 内核的超能力——完整生命周期、核心原理、三大价值支柱、2026 前沿。
✅ 你在这里
云原生网络与安全——身份感知策略、替代 kube-proxy、ClusterMesh 多集群、Gateway API、Sidecar-less 服务网格。
🔜 Coming Soon
云原生安全的最后一道防线——TracingPolicy、内核级 SIGKILL、进程族谱追踪、运行时安全策略引擎。
📋 Planned
| 术语 | 全称 | 简要说明 |
|---|---|---|
| eBPF | extended Berkeley Packet Filter | Linux 内核中的通用事件驱动运行时执行引擎 |
| cBPF | classic Berkeley Packet Filter | 1992 年的原始 BPF,仅用于包过滤,2 个 32-bit 寄存器 |
| BTF | BPF Type Format | 极精简的内核类型信息格式(~DWARF 的 1/100 体积) |
| CO-RE | Compile Once – Run Everywhere | eBPF 跨内核版本可移植性框架 |
| JIT | Just-In-Time Compilation | 将 BPF 字节码编译为原生机器指令 |
| XDP | eXpress Data Path | 网卡驱动层 eBPF Hook,最早的数据包拦截点 |
| TC | Traffic Control | 流量控制层 eBPF Hook,支持入/出站处理 |
| LSM | Linux Security Module | 安全模块 Hook 点,用于策略执行(Kernel 5.7+) |
| IoC | Inversion of Control | 控制反转——框架调用用户代码,而非反过来 |
| TTFT | Time To First Token | LLM 推理中从请求到第一个 Token 返回的延迟 |
| ITL | Inter-Token Latency | LLM 推理中相邻 Token 之间的生成间隔 |
| CNI | Container Network Interface | Kubernetes 容器网络接口标准 |
| MTTR | Mean Time To Recovery | 平均修复时间 |
| Kfunc | Kernel Function (for BPF) | Linux 6.x+ 的新一代内核 API 暴露机制 |
| sched_ext | Extensible Scheduler Class | BPF 可编程的 CPU 调度框架(Kernel 6.12+) |
| Netkit | — | 专为 BPF 设计的容器虚拟网络接口(Kernel 6.7+) |
本文由 Rao Weibo 借助 Claude Opus 完成
发布日期:2025-12-20 | 最后更新:2026-04-08
You've completed a remarkable journey. From first principles, you understood why the kernel needs safe programmability; you grasped what eBPF is — JavaScript for the kernel; you followed an eBPF program through its complete lifecycle; you saw the paradigm shifts in security, observability, and networking; you explored global-scale production deployments; and you peeked into the 2026 frontier.
Most importantly, you understand why "The Hard Way" is so difficult — and why Cilium and Tetragon by Isovalent/Cisco are the path to turning eBPF superpowers into production-ready enterprise solutions.
The superpower of Linux Kernel — complete lifecycle, core principles, three pillars, 2026 frontier.
✅ You are here
Cloud-native networking & security — identity-aware policies, kube-proxy replacement, ClusterMesh. 🔜
The last line of cloud-native defense — TracingPolicy, in-kernel SIGKILL, process ancestry. 📋
| Term | Full Name | Description |
|---|---|---|
| eBPF | extended Berkeley Packet Filter | General-purpose event-driven execution engine in the Linux kernel |
| BTF | BPF Type Format | Compact kernel type info (~1/100th of DWARF) |
| CO-RE | Compile Once – Run Everywhere | Cross-kernel portability framework |
| XDP | eXpress Data Path | NIC driver-layer hook, earliest packet interception point |
| TTFT | Time To First Token | LLM latency from request to first token returned |
| ITL | Inter-Token Latency | Time between consecutive tokens in LLM streaming |
Written by Rao Weibo with Claude Opus
Published: 2025-12-20 | Last Updated: 2026-04-08