eBPF 三部曲 · 第一篇

eBPF:Linux 内核的超能力

无需重启,无需修改源码,让操作系统即刻拥有全新的安全、网络与可观测能力—— 这不是科幻,这是 eBPF 正在做的事。

eBPF Trilogy · Part 1

eBPF: The Superpower of Linux Kernel

Instantly grant the OS new security, networking, and observability capabilities without restarting or modifying source code — this isn't science fiction, this is eBPF.

4X
网络吞吐提升
Network Throughput
<2%
CPU 开销
CPU Overhead
Zero
内核重启
Kernel Restarts
🐝 Part 1 · eBPF 🔗 Part 2 · Cilium 🛡️ Part 3 · Tetragon
🚨 凌晨三点的生死时速 03:07 AM · Production Cluster

手机在震动。你从沉睡中被拉回现实,屏幕上是刺目的红色:P0 — 生产环境 Kubernetes 集群核心服务雪崩

你冲到电脑前,眼前的场景让你心跳加速——Grafana 仪表盘上,QPS 从 15,000 骤降到 300, 延迟从 12ms 飙升到 28 秒。 客户投诉如洪水涌入 Slack 频道,VP 在群里连发三个问号。 你的直觉告诉你,这不是普通的流量高峰——某些 Pod 正在做不该做的事。

传统路径:你需要 SSH 到数十个节点,手动安装 tcpdumpstraceperf……挨个排查。每一步都需要进程重启才能生效。 而此刻每一秒都在烧钱——按照 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 等上层平台存在的必然性。

🚨 The 3 AM Incident 03:07 AM · Production Cluster

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.

💡

The Core Promise 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.

1
Chapter 1 · First Principles

WHY — 为什么需要 eBPF?

要真正理解 eBPF 的革命性,我们不能从结论出发,必须回到最基本的问题: 操作系统内核是如何工作的?为什么给内核添加新功能如此困难? 只有建立了这个认知根基,eBPF 的设计哲学才会显得不仅合理,而且必然

1.1 内核空间 vs 用户空间:城堡与护城河

想象一座中世纪城堡。城堡内部(Kernel Space,内核空间)是王国的心脏—— 这里掌管着一切核心资源:军队调度(CPU 调度)、粮仓管理(内存管理)、城门通信(网络协议栈)、 档案室(文件系统)。城堡的管理者拥有最高权限——Ring 0, 可以直接操作任何硬件设备,访问全部物理内存。

城堡外面是繁华的集市(User Space,用户空间)。商人(应用程序)们在这里做生意、 交换信息、服务客户。他们很重要,但绝不被允许直接走进城堡。 为什么?因为一个恶意的(或仅仅是粗心的)商人如果能直接接触到粮仓, 可能导致整个王国的崩溃——这在计算机术语中叫做 Kernel Panic, 即内核崩溃导致整个系统宕机。

USER SPACE(用户空间 · Ring 3) 应用程序运行于此,受限权限,无法直接访问硬件 🌐 Nginx PID 1042 📊 Prometheus PID 2187 🐍 Python App PID 3301 🗄️ PostgreSQL PID 4455 🐝 eBPF User Agent 🏰 System Call Interface — 唯一合法通道 bpf() KERNEL SPACE(内核空间 · Ring 0) 最高权限,直接访问 CPU / 内存 / 网络 / 存储等所有硬件资源 ⚙️ 进程调度 scheduler 🧠 内存管理 mm subsystem 🌐 网络协议栈 net subsystem 📁 文件系统 VFS 🐝 eBPF Runtime Verifier + JIT + Maps Hardware: CPU · RAM · NIC · SSD

图 1-1:Linux 内核架构的双层结构 —— 城堡(内核空间)与集市(用户空间)之间,System Call 是唯一的合法通道

这座城堡的安全设计源于 CPU 硬件本身。现代处理器(x86-64、ARM64)提供了保护环(Protection Rings)机制: 内核运行在 Ring 0(最高特权),用户应用运行在 Ring 3(最低特权)。这不是软件约定,而是硅片级的物理隔离。 当 Ring 3 的代码试图执行只有 Ring 0 才能执行的指令(例如直接读写 I/O 端口),CPU 会立刻触发一个硬件异常, 操作系统随即终止该进程——这就是"保护"的物理含义。

System Call:城堡的吊桥

既然用户态程序不能直接进入内核,它们如何请求内核服务?答案是 System Call(系统调用)—— 这是用户空间与内核空间之间唯一合法的通信通道

把它想象成城堡的吊桥:集市的商人(应用程序)想要运送货物到远方(发送网络数据包), 不能自己翻越城墙,必须走到吊桥前,按下门铃(触发 syscall 指令),报上身份和请求, 等待城堡卫兵(内核)验证后放行。卫兵代为执行操作,再把结果通过吊桥递回来。

每一次系统调用都意味着一次完整的"边境检查"——术语叫做 Context Switch(上下文切换)

Context Switch:赛车进站的代价

上下文切换就像 F1 赛车进站(Pit Stop):赛车必须减速、驶入维修区、换胎、加油、再加速驶出。 即使进站团队训练有素,这个过程也至少需要 2-4 秒——而赛道上的对手可不会停下来等你。

在计算机中,一次 Context Switch 涉及:

  • 保存当前所有 CPU 寄存器的值到内存
  • 切换页表(虚拟地址空间映射)
  • 刷新 TLB(Translation Lookaside Buffer)缓存
  • 可能导致 L1/L2 CPU 缓存失效
  • 恢复目标上下文的寄存器状态

单次上下文切换的直接成本约为 1-5 微秒——看似微不足道,但在高频场景下(如一台 Web 服务器每秒处理 10 万次请求), 这些微秒会累积成 显著的性能瓶颈。更关键的是间接成本:缓存被刷新后,CPU 需要重新"预热"数据, 这种缓存污染往往比直接开销还要昂贵数倍。

🔑

第一性原理 · 关键推论

安全性与性能之间存在根本矛盾:内核把用户态隔离在城墙之外是为了安全, 但每次跨越城墙(System Call + Context Switch)都有性能代价。 任何想要获得内核级速度和权限的方案,都必须想办法在城堡内部运行代码, 同时又不威胁城堡的安全。这就是 eBPF 要解决的核心矛盾。


1.2 传统方案的困境:走不通的三条路

在 eBPF 出现之前,开发者想要给内核添加新功能,基本上只有三条路可走。 不幸的是,每一条都布满荆棘。

🛤️ 路径一:修改内核源码

最"正统"的方案——直接修改 Linux 内核源代码。你可以 fork 内核仓库,在网络协议栈里加一个新 Hook, 在调度器里加一段优化逻辑,然后提交 patch 到上游社区。

问题是,Linux 内核是全世界最严格的开源项目之一。一个补丁从提交到合入主线(mainline)的典型周期是 3-5 年。 它需要经过社区评审、多轮修改、合入候选版本(RC)、在各大发行版中验证、最终才会出现在你的生产服务器上。 即便你的公司有能力维护一个自定义内核(如 Google 的 Prodkernel),这也意味着巨大的工程成本和 与上游永久分叉的风险。

对于"今晚就需要修复"的生产问题,这条路是完全走不通的

🛤️ 路径二:内核模块(LKM)

Linux 提供了一种折中方案:Loadable Kernel Module (LKM),即可加载内核模块。 你可以编写一个 .ko 文件,通过 insmod 命令动态加载到内核中—— 无需重新编译整个内核,也无需重启。

听起来很完美?问题在于:内核模块运行在 Ring 0, 拥有与内核本身完全相同的权限,但没有任何安全边界。 一个有 bug 的内核模块可以:

  • 写入任意内存地址 → 数据损坏
  • 进入无限循环 → 整个 CPU 被锁死
  • 解引用空指针 → Kernel Panic → 系统宕机
  • 引入安全漏洞 → 被攻击者利用进行提权

这就像允许一个未经安检的陌生人直接走进城堡的核心——他可能是友好的工匠, 也可能是伪装的间谍。内核模块没有验证机制、没有沙盒隔离、没有崩溃保护。 在生产环境中加载第三方内核模块,对许多企业的安全团队来说是不可接受的风险

🛤️ 路径三:用户态方案(Sidecar / Agent)

既然内核态太危险,那就全部在用户态做?这是许多传统安全和网络工具的选择—— 比如以 Sidecar 模式运行的服务网格(如 Istio/Envoy),或以 Agent 模式运行的安全工具。

用户态方案是安全的——它崩溃了最多影响自己的进程,不会拖垮内核。 但代价是性能:每一个网络包从内核到用户态再回到内核, 需要穿越两次 System Call 边界、两次 Context Switch、两次数据拷贝。 在高吞吐场景下(如 100Gbps 网络),这种开销可以吃掉 30-50% 的 CPU 资源

更致命的是可见性盲区:用户态工具只能看到内核"愿意告诉它"的信息—— 通过 /proc/sysnetlink 等接口。 那些发生在内核深处的事件(如内核函数调用、调度决策、内存分配路径), 用户态工具根本看不到。就像试图通过城堡的窗户猜测里面发生了什么—— 你看到的永远是冰山一角。

维度 修改内核源码 内核模块 (LKM) 用户态方案 eBPF ✨
交付速度 3-5 年 数周 数天 数秒 ⚡
安全风险 低(经社区审核) 🔴 极高(无沙盒) 低(进程级隔离) 极低(Verifier 证明)
需要重启? ✅ 是 ❌ 否 ❌ 否 ❌ 否
性能 原生(最优) 原生 🔴 较差(Context Switch) 接近原生(JIT)
内核可见性 完整 完整 🔴 受限 完整
崩溃影响 🔴 全系统 🔴 全系统 仅自身进程 不可能崩溃 🛡️
适合场景 核心子系统重构 驱动开发 应用层工具 安全/网络/可观测
⚠️

三难困境(Trilemma)

传统方案迫使你在三个属性中最多选两个:安全性性能灵活性。 修改源码保证了安全和性能,但失去了灵活性(要等 5 年)。 内核模块提供了性能和灵活性,但牺牲了安全。 用户态方案保障了安全和灵活性,但牺牲了性能和可见性。 在 eBPF 出现之前,没有方案能同时满足三者。


1.3 eBPF 的革命性方案:沙盒中的超能力

eBPF 打破了这个三难困境。它的核心思想可以用一句话概括:

"让用户的代码在内核中运行,但在一个数学证明安全的沙盒里。"

回到城堡类比:eBPF 的做法不是让商人翻墙进入城堡(内核模块的方式), 也不是让商人在城墙外面大喊(用户态的方式)。而是在城堡内部建立了一系列 "安全工作间"(沙盒)——商人可以进入工作间执行任务, 但工作间的每面墙壁都是由 Verifier(验证器) 用数学方法证明为坚固的。 商人无法凿穿墙壁,无法访问工作间之外的区域,更无法让整座城堡倒塌。

这个方案同时获得了三个此前不可兼得的属性:

🏎️

原生性能

代码直接在内核空间执行,经 JIT 编译为原生机器指令,零 Context Switch。 网络处理场景下可达到内核模块 95% 以上的性能。

🛡️

绝对安全

Verifier 在加载前通过穷举路径分析,数学证明程序不会崩溃、 不会死循环、不会越界访问。这是"证明安全"而非"希望安全"。

即时部署

无需重新编译内核、无需重启系统、无需中断服务。 新能力秒级上线,对正在运行的进程和连接透明。

从"黑盒猜测"到"白盒观测"的范式转移

