eBPF 的指令集、verifier、maps 和 helper 概念是通用的——它们可以在任何操作系统上实现,甚至可以在用户态进程中实现。这不是理论推演。微软在 2021 年开源了 ebpf-for-windows,将 BPF 字节码挂载到了 Windows 内核的 NetBufferList 和 System Call 钩子上。IO Visor 社区的 ubpf 提供了用户态 BPF VM,rbpf 将 BPF VM 带到了 Rust 生态。IETF 正在推动 BPF ISA 成为正式国际标准。
本文从跨平台视角审视 eBPF 的技术扩散:四种非 Linux 的 BPF 运行时实现、它们的技术取舍、以及标准化进程对 “eBPF = Linux 特性” 这一认知的挑战。
一、为什么 eBPF 可以跨平台
eBPF 的跨平台潜力来自它设计上的三个分离:
- ISA 独立于 OS:eBPF 指令集(BPF ISA)定义了完整的虚拟机规范——寄存器、指令编码、语义——不依赖 Linux 内核的任何头文件或数据结构。
- verifier 逻辑是算法级的:抽象解释和 DFA 遍历不依赖 Linux 内核的具体 API,可以用任何形式化验证技术替换。
- maps 和 helpers 是接口抽象:它们定义了 “BPF 程序和外部世界交互” 的接口——OS 可以按自己的需求实现完全不同的一组 maps 和 helpers。
flowchart TD
subgraph COMMON["平台无关层"]
ISA["BPF ISA<br/>(指令集规范)"]
VER["verifier 算法<br/>(抽象解释)"]
MAPS["map 抽象<br/>(key-value store)"]
end
subgraph LINUX["Linux 实现"]
LJIT["x86/ARM64 JIT"]
LH["kernel helpers"]
LM["hash/array/ringbuf maps"]
end
subgraph WINDOWS["Windows 实现"]
WJIT["x86-64 JIT"]
WH["NetBufferList hooks"]
WM["兼容 map 类型"]
end
subgraph USER["用户态实现"]
UJIT["ubpf JIT"]
UH["自定义 hooks"]
UM["mock maps"]
end
ISA --> LJIT
ISA --> WJIT
ISA --> UJIT
VER --> LINUX
VER --> WINDOWS
VER --> USER
MAPS --> LM
MAPS --> WM
MAPS --> UM
二、Windows eBPF (ebpf-for-windows)
2.1 架构概述
ebpf-for-windows(github.com/microsoft/ebpf-for-windows)是微软在 2021 年开源的 Windows eBPF 实现。它的架构与 Linux eBPF 结构相似,但实现独立。
架构分为两层。用户态层:ebpf_api.dll(libbpf
等价物)和 netsh ebpf(CLI
管理工具)。内核态层:eBPF Core 执行引擎(包含 PREVAIL
verifier、x86-64 JIT、hash/array maps),以及两个 hook
扩展——NetBufferList(XDP 等价)和 System Call hooks(kprobe
等价)。
关键设计决策:
- 独立的 verifier:Windows 使用 PREVAIL verifier(Polynomial-Range Enhanced Verification via Abstract Interpretation of Logical formulas),而不是 Linux 的 verifier。PREVAIL 使用更形式化的抽象解释方法。
- 相同的 BPF ISA:Windows 执行的是标准
BPF 字节码——Linux 上编译的
.o文件(经过一些调整)可以在 Windows 上加载。 - 不同的 helper 集合:Windows 的 hooks 是 Windows 特有的(NetBufferList、System Call),所以 helper 函数映射到 Windows 内核 API,而非 Linux 内核 API。
2.2 Hook 点
Windows eBPF 目前支持两种 hook:
NetBufferList hook(网络包过滤,类似 Linux XDP + TC):
/* Windows eBPF: 绑定到 NetBufferList hook */
/* 程序类型: BPF_PROG_TYPE_XDP (兼容 Linux 的 SEC 标签) */
SEC("xdp")
int filter_packet(struct xdp_md *ctx)
{
/* 检查包数据 */
void *data = (void *)(long)ctx->data;
void *data_end = (void *)(long)ctx->data_end;
/* ... 包过滤逻辑 ... */
return XDP_PASS; /* XDP_DROP, XDP_TX 也支持 */
}System Call hook 允许在系统调用前后执行 BPF 程序(类似 Linux kprobe on syscalls):
SEC("bind")
int bind_hook(bind_md_t *ctx)
{
/* 拦截 bind() 调用 */
return BIND_PERMIT;
}2.3 技术限制
与 Linux eBPF 相比,Windows eBPF 有以下限制:
| 维度 | Linux eBPF | Windows eBPF |
|---|---|---|
| hook 类型 | 30+ 程序类型 | 2 类(NetBufferList + Syscall) |
| verifier | ~10K 行 C (内核) | PREVAIL(独立实现) |
| 地图共享 | bpffs pinning | 有限支持 |
| CO-RE | BTF 驱动 | 不支持 |
| tracing | kprobe/tracepoint/fentry | 仅 syscall-level |
| 生产就绪度 | 生产(10+ 年) | 实验/早期采用 |
| JIT 后端 | x86/ARM64/RISC-V/MIPS/PowerPC/s390 | x86-64 |
| 网络性能 | 线速量级(XDP 原始模式,依硬件与驱动) | 受限于 NDIS 框架,未见公开对标基准 |
2.4 使用 ebpf-for-windows
# Windows 上安装 eBPF
msiexec /i ebpf-for-windows.msi
# 使用 netsh 管理 eBPF 程序
netsh ebpf show programs
netsh ebpf add xdp port=443 program=my_filter.o
# 加载 BPF 程序并挂载
.\bpftool.exe prog load my_prog.o三、ubpf:用户态 BPF VM
3.1 架构与设计
ubpf(github.com/iovisor/ubpf)是 IO Visor 社区的用户态 BPF VM 实现。它的核心目标是在没有内核支持的环境中运行 BPF 程序:
/* ubpf 的核心 API */
struct ubpf_vm *ubpf_create(void);
int ubpf_load(struct ubpf_vm *vm, const void *code, uint32_t code_len,
char **errmsg);
uint64_t ubpf_exec(const struct ubpf_vm *vm, void *mem, size_t mem_len);
void ubpf_destroy(struct ubpf_vm *vm);ubpf 支持两种执行模式:
解释器模式:逐条解码并执行 BPF 指令。完全可移植(纯 C 实现),但执行速度显著慢于 JIT(社区经验约 10–50x,依程序而定,非本站实测)。
/* ubpf 解释器的主循环(简化)*/
uint64_t ubpf_exec(const struct ubpf_vm *vm, void *mem, size_t mem_len)
{
uint64_t reg[16];
uint16_t pc = 0;
while (1) {
struct ebpf_inst inst = vm->insts[pc++];
switch (inst.opcode) {
case EBPF_OP_ADD_IMM:
reg[inst.dst] += inst.imm;
break;
case EBPF_OP_MOV_REG:
reg[inst.dst] = reg[inst.src];
break;
case EBPF_OP_EXIT:
return reg[0];
/* ... 其他指令 ... */
}
}
}JIT 模式:将 BPF 字节码编译为本机代码(x86-64 和 ARM64)。与内核 JIT 类似,但由用户态管理内存:
/* 启用 JIT 编译 */
ubpf_jit_fn fn = ubpf_compile(vm, &err);
if (fn) {
/* 直接调用 JIT 编译后的函数 */
uint64_t result = fn(mem, mem_len);
}3.2 典型用例
BPF 程序测试和开发:在用户态测试 BPF 程序逻辑,不需要加载到内核。这对于 verifier 调试尤其有用——可以先确保程序正确,再面对 verifier。
嵌入式 BPF:在用户态应用中嵌入 BPF 虚拟机作为脚本引擎。应用提供 “helpers” 作为外部函数注册到 VM:
/* 向 ubpf VM 注册外部函数 */
void my_helper(struct ubpf_vm *vm, uint64_t regs[16]) {
/* 实现 helper 逻辑 */
}
ubpf_register(vm, 1, "my_helper", my_helper);教育:ubpf 是学习 BPF ISA 的最佳工具——可以在打印每条指令执行结果的调试模式下运行,清晰展示虚拟机的内部状态。
3.3 限制
- 无 verifier:ubpf 信任传入的字节码,不做安全检查
- 无 maps:除非用户自己实现 map 语义并注册为外部函数
- 无内核 helpers:所有外部交互必须通过用户注册的回调模拟
四、rbpf:Rust 中的 BPF VM
4.1 架构
rbpf(github.com/qmonnet/rbpf)是 BPF 虚拟机的 Rust 实现。它提供了解释器(无 JIT)和完整的 BPF 指令汇编/反汇编支持:
// rbpf 的核心 API
use rbpf::EbpfVmFixedMbpf;
let prog = &[
0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r0, 0
// ... 更多指令
];
let vm = EbpfVmFixedMbpf::new(Some(prog)).unwrap();
let result = vm.execute_program().unwrap();4.2 组件
rbpf 提供了三个主要功能模块:
指令解码器:将原始字节解码为结构化的 BPF 指令:
use rbpf::disassembler;
let insn = disassembler::disassemble(&raw_bytes[0..8]);
// insn.opc = 0xb7 (MOV64_IMM)
// insn.dst = 0 (R0)
// insn.imm = 0解释器:执行 BPF 字节码,验证每条指令的有效性:
use rbpf::EbpfVmNoData;
let vm = EbpfVmNoData::new(Some(prog)).unwrap();
let output = vm.execute_program().unwrap();
assert_eq!(output, 0);汇编宏:用 Rust 宏编写 BPF 程序(可用于生成嵌入式 BPF 指令):
use rbpf::assembler::assemble;
let code = assemble("
mov r0, 0
mov r1, 42
add r0, r1
exit
").unwrap();4.3 Rust BPF 生态中的位置
rbpf 是 Rust BPF 生态系统的一部分:
| 项目 | 角色 |
|---|---|
| aya | Rust eBPF 库——类似 libbpf(加载/管理 BPF 程序) |
| aya-ebpf | Rust BPF 程序编写框架(编译为 BPF 字节码) |
| rbpf | BPF VM 解释器 + 反汇编(用户态,用于测试/嵌入) |
| libbpf-rs | libbpf 的 Rust 绑定 |
| bpfd | Rust BPF 守护进程(管理 BPF 程序生命周期) |
五、IETF BPF ISA 标准化
5.1 draft-ietf-bpf-isa
IETF 的 BPF ISA
草案(draft-ietf-bpf-isa)旨在将 BPF
指令集标准化为与具体平台无关的国际标准。这在 eBPF
历史上是一个关键转折——从 “Linux 内核的一个子系统” 到
“通用指令集架构”。
草案定义的内容:
- 指令编码格式:64-bit 指令的精确 bit layout
- 寄存器模型:11 个寄存器的 role 和调用约定
- 指令语义:每类指令的操作语义(形式化描述)
- 一致性组(conformance groups):定义了实现可以选择支持的子集(base, atomic32, atomic64 等)
draft-ietf-bpf-isa 定义的一致性组:
base: ALU64, ALU32, JMP, LD, ST, LDX, STX, CALL, EXIT
alu32: full 32-bit ALU operations
atomic32: 32-bit atomic operations
atomic64: 64-bit atomic operations
bswap16: 16-bit byte swap
bswap32: 32-bit byte swap
bswap64: 64-bit byte swap
sd_div: signed division
mod: modulo operation
neg: negation
sdiv: signed division (32-bit)
smod: signed modulo (32-bit)
...
5.2 一致性测试套件
与 ISA 草案配套的是 bpf_conformance
测试套件——任何声称符合 BPF ISA
的实现可以运行它来证明一致性:
# 运行一致性测试
git clone https://github.com/Alan-Jowett/bpf_conformance
cd bpf_conformance
cmake . && make
./bpf_conformance_runner --test_file tests/bpf_conformance/tests.json \
--plugin_path libubpf_plugin.so这为 “跨平台 BPF” 提供了技术基础——如果 Windows eBPF、ubpf、rbpf 都通过同样的测试套件,那么为 Linux 编译的 BPF 字节码确实可以在这些平台上运行。
5.3 标准化的影响
| 维度 | 当前 | 标准化后 |
|---|---|---|
| BPF ISA | Linux 实现即为事实标准 | IETF RFC 为规范标准 |
| 互操作性 | 各平台自行适配 | 一致性测试证明互操作性 |
| 演进控制 | Linux 社区决定 | IETF 流程决定 |
| 实现多样性 | Linux 为主 | 多个独立实现竞争/互补 |
| 验证工具 | Linux verifier | 可独立于 Linux 的验证工具 |
六、对比总览
下表性能列为定性对比或引用社区/文档描述,非本站基准测试。Windows eBPF 与 rbpf 的 API 随上游版本演进较快,使用前请对照对应仓库 release tag 核对接口。
| 维度 | Linux eBPF | Windows eBPF | ubpf | rbpf |
|---|---|---|---|---|
| 运行位置 | 内核态 | 内核态 | 用户态 | 用户态 |
| 语言 | C | C | C | Rust |
| ISA 合规 | 完整 | base + atomic | 基本完整 | 部分 |
| verifier | 有(内核) | 有(PREVAIL) | 无 | 无 |
| JIT 后端 | 6+ 架构 | x86-64 | x86-64, ARM64 | 无 |
| 解释器 | 有(回退) | 有 | 有 | 有 |
| maps | 丰富(10+ 类型) | hash, array | 无(需自定义) | 无(需自定义) |
| helpers | 丰富(按程序类型) | Windows 特定 | 无(需注册) | 无(需注册) |
| CO-RE | BTF 驱动 | 不支持 | 不适用 | 不适用 |
| 性能 | 接近内核代码(定性) | 未见公开对标数据 | 解释器模式显著慢于 JIT(社区经验约 10–50x,非本站实测) | 解释器,慢于 JIT(定性) |
| 生态成熟度 | 生产 | 早期采用 | 测试/教育 | 测试/嵌入 |
七、选择指南
需要使用 BPF 但目标不是 Linux: - Windows → ebpf-for-windows(网络和 syscall 过滤) - 嵌入式/用户态 → ubpf JIT 模式 + 自定义 helpers - Rust 生态 → rbpf (用于测试) + aya (用于加载到内核)
需要 BPF VM 作为应用程序的嵌入式脚本引擎: - 纯 C/高性能 → ubpf JIT 模式 - Rust 项目 → rbpf - 需要 verifier → 无现成方案(考虑限制脚本能力)
实验 BPF ISA 新特性: - 最灵活:ubpf(C 实现,易于修改和扩展) - 最接近标准:Linux eBPF(事实标准)
八、总结
eBPF 从 Linux 内核的一个子系统,正在演变为跨平台的通用虚拟机标准。三个趋势推动着这一演变:
- Windows eBPF 证明了 BPF ISA 可以在完全不同的操作系统内核中运行,使用独立的 verifier 实现
- ubpf/rbpf 展示了 BPF 在用户态的应用——作为嵌入式脚本引擎或测试工具
- IETF 标准化 将 BPF ISA 从 “Linux 的实现细节” 提升为 “正式的国际标准”
这些发展的核心共识是:BPF ISA 的价值超越了 Linux——它是一个经过验证的、安全的、可静态分析的指令集架构,适合任何需要 “受限安全计算” 的场景。
本系列中,本文与 第 15 篇(蹦床与 fentry/fexit) 和 第 20 篇(构建微型 eBPF Agent) 形成互补——前者是 Linux 内核特性的深度拆解,后者是 Linux 平台的实践教程,而本文提供了 “离开 Linux” 的视角。
参考资料
- ebpf-for-windows:微软 Windows eBPF 实现
- ubpf:IO Visor 用户态 BPF VM
- rbpf:Rust BPF VM
- draft-ietf-bpf-isa:IETF BPF ISA 标准化草案
- bpf_conformance:BPF 一致性测试套件
- aya:Rust eBPF 库
- eBPF Foundation 标准化文档
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【eBPF 内核实现深度拆解】BPF 指令集解码:寄存器机器、调用约定与指令编码
从 eBPF 虚拟机的 11 个 64-bit 寄存器和 struct bpf_insn 出发,逐条拆解 ALU64/ALU32、跳转、加载存储、call 四类指令的字段语义与编码格式,建立后续 verifier 和 JIT 讨论的精确基础。
【eBPF 内核实现深度拆解】验证器框架:从 BPF_PROG_LOAD 到 do_check()
跟踪 BPF_PROG_LOAD 系统调用的内核执行路径,逐层拆解 bpf_prog_load()→bpf_check()→do_check_main() 的调用链,建立 verifier 执行全景——这是理解 verifier 安全保证的入口。
【eBPF 内核实现深度拆解】JIT 编译器后端:x86-64 与 ARM64 的 BPF→Native 翻译管线
从 bpf_jit_compile() 入口出发,拆解 BPF 字节码到 x86-64/ARM64 本地指令的翻译过程——寄存器映射策略、ALU 指令的 one-to-one/many-to-one 翻译、尾调用与 call 的本地实现、JIT 镜像的 kallsyms 集成,以及 JIT 与 interpreter 的性能边界。
【eBPF 内核实现深度拆解】Map 内核实现(上):hash / array / per-CPU 的数据结构与并发模型
从 bpf_map_ops 虚函数表出发,逐层拆解 BPF_MAP_TYPE_HASH、BPF_MAP_TYPE_ARRAY、per-CPU 变体的内核实现——htab 的 bucket 链表与 prealloc、bpf_array 的零拷贝共享、per-CPU 分配器的无锁语义。