土法炼钢兴趣小组的算法知识备份

【eBPF 内核实现深度拆解】非 Linux eBPF:Windows eBPF 平台、ubpf 与 rbpf 用户态运行时

文章导航

分类入口
kernelebpf
标签入口
#ebpf#windows-ebpf#ubpf#rbpf#bpf-isa#ietf#cross-platform#linux-kernel

目录

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 的跨平台潜力来自它设计上的三个分离:

  1. ISA 独立于 OS:eBPF 指令集(BPF ISA)定义了完整的虚拟机规范——寄存器、指令编码、语义——不依赖 Linux 内核的任何头文件或数据结构。
  2. verifier 逻辑是算法级的:抽象解释和 DFA 遍历不依赖 Linux 内核的具体 API,可以用任何形式化验证技术替换。
  3. 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 等价)。

关键设计决策:

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 限制

四、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 内核的一个子系统” 到 “通用指令集架构”。

草案定义的内容:

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 内核的一个子系统,正在演变为跨平台的通用虚拟机标准。三个趋势推动着这一演变:

  1. Windows eBPF 证明了 BPF ISA 可以在完全不同的操作系统内核中运行,使用独立的 verifier 实现
  2. ubpf/rbpf 展示了 BPF 在用户态的应用——作为嵌入式脚本引擎或测试工具
  3. IETF 标准化 将 BPF ISA 从 “Linux 的实现细节” 提升为 “正式的国际标准”

这些发展的核心共识是:BPF ISA 的价值超越了 Linux——它是一个经过验证的、安全的、可静态分析的指令集架构,适合任何需要 “受限安全计算” 的场景。

本系列中,本文与 第 15 篇(蹦床与 fentry/fexit)第 20 篇(构建微型 eBPF Agent) 形成互补——前者是 Linux 内核特性的深度拆解,后者是 Linux 平台的实践教程,而本文提供了 “离开 Linux” 的视角。

参考资料

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。


By .