eBPF 带来的不仅是技术层面的突破,更是认知范式的转移

在传统模型下,内核是一个巨大的黑盒。你只能通过有限的接口(/proc/net/tcpnetstatss)窥探它的状态——就像用听诊器试图理解一台发动机的内部运作。 出了问题,你只能猜测推理试错

eBPF 则把内核变成了一个白盒。你可以在内核的任意位置放置探针—— 网络协议栈的每一层、系统调用的每一个入口和出口、调度器的每一次决策、内存分配器的每一次调用—— 然后以微秒级精度实时获取数据,而对系统性能的影响 <2%

这不是"更好的监控工具",而是观测能力的质变:从"我们能看到什么取决于内核暴露了什么", 变成了"我们想看什么就能看什么"。

🏁

第一章结论:为什么 eBPF 是唯一选择

从第一性原理出发,我们推导出了一条清晰的逻辑链:

CPU 保护环要求内核和用户态严格隔离 → System Call是唯一通道但有性能代价 → 需要在内核中运行代码才能获得极致性能和可见性 → 传统方案(改源码/LKM/用户态)要么太慢、要么太危险、要么太弱 → eBPF 是唯一能让代码同时拥有内核级权限、原生性能和崩溃豁免权的方案。

理解了"为什么",接下来我们深入探索 eBPF 到底"是什么"——它的定义、历史和核心心智模型。

1
Chapter 1 · First Principles

WHY — Why Do We Need 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.

1.1 Kernel Space vs User Space: The Castle and the Moat

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.

System Calls: The Castle's Drawbridge

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.

Context Switch: The Cost of a Pit Stop

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.

🔑

First Principles · Key Insight

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.


1.2 The Dilemma: Three Dead-End Paths

Before eBPF, developers had three paths to add kernel capabilities. Unfortunately, each was fraught with peril.

🛤️ Path 1: Modify Kernel Source

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.

🛤️ Path 2: Kernel Modules (LKM)

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.

🛤️ Path 3: User-Space Solutions

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.

⚠️

The Trilemma

Traditional approaches force you to choose at most two of three attributes: Safety, Performance, Flexibility. Before eBPF, no solution could deliver all three.


1.3 eBPF's Revolutionary Solution: Superpowers in a Sandbox

"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.

🏎️

Native Performance

Code executes directly in kernel space, JIT-compiled to native instructions. Zero Context Switches. Achieves 95%+ of kernel module performance.

🛡️

Absolute Safety

The Verifier exhaustively analyzes all paths before loading, mathematically proving the program cannot crash, loop forever, or access out-of-bounds memory.

Instant Deployment

No kernel recompilation, no system restarts, no service interruption. New capabilities go live in seconds.

From Black-Box Guessing to White-Box Observation

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.

🏁

Chapter 1 Conclusion

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.

2
Chapter 2 · Definition & Mental Model

WHAT — eBPF 到底是什么?

在第一章中,我们通过第一性原理推导出了"为什么需要 eBPF"。现在让我们给出精确的定义, 追溯它的历史脉络,并建立一个强大的心智模型——帮助你在后续的技术深潜中始终保持方向感。

2.1 精准定义

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 的静态分析——它会遍历程序所有可能的执行路径, 数学证明程序不会崩溃、不会死循环、不会越界访问。 通不过验证的程序永远无法进入内核


2.2 极简进化史:从包过滤器到内核操作系统

eBPF 的故事横跨三十多年,从一个简单的网络过滤工具进化为 Linux 内核中最强大的可编程基础设施。 理解这段历史,有助于理解为什么 eBPF 今天的形态看似复杂却每一处设计都有其必然性。

1992 cBPF 诞生 Steven McCanne & Van Jacobson 经典 BPF(Berkeley Packet Filter) 仅用于 tcpdump 包过滤 2 个 32-bit 寄存器 2014 eBPF 革命 Alexei Starovoitov · Kernel 3.18 Daniel Borkmann 架构级重写 11 个 64-bit 寄存器 JIT 编译 · Maps · Helpers Verifier 静态验证 超越网络 → 通用可编程 2020 CO-RE + BTF Andrii Nakryiko · Kernel 5.x 可移植性革命 Compile Once – Run Everywhere BTF 类型信息 · libbpf 2025-2026 全面扩张 Netkit · sched_ext · GPU LLM Observability 新前沿 Netkit 零拷贝容器网络 sched_ext 自定义 CPU 调度 gpu_ext 扩展至 GPU LLM TTFT/ITL 可观测 🔬 包过滤工具 🚀 内核可编程平台 🌍 跨版本可移植 🔮 全域扩张 寄存器进化:2×32-bit (cBPF) → 11×64-bit R0-R10 (eBPF) → 映射至物理寄存器 (JIT) 指令限制:4,096 条 (cBPF/早期 eBPF) → 1,000,000 条 (Kernel 5.2+)

图 2-1:eBPF 进化时间线 —— 从 1992 年的包过滤工具到 2026 年的内核可编程操作系统

🔬 1992:cBPF — 一切的起点

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:eBPF — 架构级的飞跃

2014 年,Alexei Starovoitov 和 Daniel Borkmann 向 Linux 内核提交了一系列革命性的补丁, 将 BPF 从一个网络过滤器重新架构为一个通用的内核虚拟机。 这次重写的变化之大,堪称从"计算器"到"计算机"的跨越:

  • 寄存器:从 2 个 32-bit 扩展到 11 个 64-bit(R0-R10), 与现代 64 位 CPU 的寄存器宽度完全对齐
  • 指令集:全新设计,接近现代 RISC 处理器架构,支持函数调用语义
  • JIT 编译:字节码不再解释执行,而是被即时编译为原生机器指令
  • Maps:引入键值存储结构,实现内核态与用户态的高效数据交换
  • Helper 函数:内核向 eBPF 程序开放一组安全的 API
  • Verifier:静态验证器确保每个程序在加载前已被数学证明安全
  • 超越网络:不再局限于包过滤,可以挂载到 Kprobe、Tracepoint、Perf Events、LSM 等多种内核事件
📖

名字的故事

"eBPF"中的"e"代表"extended"。但到了今天,eBPF 的能力已经远远超出了 任何"扩展"所能描述的范围。社区已经倾向于将 eBPF 作为一个独立术语使用, 不再展开为缩写——正如"TCP"不再需要解释为"Transmission Control Protocol"一样。 eBPF 就是 eBPF。

🌍 2020:CO-RE — 可移植性革命

eBPF 的一个早期痛点是可移植性。不同内核版本中,数据结构的字段偏移量可能不同—— 比如 struct task_structcomm 字段在内核 5.4 和 5.10 中可能在不同位置。 这意味着一个为 5.4 编译的 eBPF 程序在 5.10 上可能会读取错误的数据。

2020 年,Andrii Nakryiko 等人推出了 CO-RE(Compile Once – Run Everywhere)框架, 配合 BTF(BPF Type Format)类型信息,彻底解决了这个问题。 我们将在第三章的"编写与编译"阶段详细剖析 CO-RE 的工作机制。

🔮 2025-2026:全域扩张

eBPF 正在从传统的安全/网络/可观测性领域向更底层、更广阔的方向扩张:

  • Netkit(Linux 6.7+):替代 veth pair,实现容器网络零拷贝
  • sched_ext(Linux 6.12+):用 eBPF 自定义 CPU 调度策略
  • gpu_ext(研究阶段):将 eBPF 扩展到 GPU 资源管理
  • LLM 可观测性:追踪 AI 推理的 TTFT(Time To First Token)和 ITL(Inter-Token Latency)

这些前沿进展将在第六章详细探讨。


2.3 心智模型:eBPF 之于内核 ≈ JavaScript 之于浏览器

理解 eBPF 最有效的方式是类比:eBPF 之于 Linux 内核,正如 JavaScript 之于浏览器。 这不是一个粗略的比喻,而是在架构层面惊人地精确。

心智模型:架构级类比 🌐 浏览器 + JavaScript 📱 用户事件 click / scroll / input 📝 JS 代码 event handler 函数 V8 引擎(沙盒执行环境) JIT 编译 · 内存隔离 · 安全边界 · 垃圾回收 🔌 DOM API 安全访问接口 💾 LocalStorage 持久化数据存储 浏览器内核(不可直接访问) 渲染引擎 · 网络栈 · GPU 合成 · 进程管理 JS 只能通过 API 间接操作 ✓ 事件驱动 ✓ 沙盒隔离 ✓ 动态扩展 ✓ 🐝 Linux 内核 + eBPF ⚡ 内核事件 packet / syscall / sched 📝 eBPF 程序 SEC("xdp") handler Verifier + JIT(沙盒执行环境) 静态验证 · JIT 编译 · 内存边界 · 指令限制 🔌 Helper / Kfunc 安全内核 API 💾 BPF Maps 内核↔用户态数据通道 Linux 内核核心(不可直接修改) 进程调度 · 内存管理 · 网络协议栈 · 文件系统 eBPF 只能通过 Helper/Kfunc 间接操作 ✓ 事件驱动 ✓ 沙盒隔离 ✓ 动态扩展 ✓ JavaScript 对应关系 eBPF V8 / SpiderMonkey 执行引擎 Verifier + JIT

图 2-2:eBPF ≈ 内核中的 JavaScript —— 两者在架构上惊人一致:事件驱动、沙盒执行、动态扩展

让我们逐层展开这个类比:

架构维度 JavaScript + 浏览器 eBPF + Linux 内核
🎯 触发机制 用户事件:clickscrollkeydown 内核事件:数据包到达、系统调用触发、函数进入、定时器到期
📝 代码形式 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)
🎁 核心价值 让静态网页变成动态交互应用 让静态内核变成可动态编程的平台
🧠

L2 深度 · 为什么这个类比如此精确?

这不是巧合。JavaScript 和 eBPF 面对的是同一类工程问题: 如何在一个不可信代码受信任的核心系统之间建立安全的可编程层。 浏览器不能让网页 JS 访问你的硬盘——否则你打开一个恶意网站就会丢失所有文件。 Linux 内核不能让 eBPF 程序调用任意内核函数——否则一个 bug 就会导致系统崩溃。

两者的解法惊人一致:沙盒 + 受控 API + JIT 编译 + 事件驱动模型。 不同的是,JS 的安全边界如果被突破,影响的是一个浏览器标签页; eBPF 的安全边界如果被突破,影响的是整个操作系统。 这就是为什么 eBPF 的 Verifier 比 V8 的安全沙盒严格了一个数量级—— 它必须在程序运行之前证明安全,而不是在运行时检测违规。

🖥️ 亲自感受:静态内核 vs 可编程内核 🖥️ See It Yourself: Static Kernel vs Programmable Kernel

左边是传统的"出厂固化"内核——你只能使用编译时决定的功能。点击右边的 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.

static-kernel.sys
⚙️ 内核版本 5.15.0-generic

🔒 网络栈:iptables — 固定规则,逐条匹配
🔒 可观测性:syslog + ftrace(手动插桩)
🔒 安全策略:AppArmor / SELinux(静态配置文件)

💡 如需新功能 → 编写内核模块 → 编译 → 重启 → 祈祷不 panic 🙏
⚙️ Kernel version 5.15.0-generic

🔒 Networking: iptables — fixed rules, linear match
🔒 Observability: syslog + ftrace (manual instrumentation)
🔒 Security: AppArmor / SELinux (static config files)

💡 Need a new feature? → Write kernel module → Compile → Reboot → Pray for no panic 🙏
ebpf-kernel.sys

🧬 eBPF 可编程内核 🧬 eBPF Programmable Kernel

类比的边界

任何类比都有其适用范围。eBPF 与 JavaScript 有几个重要差异值得注意:

  • 安全验证时机:JS 在运行时检测违规(崩溃了就崩溃了,最多关闭标签页)。 eBPF 在加载时就完成全部验证——不安全的程序永远无法进入内核。这是"预防"与"检测"的本质区别。
  • 性能要求:JS 有垃圾回收器(GC),可以容忍短暂的 Stop-the-World 暂停。 eBPF 运行在内核热路径上(如网络包处理),不允许任何形式的内存分配或 GC——所有资源必须预先声明。
  • 程序复杂度:JS 可以写出数百万行的 Web 应用。eBPF 程序受到指令数限制(100 万条) 和栈空间限制(512 字节),设计上就是小而精的事件处理器,不是通用编程环境。
🏁

第二章小结

我们现在已经知道了 eBPF 是什么(内核中的通用事件驱动执行引擎), 从哪里来(1992 年的包过滤器 → 2014 年的内核虚拟机 → 2020 年的 CO-RE 可移植框架 → 2025+ 的全域扩张), 以及如何理解它(内核中的 JavaScript)。

接下来进入本文最核心的章节——第三章 HOW: 我们将跟随一个 eBPF 程序走完它的完整生命周期, 从一行 C 代码开始,到它在内核中被卸载为止。 每一个关键组件——BTF、CO-RE、Verifier、JIT、Maps、Hooks—— 都将在它登场的那个精确阶段被深入剖析。

2
Chapter 2 · Definition & Mental Model

WHAT — What Exactly Is eBPF?

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.

2.1 Precise Definition

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:

🔧 General-Purpose

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.

⚡ Event-Driven

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.

🏰 Runtime Execution Engine

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.

✅ Safety-Verified

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.


2.2 A Brief Evolution: From Packet Filter to Kernel OS

🔬 1992: cBPF — The Genesis

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.

🚀 2014: eBPF — The Architectural Leap

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.

🌍 2020: CO-RE — The Portability Revolution

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.

🔮 2025-2026: Full-Spectrum Expansion

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.


2.3 Mental Model: eBPF Is to Kernel as JavaScript Is to Browser

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
🧠

Why Is This Analogy So Precise?

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.

Limits of the Analogy

  • Verification timing: JS detects violations at runtime. eBPF proves safety at load time — unsafe programs never enter the kernel.
  • Performance constraints: JS has a garbage collector and can tolerate GC pauses. eBPF runs on kernel hot paths — no memory allocation or GC allowed.
  • Program complexity: JS can power million-line web apps. eBPF programs are limited to 1M instructions and 512 bytes of stack — they are designed as small, precise event handlers.
🏁

Chapter 2 Summary

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.

3
Chapter 3 · The Lifecycle Deep Dive

HOW — eBPF 程序的完整生命周期

这是本文的核心章节。我们将跟随一个 eBPF 程序走完它从"出生"到"终结"的每一步。 所有关键技术组件——BTF、CO-RE、Verifier、JIT、Maps、Hooks—— 都将在它们登场的精确时刻被深入剖析,而非作为孤立的百科词条。

想象你手里有一段 C 代码,你的目标是让它在 Linux 内核中安全运行、响应事件、收集数据。 这段代码需要经历七个阶段的淬炼,每一步都有严格的把关者。

eBPF 程序完整生命周期全景图 1 诞生 编写 & 编译 C/Rust → ELF BTF · CO-RE 2 准入 验证 & 安全 bpf() syscall Verifier 3 加速 JIT 编译 字节码 → 机器码 零开销映射 4 就位 加载 & 挂钩 Hook Points bpf_link 5 运行 事件触发 Event → Handler 零 Context Switch 6 通信 数据交换 BPF Maps Ring Buffer 7 终结 卸载 & 清理 refcount → 0 自动释放 ⬆ 用户空间(User Space) ⬇ 内核空间(Kernel Space) ← bpf() syscall 边界 →

图 3-0:eBPF 程序完整生命周期全景 —— 七个阶段跨越用户空间与内核空间的边界

1

阶段一:诞生 — 编写与编译

C/Rust 源码 → Clang/LLVM → BPF 字节码 (ELF) + BTF + CO-RE

一个 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";
🔍

L2 机制层 · 为什么必须声明 GPL?

这不仅仅是法律合规要求。Linux 内核中许多 Helper 函数(如 bpf_probe_read_kernelbpf_get_current_task)被标记为 GPL-only。 如果你的 eBPF 程序不声明 GPL 兼容许可证,Verifier 会拒绝你调用这些函数—— 这是内核社区通过技术手段强制执行的开源契约

编译流程:C → LLVM IR → BPF 字节码

写好代码后,我们使用 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 文件中包含了几个关键部分:

  • BPF 字节码:程序本身的指令序列
  • BTF 段:类型信息(下文详述)
  • Maps 定义:程序需要使用的数据结构声明
  • 重定位信息:用于 CO-RE 的字段偏移记录
  • 许可证信息SEC("license") 中声明的内容

BTF:内核数据结构的"X 光翻译手册"

要理解 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)中。

🧠

L2 深度 · BTF 的工作原理

从 Linux 5.2 开始,内核编译时会自动生成一个 BTF 数据段,嵌入在 /sys/kernel/btf/vmlinux 中。这个文件完整描述了当前运行内核的所有数据结构布局。 开发者可以用 bpftool btf dump file /sys/kernel/btf/vmlinux 查看它, 或使用工具将其导出为 C 头文件(vmlinux.h), 在编译 eBPF 程序时直接引用——无需安装内核头文件包。

用类比来说:如果内核数据结构是一座复杂建筑的内部结构, DWARF 就像一套完整的建筑蓝图(数百页,精确到每根管线), 而 BTF 则是一张X 光透视图——只显示你关心的承重墙和走廊位置, 但足以让你安全地在建筑内穿行。体积小了两个数量级,信息却恰好够用。

CO-RE:一次编译,处处运行

有了 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"), 然后在加载过程中自动重写字节码中的偏移量。

CO-RE 工作原理:一次编译,处处运行 📝 编译时(开发机器) task->comm 源码引用 task_struct.comm Clang/LLVM -target bpf 📦 ELF 输出文件 字节码: load R1, [R2+648] 重定位记录: "comm" @ offset 648 BTF: task_struct.comm 信息 分发 ⚙️ 加载时(目标机器) 📋 目标内核 BTF task_struct.comm → offset 672 libbpf CO-RE Relocator ✅ 重写后的字节码 load R1, [R2+672] 偏移量已自动修正! 648 → 672 ✓ 🎯 结果 ✓ Kernel 5.4 → offset 648 ✓ Kernel 5.10 → offset 672 ✓ Kernel 6.1 → offset 688 同一个 ELF,跑遍所有内核 🎉

图 3-1:CO-RE 工作原理 —— 编译时记录字段引用,加载时根据目标内核 BTF 自动重写偏移量

💡

为什么 CO-RE 如此重要?

在 CO-RE 出现之前,每次部署 eBPF 程序都需要在目标机器上现场编译—— 这意味着每台机器都必须安装 Clang、LLVM 和完整的内核头文件。 对于管理数万台服务器的企业来说,这是一场运维噩梦

CO-RE 让你可以在 CI/CD 环境中编译一次,然后将同一个二进制文件分发到运行不同内核版本的所有机器—— 就像 Java 的"Write Once, Run Anywhere",但是在内核级别实现的。 这是 eBPF 从"研究工具"走向"企业级基础设施"的关键转折点


2

阶段二:准入 — 验证与安全

bpf() 系统调用 → Verifier 静态分析 → 数学安全证明

ELF 文件编译好了。现在,它需要从用户空间进入内核空间——穿越第一章中描述的"城墙"。 这个过程有且只有一个入口:bpf() 系统调用

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(验证器)

Verifier:内核的"最高法院"

如果 eBPF 的安全性有一个总设计师,那就是 Verifier。 它是整个 eBPF 架构中最关键、最复杂的组件—— 没有它,eBPF 就只是另一种内核模块,存在一切内核模块的风险。

把 Verifier 想象成一座城堡的最高法院: 每一段代码要进入城堡,必须先在法庭上接受逐行审判。 法官不听辩护词、不看推荐信——他只看证据(代码本身), 并亲自走遍程序所有可能的执行路径, 确认每一步都不会危害城堡的安全。只要有一条路径存在风险, 整个程序就会被驳回——没有例外,没有上诉

Verifier 核心算法:有界深度优先搜索 + 状态剪枝 程序控制流图(CFG) ENTRY Block A r1 = map_lookup() r1!=NULL r1==NULL Block B ✓ val = *r1 // 安全 Block C ✗ val = *r1 // 危险! ❌ 拒绝加载 Block D EXIT ✓ Verifier 追踪每条路径上的寄存器状态: R1={type=MAP_VALUE_OR_NULL} → 分支后变为 左路: R1={type=MAP_VALUE,range=[0,4]} 右路: R1={type=NULL} 🔬 验证算法详解 ① 有界深度优先搜索(Bounded DFS) 从入口开始,沿每条分支递归遍历所有可能的 执行路径。"有界"意味着循环次数有上限(防止无穷分析) ② 寄存器状态追踪 每步记录 R0-R10 的完整状态:类型(标量/指针/ NULL)、值范围 [min, max]、对齐方式、引用关系 ③ 状态剪枝(State Pruning) 如果到达某个程序点时的寄存器状态是之前 已验证状态的子集 → 跳过(已证明安全),避免指数爆炸 ④ 安全性检查(每条指令) • 内存访问是否在合法范围内? • 指针算术是否可能越界?指针是否可能为 NULL? ⑤ 终止性保证 指令复杂度上限 100 万条;禁止无界循环(有界循环 在 5.3+ 支持,但需证明必然终止)

图 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 检查:

  • 内存访问是否在合法范围内?(栈空间 512 字节、Map 值边界、数据包边界)
  • 指针算术是否可能导致越界?
  • 是否存在未初始化的寄存器读取?
  • Helper 函数的参数类型是否匹配?
  • 是否存在可能的除零错误?

第五步:终止性保证。 程序的总指令复杂度不得超过 100 万条(自 Kernel 5.2 起, 此前为 4096 条)。所有循环必须有可证明的有限迭代次数。 这从数学上保证了程序必然终止——不会在内核中陷入死循环。

⚠️

Verifier 不是万能的

Verifier 保证的是结构安全(不崩溃、不越界、必终止), 而非逻辑正确。一个通过 Verifier 的程序可能包含逻辑 bug—— 例如错误地丢弃了正常网络包,或把数据写入了错误的 Map key。 Verifier 防止的是内核级灾难(Kernel Panic),而非应用级错误

此外,Verifier 的静态分析天然是保守的: 它可能会拒绝一些实际上安全但它无法证明安全的程序。 这是安全领域的经典权衡——宁可误拒,绝不误放(false positive over false negative)。 有经验的 eBPF 开发者需要学会"讨好 Verifier"—— 通过显式的边界检查和类型断言帮助 Verifier 理解你的意图。


3

阶段三:加速 — JIT 编译

BPF 字节码 → 原生机器指令(x86-64 / ARM64)→ 零开销执行

程序通过了 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函数返回值raxx7
R1第 1 参数rdix0
R2第 2 参数rsix1
R3第 3 参数rdxx2
R4第 4 参数rcxx3
R5第 5 参数r8x4
R6-R9被调用者保存rbx, r13-r15x19-x22
R10栈帧指针(只读)rbpx25

这意味着 JIT 在翻译时几乎不需要额外的寄存器搬移指令—— BPF 的 R1 直接就是 x86-64 的 rdi(函数第一参数寄存器), R0 直接就是 rax(返回值寄存器)。 这种设计使得 JIT 编译后的代码与手写汇编几乎零性能差距

L3 实操 · 用 bpftool 查看 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
🧠

L2 深度 · 96 字节 → 68 字节:为什么 JIT 输出更小?

注意上面输出中的 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 点上,等待内核事件的触发。 这就是下一阶段的任务。

3
Chapter 3 · The Lifecycle Deep Dive

HOW — The Complete Lifecycle of an eBPF Program

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.

1

Stage 1: Birth — Writing & Compiling

C/Rust → Clang/LLVM → BPF Bytecode (ELF) + BTF + CO-RE

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: The "X-Ray Manual" for Kernel Data Structures

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.

CO-RE: Compile Once, Run Everywhere

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.


2

Stage 2: Admission — Verification & Safety

bpf() syscall → Verifier static analysis → Mathematical safety proof

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: The Kernel's "Supreme Court"

The Verifier is the most critical component in the eBPF architecture. Its core algorithm is Bounded Depth-First Search with State Pruning:

  1. Bounded DFS: Recursively traverse all possible execution paths from the entry point.
  2. Register State Tracking: At each step, maintain the complete state of R0-R10 — type (scalar/pointer/NULL), value range [min, max], alignment.
  3. State Pruning: If the current state at a program point is a subset of a previously verified state → skip (already proven safe).
  4. Per-Instruction Safety Checks: Bounds checking, null pointer checks, type matching for helper arguments.
  5. Termination Guarantee: Complexity limit of 1 million instructions; all loops must have provably finite bounds.
⚠️

The Verifier Is Not Omnipotent

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.


3

Stage 3: Acceleration — JIT Compilation

BPF Bytecode → Native Machine Instructions (x86-64/ARM64)

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.

Inspecting JIT Output with bpftool

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
💡

Stages 1-3 Summary

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.

4

阶段四:就位 — 加载与挂钩

Loader 注入内核 → Hook 点挂载 → bpf_link 持久化 → Helper / Tail Call 能力赋予

经过编译、验证和 JIT 三道淬炼,我们的 eBPF 程序已经是一段安全的原生机器代码, 静静地存在于内核内存中。但它仍然是"待命状态"—— 就像一名已经通过所有背景调查的特工,拿到了进入城堡的许可证, 但还没有被分配到具体的岗位

这一阶段要做两件事:加载(将程序和 Maps 注入内核)和挂钩(将程序绑定到内核事件点)。 这是 eBPF 从"存在"走向"生效"的关键一步,也是理解 eBPF 工作机制的核心所在。

Loader:将程序注入内核的搬运工

用户态的 Loader(通常是 libbpf 库或 bpftool 命令行工具) 负责读取编译好的 ELF 文件,通过 bpf() 系统调用将程序和 Maps 加载到内核中。 Loader 的工作流程如下:

  1. 解析 ELF:提取 BPF 字节码段、BTF 段、Maps 定义、重定位信息
  2. CO-RE 重定位:读取目标内核的 BTF,重写字节码中的字段偏移量
  3. 创建 Maps:调用 bpf(BPF_MAP_CREATE, ...),内核返回 Map 的文件描述符(fd)
  4. 加载程序:调用 bpf(BPF_PROG_LOAD, ...),触发 Verifier → JIT → 返回程序 fd
  5. 挂载到 Hook:将程序 fd 绑定到目标事件点
📖

libbpf 的"骨架"模式(Skeleton)

现代 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);

Hook 机制:eBPF 的灵魂所在

如果说 Verifier 是 eBPF 的"安全基石",那么 Hook 机制就是 eBPF 的"灵魂"。 没有 Hook,eBPF 程序只是内核中一段沉默的代码; 有了 Hook,它就变成了一个敏锐的感知器—— 能够在内核数据流的任意关键节点上拦截、观测、决策。

⭐ 设计哲学溯源:好莱坞原则与控制反转

要真正理解 Hook,我们先追溯它的设计哲学。

在软件工程中有一条著名的 好莱坞原则(Hollywood Principle)

"Don't call us, we'll call you."(别打电话给我们,我们会打给你。)

在好莱坞,演员面试后不应该反复打电话催促——导演会在需要的时候主动联系你。 这个原则的技术表达叫做 控制反转(Inversion of Control, IoC): 不是你的代码去轮询(polling)内核"发生了什么事", 而是内核在事件发生时主动调用你的代码

这形成了一条清晰的演化链条:

🎬 好莱坞原则 "Don't call us, we'll call you." 被动等待 · 不要轮询 🔄 控制反转 (IoC) 框架调用用户代码 而非用户代码调用框架 Spring · React · Node.js ⚡ 事件驱动编程 注册回调函数 事件触发时执行 addEventListener · epoll 🐝 eBPF 内核 Hook 将 eBPF 程序注册到 内核事件的函数指针插槽 事件发生 → 内核调用你 XDP · TC · Kprobe · LSM ...

图 3-3:eBPF Hook 设计哲学演化链 —— 从好莱坞原则到控制反转到事件驱动到内核 Hook

在物理层面,Hook 的本质极为简洁:内核在关键代码路径上预留了函数指针插槽。 当你挂载一个 eBPF 程序时,实际上就是把程序的入口地址填入这个插槽。 当内核执行到该路径时,它会检查插槽——如果非空,就调用你的程序。 就像在一条高速公路上安装了一个收费站:车辆(数据)经过时, 收费站可以检查、记录、放行或拦截。卸载时,把插槽清空——收费站消失,高速公路恢复原样。

Hook 全景图:eBPF 能挂在哪里?

eBPF 的威力在很大程度上取决于它能挂载到多少种内核事件。 截至 Linux 6.12,eBPF 支持数十种程序类型,覆盖内核的几乎所有子系统。 以下是最重要的几类:

eBPF Hook 全景图 —— 数据包从网卡到应用的完整路径 🔌 网卡驱动层 (NIC Driver) Hardware Ring Buffer → DMA ⚡ XDP (eXpress Data Path) 最早拦截点 · DDoS 防护 · Mpps 级 XDP_PASS | XDP_DROP | XDP_TX | XDP_REDIRECT XDP_PASS ✓ XDP_DROP ✗ 🔀 流量控制层 (TC) qdisc → classifier → action 🏷️ TC BPF (cls_bpf) QoS · 包标记 · 策略路由 · SNAT/DNAT TC_ACT_OK | TC_ACT_SHOT | TC_ACT_REDIRECT 🌐 网络协议栈 IP → TCP/UDP → Socket 🔗 Socket / Cgroup Hooks 连接级策略 · Sockmap 加速 · 带宽控制 BPF_CGROUP_INET_INGRESS/EGRESS 📱 应用层 (User Space) recv() / send() / read() / write() 📍 其他关键 Hook 类型 🔬 Kprobes / Kretprobes 动态挂载到任意内核函数入口/出口 🔍 Uprobes / Uretprobes 动态挂载到用户态函数(如 SSL_write) 📌 Tracepoints 内核预定义的静态追踪点(稳定 ABI) 🛡️ LSM (Linux Security Module) 安全策略执行点(Kernel 5.7+) ⚙️ sched_ext (Kernel 6.12+) 自定义 CPU 调度策略 💡 越靠近网卡(越上方),处理速度越快、CPU 开销越低 XDP 在驱动层处理 — 数据包从未进入协议栈 — 性能最极致(可达 24Mpps 单核) 各 Hook 类型关键对比: XDP 最快 · L2 层 · 可在 skb 分配前丢包 · 适合 DDoS/负载均衡 · 需要驱动支持 TC L3 层 · 完整 skb 访问 · 可修改包 · 适合 NAT/策略路由/QoS · 所有网卡通用 Kprobe 动态 · 任意内核函数 · 不稳定(随内核版本变化)· 适合调试和追踪 Tracepoint 静态 · 稳定 ABI · 内核预定义 · 适合长期监控 · 如 sched/sched_switch

图 3-4:eBPF Hook 全景图 —— 从网卡驱动层(XDP)到应用层,以及安全(LSM)、调度(sched_ext)等非网络 Hook

XDP vs TC:网络路径上的两大重镇

在网络场景中,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、包标记
🔑

为什么 XDP 这么快?

秘密在于"跳过"。在传统的网络路径中,数据包从网卡 DMA 到达后, 内核会为其分配一个 sk_buff (skb) 结构体——这是一个相当复杂的对象, 包含几十个字段和各种指针。这个分配过程本身就有不小的 CPU 开销。

XDP 的关键创新是:在 skb 分配之前就拦截数据包。 它直接操作网卡 DMA 缓冲区中的原始数据。 如果判定为恶意流量(如 DDoS 攻击包),立刻 XDP_DROP—— 数据包被丢弃,skb 从未被分配、协议栈从未被触发。 这就是 XDP 能达到每核每秒处理数百万包(Mpps)的根本原因。


Helper Functions:内核开放给 eBPF 的安全 API

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 恶意进程

Kfuncs:下一代内核 API(v6.x+)

从 Linux 6.x 开始,内核引入了 Kfuncs(Kernel Functions)作为 Helper 的进化方向。 与 Helper 不同,Kfuncs 不需要在内核中硬编码注册—— 内核开发者可以将任何内核函数标记为可供 eBPF 调用, 并通过 BTF 自动暴露其签名和参数类型。 这大幅降低了扩展 eBPF 能力的门槛,新功能不再需要等待正式的 Helper 函数审批流程。


尾调用(Tail Calls):模块化处理管道

单个 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 层


bpf_link:持久化连接与生命周期管理

早期的 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 构建处理管道。

现在,它正安静地等待—— 等待那个事件的到来。一旦事件触发,内核会通过函数指针瞬间唤醒它。 这就是下一个阶段——运行


5

阶段五:运行 — 事件触发与执行

内核事件发生 → Hook 激活 → eBPF 程序在内核上下文中执行 → 零 Context Switch

这是整个生命周期中最"安静"但最"关键"的阶段——因为它几乎不需要你做任何事情

当挂载点对应的内核事件发生时—— 一个网络数据包到达网卡(XDP)、 一个进程调用了 execve()(Tracepoint)、 一个文件被打开(LSM)、 一次 TCP 连接被建立(Kprobe on tcp_v4_connect)—— 内核的执行流会自然地经过挂载点,检查函数指针插槽, 发现有 eBPF 程序注册在此,然后直接调用它。

让我们详细看看这个过程中发生了什么:

eBPF 运行时执行流程 ⚡ 内核事件发生 如:网络包到达 sys_enter_execve 🔍 Hook 检查 函数指针非空? if (hook->prog != NULL) hook->prog->run(ctx); 🐝 eBPF 程序执行 JIT 后的原生机器码 在内核上下文中直接运行 零 Context Switch ⚡ ✅ 返回结果 XDP: PASS / DROP TC: OK / REDIRECT 🏎️ 零 Context Switch 程序在触发事件的同一个 CPU 核心上 以内核权限直接执行,无需切换上下文 CPU 缓存保持热态 → 极低延迟 延迟: ~100ns — 数μs 🛡️ 沙盒保证 已通过 Verifier 验证 → 不会崩溃 指令上限 100 万 → 必然终止 内存边界已检查 → 不会越界 即使有 bug 也不会 Kernel Panic 🔗 上下文访问 通过 ctx 参数获取事件上下文: XDP: struct xdp_md *ctx TC: struct __sk_buff *ctx TP: struct trace_event_raw_* ctx

图 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)形成鲜明对比。

🧠

L2 深度 · eBPF 程序能看到什么?

每个 eBPF 程序在被调用时都会收到一个 ctx(上下文)参数, 它的类型取决于 Hook 类型:

  • XDPstruct xdp_md *ctx — 包含数据包起止指针、接收接口等
  • TCstruct __sk_buff *ctx — 包含完整的 skb 元数据(协议、长度、mark 等)
  • Tracepointstruct trace_event_raw_xxx *ctx — 包含事件参数(如 execve 的文件名、参数列表)
  • Kprobestruct pt_regs *ctx — 包含被探测函数的寄存器状态(参数值)
  • LSM:函数签名与对应的安全 Hook 一致(如 bprm_check_security 的参数)

eBPF 程序通过这个 ctx 参数感知世界, 再通过 Helper 函数和 BPF Maps 与外部交换信息

4

Stage 4: Positioning — Loading & Hooking

Loader injects into kernel → Hook point attachment → bpf_link persistence → Helper / Tail Call capabilities

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.

⭐ The Hook Design Philosophy: The Hollywood Principle

"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 Panorama: Where Can eBPF Attach?

Hook Type Layer Key Use Cases
XDPNIC Driver (pre-skb)DDoS mitigation, load balancing (24 Mpps/core)
TC BPFTraffic Control (post-skb)NAT, policy routing, QoS, packet marking
KprobesAny kernel functionDynamic tracing, debugging (unstable ABI)
TracepointsPredefined kernel pointsLong-term monitoring (stable ABI)
LSMSecurity module hooksSecurity policy enforcement (Kernel 5.7+)
UprobesUser-space functionsApplication tracing (e.g., SSL_write)
sched_extCPU schedulerCustom scheduling policies (Kernel 6.12+)

Helper Functions: The Kernel's Safe API

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: Modular Processing Pipelines

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: Persistent Attachment & Atomic Replacement

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.


5

Stage 5: Execution — Event-Triggered Running

Kernel event fires → Hook activates → eBPF runs in kernel context → Zero Context Switch

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:

🏎️ Zero Context Switch

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.

🛡️ Sandbox Guarantees

Verified safe → cannot crash. Instruction limit → must terminate. Bounds checked → cannot access out-of-bounds memory. Even a buggy program won't Kernel Panic.

⚡ Pay-per-Event

If no events fire for 10 minutes, the eBPF program consumes exactly zero CPU. This is fundamentally different from polling-based monitoring.

🧠

What Can an eBPF Program "See"?

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.

6

阶段六:通信 — 内核与用户空间的数据交换

BPF Maps · Ring Buffer vs Perf Buffer · 完整代码示例(BCC + libbpf)

eBPF 程序在内核中运行、收集数据、做出决策——但这些信息如果无法传递到用户空间, 就像一个在前线侦察的特工无法向总部汇报——再好的情报也毫无意义。

eBPF 生态中解决这个问题的核心机制叫做 BPF Maps—— 它是内核态和用户态之间的高速数据通道

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 级重定向、零拷贝数据转发

Ring Buffer vs Perf Buffer:2025 最佳实践

当 eBPF 程序需要将事件数据流(如每次 syscall 的参数、每个网络连接的元数据) 推送到用户态时,有两种主要的 Map 类型可选: 传统的 Perf Buffer(Linux 4.4+)和新一代的 Ring Buffer(Linux 5.8+)。

Ring Buffer vs Perf Buffer 架构对比 Perf Buffer(旧方案 · Linux 4.4+) 每个 CPU 独立的缓冲区: CPU 0 Buffer [████░░░░] 50% CPU 1 Buffer [██░░░░░░] 25% CPU 2 Buffer [████████] 溢出! ⚠️ CPU 3 Buffer [█░░░░░░░] 12% ❌ 问题 • 内存浪费:空闲 CPU 的 缓冲区被预留但未使用 • 热点溢出:忙碌 CPU 的 缓冲区可能满溢丢数据 • 数据拷贝:事件从内核 缓冲区拷贝到用户空间 • 乱序:多 CPU 数据需要 用户态排序 总内存 = Buffer Size × CPU 数量 (如 64KB × 128 核 = 8MB) bpf_perf_event_output(ctx, &map, BPF_F_CURRENT_CPU, &data, sz) Ring Buffer(新方案 · Linux 5.8+)⭐ 所有 CPU 共享的单一缓冲区: 共享 Ring 所有 CPU 写入同一个环 CPU0↘ CPU1↗ ↙CPU2 ↖CPU3 ✅ 优势 • 内存高效:单一缓冲区,无碎片浪费 • 无溢出:忙碌 CPU 可使用闲 CPU 的空间 • 天然有序:FIFO 顺序,无需用户态排序 • 零拷贝读取:用户态通过 mmap 直接读内核内存 • 变长记录:支持不同大小的事件,节省空间

图 3-6:Ring Buffer vs Perf Buffer —— Ring Buffer 用共享单环取代 Per-CPU 多环,更高效、更有序、更省内存

💡

2025 最佳实践:优先使用 Ring Buffer

除非你需要支持 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 示例(3 行核心 Python)

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 的局限性

BCC 简单但不适合生产环境:每次运行都在目标机器上编译(需要 LLVM + 头文件), 启动慢、资源消耗大、没有 CO-RE(跨版本不兼容)。 生产环境请使用下面的 libbpf + CO-RE 范式。

示例二:完整 libbpf 范式(生产级)

这是现代 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
🧠

L2 深度 · 这段代码完整覆盖了生命周期的哪些阶段?

回顾一下我们学过的七个阶段,这个例子精确对应:

  • 阶段一(诞生)clang -target bpf 编译 → ELF + BTF
  • 阶段二(准入)open_and_load() 内部调用 bpf(BPF_PROG_LOAD) → Verifier 验证
  • 阶段三(加速):Verifier 通过后内核自动 JIT 编译
  • 阶段四(就位)attach() 将程序挂载到 tracepoint
  • 阶段五(运行):每次 execve() 触发 → eBPF 程序执行
  • 阶段六(通信)bpf_ringbuf_submit() 推送 → ring_buffer__poll() 读取
  • 阶段七(终结)destroy() 释放所有资源

7

阶段七:终结 — 卸载与清理

引用计数归零 → 内核自动释放程序 + Maps + Hook 连接

万物皆有终。当 eBPF 程序完成使命(或不再被需要),它需要被安全地卸载。

Linux 内核通过引用计数(Reference Counting)管理 eBPF 程序的生命周期。 每一个引用——用户态的文件描述符、bpf_link、被其他程序 Tail Call 引用—— 都会将计数加一。当所有引用被关闭,计数归零时, 内核会自动执行清理

  • 解除 Hook:将 Hook 点上的函数指针清空——"收费站"消失,高速公路恢复畅通
  • 释放 JIT 代码:回收存放原生机器指令的内核内存
  • 销毁 Maps:释放所有关联的 BPF Maps(除非 Map 被其他程序共享或被 pin 到 BPF 文件系统)
  • 释放 BTF 信息:回收程序关联的类型元数据

整个过程是优雅且原子的——不会出现"程序卸了一半,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

🔥 The Hard Way:为什么你需要 Cilium 和 Tetragon

到这里,你已经跟随一个 eBPF 程序走完了它从诞生到终结的完整生命周期。 恭喜——你现在对 eBPF 的理解,已经超过了 95% 的技术从业者。

但我必须诚实地说:这条路极其艰难。

让我们回顾一下,要手动开发和部署一个生产级 eBPF 解决方案,你需要:

🔥 "The Hard Way" — 手写 eBPF 的七大痛点 ① Verifier 搏斗 每一处边界检查、NULL 判断 都可能被 Verifier 弹回 "invalid mem access" × 100 ② 内核版本碎片化 生产环境可能跑着 4.19 ~ 6.8 多个版本,CO-RE 也非万能 Helper/Kfunc 可用性差异巨大 ③ 受限编程模型 512B 栈 · 无动态内存分配 无全局可变状态 · 受限循环 这不是 "写 C",是 "写受限 C" ④ 调试地狱 内核态无 GDB · 无 printf bpf_printk 是你唯一的朋友 trace_pipe 刷屏噩梦 ⑤ 安全策略编排 手写网络策略 = 手写 iptables × 100(每个 Pod 一套规则) K8s 动态拓扑下不可维护 ⑥ 数据管道工程 Ring Buffer 满了怎么办? 事件聚合、去重、导出? 自建遥测管道成本极高 ⑦ 运维与升级 程序热更新、回滚机制 集群级分发、版本管理 谁来管理 10,000 台节点? 💡 这就是为什么我们需要更高层次的抽象 Cilium = eBPF 之上的云原生网络与安全平台 Tetragon = eBPF 之上的运行时安全引擎

图 3-7:"The Hard Way" —— 手写 eBPF 的七大痛点,以及为什么 Cilium 和 Tetragon 是必然的进化方向

想象一下:你不只是要写一个简单的 execve 追踪器—— 你要在一个拥有 500 个微服务、10,000 个 Pod 的 Kubernetes 集群中实现 L3/L4/L7 网络策略透明加密负载均衡DNS 感知策略运行时安全监控全栈可观测性。 你需要编写和维护数十个相互协作的 eBPF 程序、 管理数百个 BPF Maps、处理版本兼容性、 构建遥测管道、实现策略编排引擎……

这不是一个人或一个小团队能承受的工程量。这需要的是: 一个经过数百万节点生产验证的平台。

🚀

从 "The Hard Way" 到产业级平台

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 内核中的完整秘密。

6

Stage 6: Communication — Kernel ↔ User-Space Data Exchange

BPF Maps · Ring Buffer vs Perf Buffer · Complete Code Examples

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.

Ring Buffer vs Perf Buffer

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

Complete Code Example: libbpf + Ring Buffer

The complete production-grade example (shown in the Chinese section above) demonstrates the full lifecycle:

  1. Kernel-side (execmon.bpf.c): SEC macro, Ring Buffer Map, bpf_ringbuf_reserve/submit
  2. User-side (execmon.c): open_and_load → attach → ring_buffer__poll → destroy
  3. Build: clang → bpftool gen skeleton → gcc → sudo run

7

Stage 7: Termination — Unloading & Cleanup

Reference count → 0 → Kernel auto-releases program + Maps + Hook connections

The 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.


🔥 The Hard Way: Why You Need Cilium and Tetragon

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:

  1. Verifier battles: Every boundary check can bounce your program
  2. Kernel version fragmentation: Production environments run 4.19 through 6.8
  3. Constrained programming model: 512B stack, no dynamic allocation, limited loops
  4. Debugging hell: No GDB in kernel space; bpf_printk is your only friend
  5. Policy orchestration: Hand-writing network policies for 10,000 Pods is unsustainable
  6. Data pipeline engineering: Event aggregation, deduplication, export at scale
  7. Operations & upgrades: Hot updates, rollback, cluster-wide distribution

This 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.

🏁

Chapter 3 Summary: The Complete Lifecycle

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

4
Chapter 4 · Value Proposition

VALUE — eBPF 的三大应用支柱

在第三章中,我们深入理解了 eBPF 程序"如何"在内核中运行。 现在让我们转向更实际的问题:eBPF 能为我们做什么? 它正在三个核心领域引发深刻的变革——安全、可观测性和网络。 每个领域都不是简单的"改进",而是范式级的颠覆

eBPF Runtime(Verifier · JIT · Maps · Hooks) 🛡️ 下一代安全 不可绕过 · 纵深防御 实时拦截 · 内核级执行 🔭 极致可观测性 无侵入 · 全栈透视 持续剖析 · 加密前捕获 🌐 云原生网络加速 XDP 替代 iptables Sidecar-less · 4X 吞吐

4.1 下一代安全架构:从"事后检测"到"实时拦截"

传统的安全工具(EDR、HIDS、WAF)大多运行在用户空间, 通过系统调用钩子(如 ptraceaudit 框架)来监控内核行为。 这种架构有三个致命弱点:

❌ 可绕过

攻击者可以通过内核漏洞(如 dirty pipedirty cow) 直接在内核态执行恶意代码,完全绕过用户态的安全监控。 如果安全工具不在内核中,它就看不到内核中发生的事情。

❌ 高延迟

用户态安全工具检测到威胁后,需要通过系统调用请求内核采取行动(如终止进程)。 从检测到响应,可能有数毫秒到数秒的延迟—— 足够攻击者完成数据外泄或横向移动。

❌ 高开销

传统工具通过 audit 子系统收集日志,产生海量数据, 需要发送到用户态解析。在高频系统调用场景下, 仅日志收集就可能消耗 5-15% 的 CPU

eBPF 的安全范式转移

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—— 攻击者的代码从未有机会在宿主机上执行一条指令


4.2 极致可观测性:无侵入全栈透视

可观测性(Observability)是理解系统行为的能力——不是"它告诉了你什么", 而是"你能问它什么"。eBPF 将这种能力提升到了前所未有的水平。

传统可观测性的困境

传统方案需要修改应用代码——加入 SDK、埋入追踪点(tracing instrumentation)、 引入 Sidecar Proxy 来采集网络指标。每一步都意味着代码侵入性能开销维护负担。更关键的是,这些方案只能看到应用层的行为—— 内核中发生了什么(调度延迟、内存压力、网络协议栈拥塞),它们完全看不到。

eBPF 可观测性的四大突破

🔍 零侵入全栈透视

eBPF 可以在不修改应用代码、不重启服务的前提下, 从网卡驱动层到用户态应用,采集每一层的详细指标。 无需 SDK 集成、无需重新编译、无需维护复杂的 Agent 配置。

场景:你新接手一个遗留系统,没有文档、没有追踪代码—— 通过 eBPF,你可以在几分钟内获得完整的系统调用画像、网络连接图谱和函数级别的延迟剖析。

📊 持续剖析(Continuous Profiling)

传统 profiling 是"采样式"的——你在问题发生时手动运行 perf record, 希望能捕获到问题瞬间。eBPF 实现了7×24 小时持续剖析, 以 <2% CPU 开销持续采集 CPU 栈、内存分配、锁竞争等信息。

结果:生成实时火焰图(Flame Graph),精确到函数级定位性能瓶颈。 问题发生后回溯历史数据,而不是"等问题再现时手动抓取"。

🔐 加密前原始数据捕获

在 TLS/mTLS 无处不在的云原生环境中,传统网络抓包只能看到加密后的乱码。 eBPF 通过 Uprobe 挂钩 SSL 库(如 OpenSSL 的 SSL_writeSSL_read),在加密之前/解密之后捕获原始明文数据。

意义:无需部署 mTLS 解密代理、无需分发私钥—— 在安全合规的前提下实现对加密流量的完整可见性。

🗺️ 服务拓扑自动发现

通过观测内核网络栈中的实际连接行为(TCP connect / accept / close), eBPF 可以自动绘制服务之间的依赖关系图—— 无需人工配置、无需服务注册中心、无需修改服务代码。

代表工具:Hubble(Cilium 的可观测层)、Pixie、Parca、SkyWalking Rover。


4.3 云原生网络加速:从 iptables 到 eBPF

Kubernetes 网络的核心问题是:如何在动态变化的容器拓扑中高效地实现 服务发现、负载均衡、网络策略和流量加密。 传统方案依赖 iptables(或其后继 nftables)和 Sidecar Proxy—— 但它们在大规模集群中正在达到性能和可维护性的极限

iptables 的瓶颈

iptables 使用线性链式规则匹配——每个数据包到达时, 从第一条规则开始逐条匹配,直到找到匹配项。在一个拥有 10,000 个 Service 的集群中, kube-proxy 会生成数万条 iptables 规则。 规则更新时需要全量重写(O(n) 复杂度), 每次 Pod 变更都会导致数秒的规则刷新延迟。

eBPF 的网络革命

XDP 替代 iptables

eBPF 用哈希表查找(O(1))替代 iptables 的线性匹配(O(n))。 Cilium 将 Service → Endpoint 映射存储在 BPF Map 中, 数据包到达时直接查表转发——无论有多少条规则,查找时间恒定。 规则更新是增量式的:只修改变更的条目,而非全量重写。

Sidecar-less 架构

传统服务网格(如 Istio)为每个 Pod 注入一个 Sidecar Proxy(Envoy), 增加了内存开销(每 Pod 100-200MB)和延迟(额外 2-5ms)。 Cilium 的 eBPF 方案将 L3/L4 处理下沉到内核, 资源消耗降低 70%+,延迟降低到 <1ms

透明加密

Cilium 利用 eBPF + IPsec/WireGuard 实现节点间流量透明加密—— 应用层完全无感知、无需修改代码、无需管理证书。 对于合规要求严格的行业(金融、医疗), 这是一项杀手级能力。


🖥️ 交互式内核终端 Demo

以下是一个模拟的 eBPF 操作终端。点击预设按钮或输入命令,体验 eBPF 的核心能力:

ebpf-lab — eBPF Interactive Demo
root@ebpf-lab:~# Welcome to the eBPF Interactive Demo Terminal
Type a command or click a button below to explore eBPF capabilities.
Available: xdp-drop, kprobe-trace, map-query, bpftool-prog, bpftool-map, ringbuf-poll, help
 
root@ebpf-lab:~#
4
Chapter 4 · Value Proposition

VALUE — The Three Pillars of 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.


4.1 Next-Generation Security: From Detection to Real-Time Enforcement

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:

🛡️ Unbypassable

eBPF hooks sit on kernel code paths that all operations must traverse. Attackers cannot bypass monitoring that lives inside the kernel itself.

⚡ In-Kernel Enforcement

Tetragon uses bpf_send_signal(SIGKILL) to terminate malicious processes before the syscall returns — nanosecond-level response, no user-space round-trip.

🏰 Defense in Depth

eBPF adds enforcement at every layer: network (XDP/TC), syscall (Tracepoint), security policy (LSM), process behavior (Kprobe), and data (Uprobe on SSL).


4.2 Ultimate Observability: Non-Intrusive Full-Stack Vision

🔍 Zero-Instrumentation

Collect metrics from NIC driver to application layer without modifying code, restarting services, or deploying SDKs. Ideal for legacy systems with no documentation.

📊 Continuous Profiling

24/7 profiling at <2% CPU overhead: CPU flame graphs, memory allocation tracing, lock contention analysis. Retroactively investigate issues instead of waiting for reproduction.

🔐 Pre-Encryption Capture

Hook SSL_write/SSL_read via Uprobes to capture plaintext data before encryption — no decryption proxies, no private key distribution.

🗺️ Auto Service Topology

Observe actual kernel-level connections (TCP connect/accept/close) to automatically map service dependencies — no manual config needed.


4.3 Cloud-Native Networking: From iptables to eBPF

O(1) Replacement for iptables

eBPF replaces iptables' linear chain matching (O(n)) with hash table lookups (O(1)). Incremental updates vs. full-table rewrites.

Sidecar-less Service Mesh

Cilium pushes L3/L4 processing to kernel, reducing resource consumption by 70%+ and latency to <1ms vs. Sidecar Proxy models.

Transparent Encryption

eBPF + IPsec/WireGuard for node-to-node traffic encryption — fully transparent to applications, no certificate management required.


🖥️ Interactive Kernel Terminal Demo

Click the buttons or type commands in the terminal above to explore eBPF capabilities interactively.

5
Chapter 5 · Industry Ecosystem

ECOSYSTEM — 产业生态与先驱

任何技术的真正力量不仅在于其原理的精妙,更在于生态系统的繁荣。 eBPF 之所以能从学术研究走向全球基础设施的核心, 是因为一个由开源项目、商业公司和全球巨头共同构建的正向飞轮正在加速旋转。

5.1 开源生态:eBPF 的四大天王

eBPF Linux Kernel 🔗 Cilium 云原生网络 · 安全 · 可观测性 CNI · Service Mesh · ClusterMesh GitHub ★ 20k+ · CNCF Graduated 🛡️ Tetragon 运行时安全 · 内核级执行 TracingPolicy · SIGKILL · Process Ancestry GitHub ★ 3.5k+ · CNCF Sandbox 🔬 bpftrace / BCC 内核/用户态动态追踪 · 一行式脚本 Brendan Gregg 创建 · 性能分析首选 bpftrace -e 'tracepoint:syscalls:sys_enter_*{@=count()}' 📊 Hubble / Pixie / SkyWalking 云原生可观测性平台 Hubble: Cilium 网络可观测 · L3-L7 流可视化 SkyWalking Rover: eBPF 驱动的 APM Agent Katran Falco Parca Tracee eBPF Foundation (Linux Foundation)

图 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

5.2 Isovalent 的工业化贡献:从开源到企业级平台

在 eBPF 生态中,Isovalent 扮演着独特且关键的角色。 它不仅是 Cilium 和 Tetragon 的主要创建者和维护者, 更是推动 eBPF 从"内核开发者的工具"走向"企业级基础设施"的核心力量。

Isovalent A Cisco Company ① 开源引领 创建 Cilium + Tetragon 贡献 Linux 内核 eBPF 代码 ② 标准制定 CNCF Graduated (Cilium) Gateway API · eBPF Foundation ③ 降低门槛 声明式 API 替代手写 eBPF Helm/Operator 一键部署 ④ 企业级平台 Isovalent Enterprise Cisco Secure Connect · 全球支持

图 5-2:Isovalent 飞轮模型 —— 开源引领 → 标准制定 → 降低门槛 → 企业级平台,形成正向循环

Cisco 收购的战略意义(2024)

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 长期开源投入的回报。


5.3 全球巨头实战:eBPF 在生产中的规模验证

eBPF 已经不是实验室技术——它正在全球最大的基础设施中运行, 每天处理着数万亿个事件。以下是最具代表性的部署案例:

🟦

Google

Google 在其生产基础设施中大规模使用 eBPF。GKE(Google Kubernetes Engine) 将 Cilium 作为默认 CNI 可选项。Google 的安全团队使用 eBPF 进行 内核级安全监控和异常检测。Prodkernel 团队持续贡献 eBPF 上游代码。

规模:数百万节点 · 场景:CNI / 安全 / 性能分析

🟦

Meta (Facebook)

Meta 是 eBPF 最早和最大规模的采用者之一。Katran(XDP L4 负载均衡器) 每秒处理超过 1000 万个连接,替代了传统的 IPVS 方案。 Meta 还深度参与了 BTF、CO-RE 等核心基础设施的开发。

规模:数十万服务器 · 场景:L4 LB / DDoS / 性能追踪

🟧

Cloudflare

Cloudflare 全球边缘网络(300+ 城市)使用 XDP 进行DDoS 防护, 在 Tbps 级攻击下保持服务可用。其 bpf_xdp 程序 在网卡驱动层直接丢弃恶意流量,保护上游服务完全不受影响。

规模:300+ 全球 PoP · 场景:DDoS / 边缘安全 / Magic Firewall

🟥

Netflix

Netflix 使用 eBPF 进行深度性能分析(Brendan Gregg 是其首席性能工程师, 也是 bpftrace/BCC 的核心贡献者)。他们通过 eBPF 实时追踪 内存分配模式、CPU 调度延迟和网络栈瓶颈,支撑每秒数百万流的视频分发。

规模:万级实例 · 场景:性能剖析 / FlameScope / 延迟分析

🟪

Microsoft Azure

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 正在向哪些全新的领域扩张。

5
Chapter 5 · Industry Ecosystem

ECOSYSTEM — Industry Landscape & Pioneers

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.

5.1 Open Source Ecosystem

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

5.2 Isovalent: From Open Source to Enterprise Platform

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:

  1. Open Source Leadership: Create and maintain Cilium + Tetragon; contribute upstream kernel code
  2. Standard Setting: Drive CNCF graduation, Gateway API adoption, eBPF Foundation
  3. Lower Barriers: Declarative APIs replace hand-written eBPF; Helm/Operator one-click deployment
  4. Enterprise Platform: Isovalent Enterprise → Cisco Secure Connect with global support

Cisco Acquisition (2024): Strategic Significance

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.


5.3 Global Production Deployments

🟦 Google

GKE offers Cilium as default CNI option. Millions of nodes. Deep eBPF upstream contributions from Prodkernel team.

🟦 Meta

Katran XDP load balancer handles 10M+ connections/sec. Key contributor to BTF and CO-RE infrastructure.

🟧 Cloudflare

XDP-based DDoS protection across 300+ global PoPs. Withstands Tbps-scale attacks at the driver layer.

🟥 Netflix

Deep performance analysis with bpftrace/BCC. Brendan Gregg pioneered eBPF-based flame graphs here.

🟪 Microsoft Azure

Cilium as advanced CNI in AKS. Also leading eBPF for Windows, extending eBPF beyond Linux boundaries.

🟩 Others

Alibaba · ByteDance · Datadog · OpenAI: From internet giants to finance, telecom, and government sectors.

💡

Chapter 5 Summary

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.

6
Chapter 6 · 2026 Frontier

FRONTIER — 2026 技术前沿

eBPF 正在从其传统的"安全/网络/可观测性"三角地带, 向更底层、更广阔、更出人意料的方向扩张。 本章探讨三个正在重新定义 eBPF 边界的前沿领域—— 每一个都可能在未来两年内深刻改变我们构建和运维系统的方式。


6.1 Netkit:容器网络的零拷贝革命

在 Kubernetes 网络中,每个 Pod 都需要一个虚拟网络接口来与宿主机通信。 传统方案使用 veth pair(虚拟以太网对)—— 这是 Linux 内核中一种创建虚拟网络管道的经典机制。 但在高密度容器场景(单节点 100+ Pod)下,veth 的性能瓶颈日益凸显。

veth pair 的瓶颈根因

veth pair 的本质是一对互联的虚拟网卡——从一端写入的数据会从另一端出来。 问题在于,数据包在穿越 veth pair 时需要经历多次冗余处理

传统 veth pair vs Netkit:数据包路径对比 ❌ 传统 veth pair 📦 Pod eth0 TC egress 处理 veth 隧道传输 ⚠️ 完整 netdev_rx 重入 TC ingress 再次处理 ⚠️ 性能瓶颈 • skb 重新分配 • 完整网络栈重入 • TC hook 双重执行 • CPU 缓存被污染 延迟:~15-20μs per hop ✅ Netkit(Linux 6.7+) 📦 Pod netkit0 bpf_redirect_peer() 直接跳转 · 跳过 netdev_rx 🖥️ 宿主机 TC ✅ 性能优势 • 跳过完整 netdev_rx 路径 • 无 skb 重新分配 • TC hook 仅执行一次 • CPU 缓存友好 • 专为 BPF 设计的接口 延迟:~3-5μs per hop 🚀 📊 性能提升 吞吐提升 ~40% · 延迟降低 ~60% · CPU 开销降低 ~30%

图 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 接收层的冗余处理。
  • 天生为 BPF 而生: Netkit 接口只支持 BPF 程序作为数据路径处理器——不走传统的 qdisc/classifier 路径。 这意味着更简洁的代码路径、更少的锁竞争、更好的 CPU 缓存局部性。
  • 对 Cilium 的意义: Cilium 已在最新版本中实验性支持 Netkit 作为 veth 的替代。 在大规模集群中(500+ Pod/node),这可以带来显著的网络性能提升和 CPU 节省。
🔑

回答隐含问题:相比传统 veth,Netkit 有哪些性能优势?

① 吞吐量提升约 40%(消除了 netdev_rx 重入开销)。 ② 延迟降低约 60%(从 ~15μs 降至 ~5μs per hop)。 ③ CPU 开销降低约 30%(减少了 skb 操作和锁竞争)。 ④ 专为 BPF 优化的接口,避免传统 TC qdisc 的通用性开销。 这些改进在高密度容器场景(单节点 100+ Pod)下效果尤为显著。


6.2 sched_ext:用 eBPF 重写 CPU 调度器

如果说 XDP 让 eBPF 接管了网络数据路径, 那么 sched_ext(Linux 6.12+)则让 eBPF 触及了操作系统最核心的领域—— CPU 调度。这是一个足以改写操作系统教科书的突破。

传统调度器的局限

Linux 内核的默认调度器(CFS/EEVDF)是为通用负载设计的—— 它尝试在所有进程之间公平分配 CPU 时间。这对大多数场景足够好, 但在特定工作负载下会产生系统性的低效

🤖 AI/ML 训练

GPU 训练任务需要 CPU 在精确的时间窗口内准备好数据批次。 通用调度器不了解 GPU pipeline 的时序要求, 可能在关键时刻将 CPU 时间分配给无关进程—— 导致 GPU 空等(GPU starvation)

🎮 延迟敏感型

实时音视频、在线游戏服务器需要微秒级的调度响应。 CFS 的公平性算法在负载突增时可能产生 数毫秒的调度延迟尖峰——对这些场景是不可接受的。

☁️ 多租户隔离

云服务商需要确保租户 A 的突发负载不影响租户 B 的性能。 CFS 的 cgroup 带宽控制是粗粒度的, 无法根据实时负载动态调整策略。

sched_ext 的革命性设计

sched_ext(由 Meta 的 Tejun Heo 和 David Vernet 主导开发) 在调度器框架中引入了一组BPF 可编程的回调点

sched_ext:eBPF 可编程的 CPU 调度回调 Linux CPU Scheduler Core CFS / EEVDF 基础框架 ops.select_cpu() 选择目标 CPU 核心 ops.enqueue() 任务入队策略 ops.dispatch() 任务调度决策 ops.running() 任务开始/结束回调 🤖 AI 训练优化 GPU 数据准备优先级提升 ☁️ 多租户 QoS 动态带宽分配 · 实时调整 🎮 延迟优先调度 微秒级响应 · 尾延迟优化

图 6-2:sched_ext 架构 —— 在调度器核心框架中插入 BPF 可编程回调,实现自定义调度策略

sched_ext 的关键设计原则:

  • 安全回退:如果 BPF 调度器崩溃或行为异常,内核自动切换回默认的 CFS/EEVDF—— 系统不会因为实验性调度策略而宕机
  • 热加载:无需重启系统即可加载/卸载自定义调度器—— 你可以在生产环境中在线 A/B 测试不同的调度策略
  • 全功能 BPF:调度回调中可使用完整的 BPF Map 和 Helper 功能—— 调度决策可以参考实时的系统指标(CPU 温度、内存压力、GPU 队列深度……)
🧠

L2 深度 · 为什么 AI 训练需要自定义调度?

现代 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%—— 在动辄数百万美元的训练任务中,这是巨大的成本节省。


6.3 LLM 安全与可观测性:当 eBPF 遇上大模型

2024-2026 年,大语言模型(LLM)的部署规模爆炸式增长。 但 AI 推理服务的安全性和可观测性却远远落后于其功能发展—— 这为 eBPF 开辟了一个全新的应用领域。

TLS 明文解析:Uprobes 挂钩 SSL 库

LLM 推理服务几乎都通过 HTTPS/gRPC(TLS 加密)提供 API。 传统的网络监控工具只能看到加密后的密文—— 无法得知请求中包含了什么 prompt、返回了什么内容、 是否存在 prompt injection 攻击。

eBPF 通过 Uprobe 挂钩用户态 SSL 库(如 OpenSSL 的 SSL_writeSSL_read 函数),在加密之前和解密之后捕获原始明文数据。 这意味着安全团队可以:

  • 检测 Prompt Injection 攻击(恶意用户试图操纵模型行为)
  • 审计 PII(个人身份信息)泄露——模型是否在回答中泄露了敏感数据
  • 监控 模型幻觉(Hallucination)的发生频率和模式
  • 捕获完整的请求/响应对用于合规审计——无需修改推理服务代码

LLM 推理性能关键指标

eBPF 能够追踪 LLM 推理服务的两个最关键性能指标:

⏱️ TTFT — Time To First Token

从用户发送请求到第一个 Token 开始返回的时间。 这是衡量 LLM 响应速度的最核心指标——直接决定了用户感知到的"延迟"。

影响因素:模型大小、KV Cache 命中率、Prefill 阶段的计算量、GPU 队列深度。

典型值:优质服务 <500ms;用户可感知阈值 ~2s。

📊 ITL — Inter-Token Latency

相邻两个 Token 之间的生成间隔时间。 它决定了用户在 Streaming 模式下感知到的"流畅度"—— ITL 过高意味着文字输出卡顿。

影响因素:Decode 阶段效率、Batch Size、内存带宽。

典型值:优质服务 <50ms/token;流畅阈值 ~100ms/token。

⚠️

术语修正 · TTFT = "Time To First Token"

TTFT 的正确全称是 Time To First Token—— 这是 LLM 推理领域的标准术语,指从请求发出到首个 Token 返回的延迟。 请注意区分:它与网络领域的 TTFB(Time To First Byte)虽然概念类似, 但测量的粒度不同——TTFT 是应用语义级的(Token),TTFB 是传输级的(Byte)。

eBPF 如何追踪 TTFT 和 ITL?

通过 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); }
🧠

L2 深度 · 为什么不用应用层 SDK 代替 eBPF?

应用层 SDK(如 OpenTelemetry)可以追踪 TTFT 和 ITL,但有三个局限:

  • 侵入性:需要修改推理框架代码或添加中间件——在快速迭代的 LLM 栈中维护成本高
  • 盲区:SDK 只能看到应用层,看不到内核级的调度延迟、网络栈拥塞、NUMA 不对齐等底层因素
  • 全局性:eBPF 可以同时追踪推理框架 + SSL 库 + 内核网络栈 + CPU 调度,形成从应用到内核的完整因果链

例如,当 TTFT 突然飙升时,eBPF 可以同时告诉你: "Prefill 阶段的 CUDA kernel 等待了 800μs(Uprobe on CUDA runtime)+ 数据准备线程被调度延迟了 1.2ms(sched tracepoint)+ KV Cache 加载触发了 NUMA 远端内存访问(perf event)"—— 这种跨层关联分析是应用层 SDK 永远无法实现的。

💡

第六章小结

eBPF 的边界正在被重新定义:

  • Netkit 把 eBPF 推向了容器网络的数据路径核心——零拷贝、专用设计
  • sched_ext 把 eBPF 推向了 CPU 调度器——操作系统最神圣的领域
  • LLM 可观测性把 eBPF 推向了 AI 基础设施——追踪 Token 级别的性能指标

还有研究阶段的 gpu_ext(将 eBPF 扩展到 GPU 资源管理, 通过 SIMT 感知的验证器实现 GPU 上的安全可编程性)—— 这预示着 eBPF 的未来可能远超 Linux 内核本身的边界。

6
Chapter 6 · 2026 Frontier

FRONTIER — 2026 Technology Frontiers

eBPF is expanding from its traditional security/networking/observability triangle into deeper, broader, and more unexpected directions.


6.1 Netkit: Zero-Copy Container Networking

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).


6.2 sched_ext: Rewriting the CPU Scheduler with eBPF

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:

  • Safe fallback: If the BPF scheduler crashes, the kernel auto-reverts to CFS/EEVDF
  • Hot-loadable: Load/unload custom schedulers without rebooting — online A/B testing in production
  • Full BPF power: Scheduling decisions can reference real-time metrics via BPF Maps

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%.


6.3 LLM Security & Observability: eBPF Meets Large Language Models

TLS Plaintext Capture

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.

Key LLM Performance Metrics

⏱️ TTFT — Time To First Token

Time from request submission to first token returned. The primary metric for perceived LLM latency. Target: <500ms for quality service; user perception threshold ~2s.

📊 ITL — Inter-Token Latency

Time between consecutive tokens in streaming mode. Determines perceived "fluency." Target: <50ms/token; fluency threshold ~100ms/token.

⚠️

Terminology Note

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).

💡

Chapter 6 Summary

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.

Finale · Call to Action

终章:从理解到行动

你已经走完了一段不平凡的旅程。

从第一性原理出发,你理解了为什么操作系统内核需要一种安全的可编程机制; 你掌握了 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 超能力转化为可落地的企业级方案的必经之路。

🐝 eBPF 三部曲导航

🐝 当前阅读

Part 1 · eBPF

Linux 内核的超能力——完整生命周期、核心原理、三大价值支柱、2026 前沿。

✅ 你在这里

🔗 即将发布

Part 2 · Cilium

云原生网络与安全——身份感知策略、替代 kube-proxy、ClusterMesh 多集群、Gateway API、Sidecar-less 服务网格。

🔜 Coming Soon

🛡️ 规划中

Part 3 · Tetragon

云原生安全的最后一道防线——TracingPolicy、内核级 SIGKILL、进程族谱追踪、运行时安全策略引擎。

📋 Planned


❓ 常见问题(FAQ)

Q1: eBPF 程序会不会导致 Kernel Panic?
不会。这是 eBPF 与传统内核模块的根本区别。 Verifier 在程序加载到内核之前就通过穷举路径分析, 数学证明程序不会崩溃、不会死循环、不会越界访问内存。 通不过验证的程序永远无法进入内核。 即使程序中存在逻辑错误(如错误地丢弃了正常数据包), 它也不会导致内核崩溃——最多影响该 eBPF 程序的功能正确性。
Q2: eBPF 需要修改应用代码吗?
完全不需要。eBPF 程序在内核空间运行,对应用层完全透明。 无论是网络策略(Cilium)、安全监控(Tetragon)还是性能分析(bpftrace), 都不需要修改、重编译或重启目标应用。 甚至 TLS 加密流量的明文捕获(通过 Uprobe 挂钩 SSL 库), 也不需要应用做任何配合。
Q3: 运行 eBPF 需要什么内核版本?
eBPF 基础能力从 Linux 3.18(2014)开始可用, 但完整特性集(BTF、CO-RE、Ring Buffer、LSM-BPF 等)建议 Linux 5.8+。 Cilium 最低支持 Linux 4.19(长期支持版),推荐 5.10+。 前沿功能如 Netkit 需要 6.7+,sched_ext 需要 6.12+。

建议:生产环境使用 Linux 5.15 LTS 或 6.1 LTS 作为基线。
Q4: eBPF 的性能开销到底有多大?
取决于场景,但通常极低
  • 网络处理(XDP/TC):<1% CPU 开销,因为程序直接在内核数据路径中以 JIT 编译后的原生代码运行
  • 追踪/可观测性:1-2% CPU 开销(持续剖析场景)
  • 安全监控:<2% CPU 开销(Tetragon 在大规模测试中的实测值)
与传统的 Sidecar Proxy 方案(10-30% CPU + 100-200MB 内存/Pod)相比, eBPF 的资源效率高出一到两个数量级。
Q5: eBPF 能否在 Windows 上运行?
是的(有限支持)。Microsoft 正在开发 eBPF for Windows 项目, 旨在将 eBPF 的可编程性带入 Windows 操作系统。 该项目使用现有的 eBPF 工具链(Clang/LLVM)和开源 Verifier(uBPF/PREVAIL), 在 Windows 内核的网络过滤层(WFP)和安全层上提供 Hook 点。 虽然还在早期阶段,但它标志着 eBPF 正在成为跨操作系统的标准。
Q6: 我应该直接学 eBPF 还是直接用 Cilium?
取决于你的角色。
  • 平台工程师 / SRE:直接使用 Cilium/Tetragon。你不需要写 eBPF 代码, 只需要理解它的原理(本文已覆盖)和操作方式(Part 2/3 将覆盖)。
  • 安全研究员 / 内核开发者:深入学习 eBPF 编程。推荐从 libbpf + CO-RE 开始, 辅以 Liz Rice 的《Learning eBPF》和 Brendan Gregg 的 bpftrace 实践。
  • 技术决策者:理解 eBPF 的价值和边界(本文已覆盖), 评估 Cilium/Tetragon 在你的技术栈中的定位。

📖 术语表

术语 全称 简要说明
eBPFextended Berkeley Packet FilterLinux 内核中的通用事件驱动运行时执行引擎
cBPFclassic Berkeley Packet Filter1992 年的原始 BPF,仅用于包过滤,2 个 32-bit 寄存器
BTFBPF Type Format极精简的内核类型信息格式(~DWARF 的 1/100 体积)
CO-RECompile Once – Run EverywhereeBPF 跨内核版本可移植性框架
JITJust-In-Time Compilation将 BPF 字节码编译为原生机器指令
XDPeXpress Data Path网卡驱动层 eBPF Hook,最早的数据包拦截点
TCTraffic Control流量控制层 eBPF Hook,支持入/出站处理
LSMLinux Security Module安全模块 Hook 点,用于策略执行(Kernel 5.7+)
IoCInversion of Control控制反转——框架调用用户代码,而非反过来
TTFTTime To First TokenLLM 推理中从请求到第一个 Token 返回的延迟
ITLInter-Token LatencyLLM 推理中相邻 Token 之间的生成间隔
CNIContainer Network InterfaceKubernetes 容器网络接口标准
MTTRMean Time To Recovery平均修复时间
KfuncKernel Function (for BPF)Linux 6.x+ 的新一代内核 API 暴露机制
sched_extExtensible Scheduler ClassBPF 可编程的 CPU 调度框架(Kernel 6.12+)
Netkit专为 BPF 设计的容器虚拟网络接口(Kernel 6.7+)

📚 参考资料

  1. Liz Rice. Learning eBPF: Programming the Linux Kernel for Enhanced Observability, Networking, and Security. O'Reilly Media, 2023.
  2. D. Thaler, et al. BPF Instruction Set Architecture (ISA). IETF RFC 9669, 2024.
  3. S. McCanne and V. Jacobson. The BSD Packet Filter: A New Architecture for User-level Packet Capture. USENIX Winter 1993.
  4. A. Starovoitov. BPF – in-kernel virtual machine. netdev 0.1, 2015.
  5. D. Borkmann, et al. Netkit: BPF-programmable network devices. Linux Kernel 6.7, 2024.
  6. T. Heo, D. Vernet. sched_ext: BPF-extensible scheduler class. Linux Kernel 6.12, 2024.
  7. Y. Zhao, et al. gpu_ext: Extensible OS Policies for GPUs via eBPF. arXiv:2512.12615v2, 2025.
  8. P. Murali, et al. An In-Depth Analysis of eBPF-Based System Security Tools in Cloud-Native Environments. 2024.
  9. Isovalent. State of Kubernetes Networking Report — 2025. Cisco/Isovalent, 2025.
  10. bpf(2) — Linux manual page. Linux Programmer's Manual.
  11. Brendan Gregg. BPF Performance Tools. Addison-Wesley, 2019.
  12. eBPF Foundation. ebpf.io — The official eBPF documentation site. Linux Foundation.

本文由 Rao Weibo 借助 Claude Opus 完成

发布日期:2025-12-20  |  最后更新:2026-04-08

Finale · Call to Action

Finale: From Understanding to Action

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.

🐝 eBPF Trilogy Navigation

Part 1 · eBPF

The superpower of Linux Kernel — complete lifecycle, core principles, three pillars, 2026 frontier.
✅ You are here

Part 2 · Cilium

Cloud-native networking & security — identity-aware policies, kube-proxy replacement, ClusterMesh. 🔜

Part 3 · Tetragon

The last line of cloud-native defense — TracingPolicy, in-kernel SIGKILL, process ancestry. 📋


❓ FAQ

Q1: Can eBPF cause a Kernel Panic?
No. The Verifier mathematically proves safety before any program enters the kernel. Even buggy logic won't crash the kernel — it only affects the eBPF program's own functionality.
Q2: Does eBPF require application code changes?
No. eBPF runs in kernel space, completely transparent to applications.
Q3: What kernel version is required?
Basic eBPF: Linux 3.18+. Full features (BTF, CO-RE, Ring Buffer): Linux 5.8+. Recommended baseline: Linux 5.15 LTS or 6.1 LTS.
Q4: What's the performance overhead?
Typically <2% CPU. XDP/TC networking: <1%. Tracing: 1-2%. Security monitoring: <2%. Orders of magnitude more efficient than Sidecar Proxy approaches.

📖 Glossary

TermFull NameDescription
eBPFextended Berkeley Packet FilterGeneral-purpose event-driven execution engine in the Linux kernel
BTFBPF Type FormatCompact kernel type info (~1/100th of DWARF)
CO-RECompile Once – Run EverywhereCross-kernel portability framework
XDPeXpress Data PathNIC driver-layer hook, earliest packet interception point
TTFTTime To First TokenLLM latency from request to first token returned
ITLInter-Token LatencyTime between consecutive tokens in LLM streaming

📚 References

  1. Liz Rice. Learning eBPF. O'Reilly Media, 2023.
  2. IETF RFC 9669. BPF Instruction Set Architecture. 2024.
  3. S. McCanne, V. Jacobson. The BSD Packet Filter. USENIX 1993.
  4. Isovalent. State of Kubernetes Networking Report — 2025.
  5. eBPF Foundation. ebpf.io. Linux Foundation.

Written by Rao Weibo with Claude Opus

Published: 2025-12-20  |  Last Updated: 2026-04-08