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

【eBPF 内核实现深度拆解】JIT 编译器后端:x86-64 与 ARM64 的 BPF→Native 翻译管线

文章导航

分类入口
kernelebpf
标签入口
#ebpf#bpf-jit#x86-64#arm64#jit-compiler#bpf_prog#linux-kernel

目录

bpftool prog dump jited id 42 输出了一段 x86-64 汇编。raxrdirsi 的名字你全认识,但有一个关键问题直接决定了你是否理解 JIT 的效率边界:为什么 BPF 的 BPF_ALU32 加法在 x86-64 上往往只需一条 addl,而在 ARM64 上却可能需要额外的 uxtw 来清零高 32 位?为什么 JIT 不能总是生成最优本地码?

本文拆解 BPF JIT 编译器的完整翻译管线:从 bpf_jit_compile() 的架构调度入口,到 x86-64 和 ARM64 两个后端的寄存器映射、ALU 翻译策略、分支和调用实现、尾调用机制,再到 JIT 镜像的 kallsyms 暴露和 bpf_jit_limit 的资源限制。读完后,你理解 JIT 编译不仅是一个”字节码→本地码”的简单转换,而是一个受限于 BPF ISA 表达力的有损翻译过程。

一、JIT 解决的问题和入口

1.1 Interpreter vs JIT 的性能差异

当 BPF 程序被 verifier 接受但 JIT 未启用时,内核使用 BPF 解释器(interpreter)来执行程序:

/* Linux 6.6: kernel/bpf/core.c — BPF 解释器的核心循环 */
static u64 ___bpf_prog_run(u64 *regs, const struct bpf_insn *insn)
{
    u64 stack[MAX_BPF_STACK / sizeof(u64)];
    ...
    select_insn:
        goto *jumptable[insn->code];
    ...
}

解释器的核心是一个间接跳转表——每条 BPF 指令通过 jumptable 分发到对应的 handler。每条指令至少涉及:两次内存加载(取指令 + 解引用 jumptable),一次间接跳转,以及实际的计算逻辑。在现代超标量处理器上,间接跳转会破坏分支预测,导致流水线停顿。

典型性能差异(定性描述,具体倍率依 CPU 与程序形态而定,需在本机 benchmark 验证):

场景 Interpreter 特点 JIT 特点
简单 ALU(BPF_MOV) 每条指令经跳转表分发,间接跳转多 通常可一对一映射为单条本地 mov
BPF_LD_IMM64 双宽指令 + 分发开销 x86-64 上常见为 movabsq 等少量指令
密集算术循环 每步都有分发与取指开销 本地码可合并、寄存器分配更灵活
helper 调用 包装层 + 分发 仍有序言与寄存器保存,但无解释器循环

平均而言,JIT 相对解释器通常快数倍到一个数量级;对于网络数据面(XDP、TC),生产环境普遍启用 JIT——interpreter 难以支撑高吞吐线速处理。

1.2 JIT 的架构调度入口

bpf_jit_compile() 是 JIT 编译的架构无关入口:

/* Linux 6.6: kernel/bpf/core.c */
struct bpf_prog *bpf_jit_compile(struct bpf_prog *prog)
{
    /* 1. 检查是否启用 JIT */
    if (!bpf_jit_enable)
        return prog;

    /* 2. 检查是否有对应架构的 JIT 编译器 */
    if (!bpf_jit_compiler_ops[prog->jit_arch])
        return prog;

    /* 3. 调用架构特定的 JIT 编译函数 */
    prog = bpf_int_jit_compile(prog);

    /* 4. 如果 JIT 失败,保留为 interpreter 模式 */
    return prog;
}

bpf_int_jit_compile() 是架构特定的函数指针。对于 x86-64,这个函数定义在 arch/x86/net/bpf_jit_comp.c;对于 ARM64,定义在 arch/arm64/net/bpf_jit_comp.c

/* Linux 6.6: arch/x86/net/bpf_jit_comp.c — x86-64 入口 */
struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog)
{
    struct bpf_binary_header *header = NULL;
    struct bpf_prog *tmp, *orig_prog = prog;
    int proglen, pass;
    u8 *image = NULL;
    
    /* 第 1 pass:计算镜像大小 */
    /* 第 2 pass:生成实际机器码 */
    ...
}

JIT 使用两轮编译(two-pass)策略:第一轮计算需要的镜像大小(因为 x86-64 的每条 BPF 指令可能生成 1-20 字节不等的本地码),第二轮填充实际的机器码字节。

二、x86-64 JIT 后端

2.1 寄存器映射

x86-64 JIT 的寄存器映射遵循 System V AMD64 ABI 的调用约定:

BPF 寄存器 x86-64 寄存器 选择理由
R0 (返回值) rax x86-64 ABI 返回值寄存器
R1 (arg0) rdi x86-64 ABI 第 1 参数
R2 (arg1) rsi ABI 第 2 参数
R3 (arg2) rdx ABI 第 3 参数
R4 (arg3) rcx ABI 第 4 参数
R5 (arg4) r8 ABI 第 5 参数
R6 (callee-saved) rbx callee-saved in ABI
R7 (callee-saved) r13 callee-saved (low regs avoid saving)
R8 (callee-saved) r14 callee-saved
R9 (callee-saved) r15 callee-saved
R10 (frame) rbp x86-64 frame pointer

寄存器映射的实现见 arch/x86/net/bpf_jit_comp.c 中的 bpf2reg[] 数组(将 BPF 寄存器编号映射到 x86 寄存器编码)。上表即为 R0–R10 到 x86-64 寄存器的对应关系,无需再引用源码中的编码细节。

x86-64 JIT 的映射有以下几个工程考量:

  1. R1-R5 直接映射到 ABI 参数寄存器:这使得从 JIT 代码调用 helper 函数时不需要寄存器的移动——BPF R1 就是 rdi,而 rdi 就是 C ABI 的 arg0。调用 helper 直接就是 call helper_func

  2. R6-R9 使用 callee-saved 寄存器rbxr13r14r15 在 x86-64 ABI 中是 callee-saved 的。这意味着 JIT 生成的代码在调用 helper 时不需要显式保存这些寄存器——C 调用约定已经保证 helper 不会修改它们。如果选择了 caller-saved 寄存器(如 r10r11),每次 helper 调用都需要额外的 push/pop

  3. R10 是 rbp:BPF 的帧指针映射到 x86-64 的基址指针。这允许使用 x86-64 的自然 [rbp - offset] 寻址模式。

2.2 ALU 指令翻译

ALU64(64-bit 操作)——one-to-one 翻译

BPF 指令 x86-64 指令 编码示例(BPF R0 += BPF R1)
BPF_ADD addq %r1, %r0 48 01 c8
BPF_SUB subq %r1, %r0 48 29 c8
BPF_MUL imulq %r1, %r0 48 0f af c1
BPF_DIV divq %r1 (需先 xor %edx,%edx) 48 f7 f1
BPF_OR orq %r1, %r0 48 09 c8
BPF_AND andq %r1, %r0 48 21 c8
BPF_LSH shlq %cl, %r0 (移位量必须先存到 %cl) 48 d3 e0
BPF_RSH shrq %cl, %r0 48 d3 e8
BPF_ARSH sarq %cl, %r0 48 d3 f8
BPF_NEG negq %r0 48 f7 d8
BPF_XOR xorq %r1, %r0 48 31 c8
BPF_MOV movq %r1, %r0 48 89 c8

立即数变体(BPF_K 而非 BPF_X)使用 x86-64 的立即数指令编码(addq $imm, %r0)。

ALU32(32-bit 操作)——利用 x86-64 零扩展特性

x86-64 的一个关键特性是:任何 32-bit 寄存器操作会自动将高 32-bit 清零。例如 xor %eax, %eax 清零了整个 64-bit rax。JIT 利用这一特性实现 BPF_ALU32:

/* BPF_ALU32: dst = (u32)dst OP (u32)src */
/* x86-64 JIT 翻译(以 ADD 为例): */
/* BPF: r0 += r1 (32-bit) */
/* x86: addl %r1d, %r0d    // 32-bit 操作自动清空 r0 的高 32-bit */

这意味着 BPF_ALU32 的操作序列比 ALU64 更紧凑——不需要显式的 mov %eax, %eax 来清零高 32-bit。

BPF_LD_IMM64——双宽指令的特殊处理

/* BPF_LD_IMM64 dst_reg, imm64 */
/* x86-64 翻译:movabsq $imm64, %dst_reg  */
/* 占用 10 字节(2 字节 opcode + 8 字节立即数)*/

movabsq 是 x86-64 中唯一能加载 64-bit 立即数的指令,它使用完整的 8 字节立即数编码。

BPF_END 端序转换

/* BPF_TO_LE (在小端主机上就是 NOP) */
/* BPF_TO_BE on x86: bswap + 右移 */
/* 例如 BE16: rol $8, %reg   ; movzwl %reg, %reg */

2.3 内存访问

加载

/* BPF_LDX_DW r0, [r1 + off] */
/* x86-64: movq off(%r1), %r0 */
/* 编码:48 8b 41 off (off 在 [-128, 127] 时)*/

x86-64 的寻址模式天然支持 base + displacement,与 BPF 的 [src_reg + off] 模型完美匹配。

存储

/* BPF_STX_W [r10 + off], r0 */
/* x86-64: movl %r0d, off(%rbp) */

2.4 函数调用

Helper 函数调用(BPF_CALL

/* BPF: call helper#N */
/* x86-64 JIT 翻译序列: */
/* 1. 保存 caller-saved 寄存器(如果需要) */
/* 2. movabsq $__bpf_call_base + N*8, %rax */
/* 3. call *(%rax) */
/* 4. 恢复 caller-saved 寄存器(如果需要) */

helper 调用通过 __bpf_call_base 表间接跳转。__bpf_call_base 是一个函数指针数组,索引为 helper ID:

/* Linux 6.6: kernel/bpf/core.c */
const struct bpf_func_proto *bpf_get_trace_printk_proto(void);
...
void * __bpf_call_base = __bpf_call_base_arr;

/* 运行时,地址计算为 __bpf_call_base_arr[helper_id * 8] */

程序内调用(BPF_PSEUDO_CALL

程序内调用直接使用 x86-64 的 call 指令跳转到目标子程序的 JIT 镜像内地址。JIT 在编译时计算相对偏移:

/* x86-64: call rel32_offset */
/* rel32 = target_addr - (current_addr + 5) */

这与 x86-64 上正常的 C 函数调用完全一致。

2.5 尾调用(Tail Call)

尾调用是 BPF 中特殊的”长跳转”机制——允许一个 BPF 程序直接跳转到另一个 BPF 程序入口(不返回)。BPF 程序侧通过 helper bpf_tail_call() 触发;x86-64 JIT 内部由 emit_bpf_tail_call()(或等价路径)生成本地码。

/* Linux 6.6: arch/x86/net/bpf_jit_comp.c — tail call JIT 序列(简化) */
/* 尾调用的 x86-64 序列: */
/* 1. 检查 prog_array map 中的目标程序是否存在 */
/* 2. 如果目标为 NULL:继续执行当前程序 */
/* 3. 如果目标存在:准备新的栈帧,jmp 到目标程序的 JIT 入口 */

/* 实际生成的代码(简化) */
/* lea -STACK_SIZE(%rbp), %rsp     ; 重置栈指针到目标程序的帧 */
/* jmp *target_entry               ; 直接跳转到目标程序 */

尾调用的性能开销:每次尾调用约 10–15 条本地指令(map 查找 + 栈重建 + 跳转),在 x86-64 上约 5–10 ns。比完整函数调用(需要 push/pop 和返回)快约 2–3 倍,比重新从 interpreter 入口进入快约 10 倍。

2.6 JIT 优化:bpf_jit_optimize

x86-64 JIT 在两轮编译之间执行轻量级优化:

/* Linux 6.6: arch/x86/net/bpf_jit_comp.c */
static void bpf_jit_optimize(struct bpf_prog *prog)
{
    /* 1. 消除 NOP 指令(BPF_NOP/jmp 0) */
    /* 2. 转换冗余的 MOV (mov %rA, %rB; mov %rB, %rA → 单条 mov) */
    /* 3. 将条件跳转的 NEG+JMP 合并为单条 JMP(翻转条件) */
    /* 4. 消除死代码(在 verifier 已经保证了可达性的前提下有限) */
}

这些优化在编译时对 BPF 指令流进行,然后在第二 pass 生成优化后的机器码。与 LLVM/GCC 的优化流水线相比非常有限,主要是因为 BPF 经过 clang -O2 编译后已经优化过,JIT 只需要做字节码模式级别的转换。

三、ARM64 JIT 后端

3.1 寄存器映射

ARM64 JIT 的寄存器映射与 x86-64 有根本不同:

BPF 寄存器 ARM64 寄存器 选择理由
R0 x7 ARM64: x0-x7 是参数/返回值寄存器
R1 x0 映射到 ARM64 ABI arg0(与 x86 的 rdi 的角色相同,但寄存器号不同)
R2 x1 arg1
R3 x2 arg2
R4 x3 arg3
R5 x4 arg4
R6 x19 callee-saved (x19-x28)
R7 x20 callee-saved
R8 x21 callee-saved
R9 x22 callee-saved
R10 x25 用作帧指针(ARM64 上 x29 是硬件 FP,但被 JIT 保留)
TCC x26 尾调用计数器(BPF_MAX_TAIL_CALL_CNT 检查)

ARM64 的映射刻意将 BPF R1-R5 对齐到 ARM ABI 的 x0-x4——这使得 helper 调用的参数传递与 BPF 调用约定自然重合,无需寄存器移动。

3.2 ALU64 翻译

ARM64 上的 64-bit ALU 指令大多是 one-to-one 的:

BPF 指令 ARM64 指令
BPF_ADD add xD, xA, xB
BPF_SUB sub xD, xA, xB
BPF_MUL mul xD, xA, xB
BPF_DIV udiv xD, xA, xB
BPF_OR orr xD, xA, xB
BPF_AND and xD, xA, xB
BPF_LSH lsl xD, xA, xB
BPF_RSH lsr xD, xA, xB
BPF_ARSH asr xD, xA, xB
BPF_NEG neg xD, xA

但有一个关键例外:BPF_MUL (64-bit)

BPF: r0 = r0 * r1  (完整的 128-bit 结果截断到低 64-bit)
ARM64: mul x7, x7, x0  // ARM64 的 mul 指令也产生 64-bit 截断结果
// 这是兼容的——BPF 和 ARM64 都丢弃溢出高位
// 但 x86-64 的 imulq 是一条指令,ARM64 的 mul 也是一条——两者的 64-bit 乘法性能相同

3.3 ALU32 翻译:ARM64 的额外负担

这是 ARM64 JIT 和 x86-64 JIT 最显著的差异。ARM64 没有”32-bit 操作自动清零高 32-bit”的硬件特性。BPF_ALU32 的语义要求在操作后将目标寄存器的高 32-bit 清零,这在 ARM64 上需要显式指令:

BPF: r0 += r1 (32-bit)

x86-64: addl %r1d, %r0d           ; 1 条指令,自动零扩展
ARM64:  add w7, w7, w0            ; 1 条指令(使用 w 寄存器自动截断)
        ; 但高 32-bit 未清零!
        ; 需要显式 uxtw x7, w7     ; 或者 mov w7, w7

在实践中,JIT 编译器会追踪是否需要清零高 32-bit。如果后续指令也会覆盖完整的 64-bit 寄存器,则可以省略 uxtw

/* ARM64 JIT 的优化逻辑:推迟 uxtw 到真正需要 64-bit 值的时候 */
/* 如果多条 BPF_ALU32 连续执行,只在最后一条或需要 64-bit 使用前插入 uxtw */

这导致 ARM64 上密集的 32-bit 算术比等效的 64-bit 算术有额外开销。

3.4 原子操作

ARM64 支持通过 LSE(Large System Extensions)指令集实现高效的原子操作:

BPF 原子操作 ARM64 指令(LSE) ARM64 指令(无 LSE)
BPF_ATOMIC_ADD stadd ldaxr; add; stlxr (LL/SC 循环)
BPF_ATOMIC_FETCH_ADD ldaddal LL/SC loop with old value
BPF_ATOMIC_XCHG swpal LL/SC loop
BPF_ATOMIC_CMPXCHG casal LL/SC loop

如果硬件支持 LSE(ARMv8.1+),原子操作为 one-to-one 翻译;否则退化为 load-linked/store-conditional 循环。

3.5 Tail Call 在 ARM64 上的实现

ARM64 的 tail call 实现与 x86-64 在语义上相同,但使用了不同的寄存器:

/* ARM64 JIT 中的 tail call 实现(简化) */
/* 1. 将 TCC (x26) 递增,检查是否达到 BPF_MAX_TAIL_CALL_CNT */
/* 2. 查询 prog_array map(使用 R1/R2 作为参数) */
/* 3. 如果找到目标:构建新帧,br 到目标入口 */
/* 4. 否则继续当前程序 */

ARM64 使用专用寄存器 x26 作为尾调用计数器(TCC),避免每次尾调用都需要从内存重新加载计数器。

四、JIT 资源限制与调试集成

4.1 bpf_jit_limit

JIT 编译的镜像需要分配可执行内核内存。为防止单个用户消耗过多内存,内核设置了 bpf_jit_limit

# 查看和设置 JIT 内存限制
cat /proc/sys/net/core/bpf_jit_limit
# 默认值:PAGE_SIZE * 4000 ≈ 16 MB(在 4K 页大小系统上)
# 或者:min(PAGE_SIZE * 4000, totalram_pages / 4)

# 设置自定义限制
echo 33554432 > /proc/sys/net/core/bpf_jit_limit  # 32 MB

当 JIT 内存耗尽时,内核会返回 -ENOMEM 并跳过 JIT 编译,程序退化为 interpreter 模式执行。

4.2 bpf_jit_kallsyms

JIT 编译的函数镜像被暴露在 /proc/kallsyms 中,以便调试和性能分析工具关联 JIT 生成的代码:

# 查看 JIT'd BPF 程序的内核地址
grep bpf_prog /proc/kallsyms | head

# 输出示例:
# ffffffff81001234 t bpf_prog_42_xdp_filter [bpf]
# ffffffff81001500 t bpf_prog_43_tc_ingress [bpf]

每个 JIT 镜像以 bpf_prog_<ID>_<name> 的格式注册。这使得 perf recordperf report 能够按名称显示 BPF 程序的 CPU 使用率:

# 采样 5 秒后查看调用栈(需 root 且 kallsyms 可见)
sudo perf record -g -- sleep 5
sudo perf report --sort comm,dso,symbol | grep bpf_prog
# 输出示例:
#   15.23%  bpf_prog_42_xdp_filter  [bpf]
#    8.45%  bpf_prog_43_tc_ingress  [bpf]

4.3 JIT Hardening

bpf_jit_harden 控制 JIT 代码的安全加固:

# 0 = 不加固(默认)
# 1 = 对非特权用户启用加固
# 2 = 对所有用户启用加固(最佳安全性,但有性能代价)

echo 2 > /proc/sys/net/core/bpf_jit_harden

加固机制包括:

  1. 常数盲化(Constant blinding):将 BPF 程序中的 64-bit 立即数通过 XOR 操作盲化,防止攻击者利用 JIT 镜像中的已知常数构建 ROP/JOP 链。
  2. Retpoline:使用 retpoline 模式生成间接跳转(call *(%rax) 替换为 lfence; jmp *(%rax)),防止 Spectre v2 攻击。

常数盲化会引入额外的 XOR 等指令,典型开销为数个百分点(依程序形态而定,需在本机 benchmark 验证)。

五、Interpreter vs JIT 对比

维度 Interpreter JIT
加载延迟 极小(直接开始执行) ~10-100us 编译开销
执行速度 基准值 通常快数倍到一个数量级(依程序与 CPU 而定)
内存占用 无额外内存 每条指令 1–20 字节镜像
调试支持 容易(单步执行) 困难(需要反汇编 JIT 镜像)
安全加固 N/A(interpreted 无 ROP 风险) 常数盲化、retpoline
适用场景 一次性追踪脚本、测试 高吞吐数据面(XDP、TC)

JIT 通过 bpf_jit_enable sysctl 控制是否启用。生产环境中应始终设置为 1(启用)。

六、Mermaid:JIT 编译与执行流程

sequenceDiagram
    participant USER as 用户态 (libbpf/bpftool)
    participant SYS as bpf() syscall
    participant VERIF as verifier (kernel/bpf/verifier.c)
    participant CORE as bpf_jit_compile()<br/>(kernel/bpf/core.c)
    participant X86 as x86-64 JIT<br/>(arch/x86/net/bpf_jit_comp.c)
    participant ARM64 as ARM64 JIT<br/>(arch/arm64/net/bpf_jit_comp.c)
    participant MEM as 内核可执行内存
    participant HOOK as BPF hook (XDP/TC/kprobe)
    participant KP as kallsyms

    USER->>SYS: bpf(BPF_PROG_LOAD, insns, size)
    SYS->>VERIF: bpf_check(prog) → 逐条验证
    VERIF-->>SYS: 验证通过 ✓
    
    SYS->>CORE: bpf_jit_compile(prog)
    CORE->>CORE: 检查 bpf_jit_enable<br/>检查 bpf_jit_limit
    
    alt x86-64 系统
        CORE->>X86: bpf_int_jit_compile(prog)
        X86->>X86: Pass 1: 计算镜像大小<br/>寄存器分配 + 指令选择
        X86->>X86: bpf_jit_optimize(prog)<br/>消除 NOP、合并 MOV
        X86->>MEM: Pass 2: emit 机器码<br/>分配 module_alloc() 内存
    else ARM64 系统
        CORE->>ARM64: bpf_int_jit_compile(prog)
        ARM64->>ARM64: Pass 1: 计算镜像大小<br/>寄存器分配 (x0-x4, x19-x25)
        ARM64->>ARM64: 插入 uxtw 指令<br/>(ALU32 高 32-bit 清零)
        ARM64->>MEM: Pass 2: emit ARM64 码<br/>分配可执行内存
    end
    
    X86-->>CORE: 返回 bpf_binary_header
    ARM64-->>CORE: 返回 bpf_binary_header
    
    CORE->>KP: 注册 JIT 镜像<br/>bpf_jit_kallsyms_add()
    CORE-->>SYS: 返回编译后的 prog
    SYS-->>USER: 返回程序 FD
    
    Note over HOOK: 运行时:attach 到 hook
    USER->>HOOK: attach (XDP/netlink/perf_event)
    HOOK->>MEM: call bpf_prog_42_xdp_filter
    Note over MEM: 本地指令直接执行<br/>在 CPU 上全速运行

七、为什么 JIT 不能总是生成最优码

从上面的分析可以总结出 JIT 翻译质量的几个根本限制:

  1. BPF ISA 的表达力上限:BPF 没有浮点指令、没有 SIMD/向量指令、没有条件执行(ARM64 的 csel 等)。JIT 无法凭空生成这些优化。

  2. 寄存器压力:BPF 只有 11 个寄存器,比 x86-64 的 16 个(或 ARM64 的 31 个)少得多。JIT 必须精确映射,无法利用多余的寄存器做 loop unrolling 或指令调度。

  3. 调用约定耦合:BPF 的 R1-R5 必须映射到 ABI 参数寄存器,这固化了寄存器分配。编译器优化中常见的寄存器重命名、循环流水等优化不可行。

  4. 无全局优化:JIT 只在单条 BPF 指令级别做模式匹配(BPF_ADD → addq),不做跨指令优化。例如连续的 BPF_MOV + BPF_ADD 不能自动合并为 lea(x86-64 的地址计算指令)。

  5. 安全约束:常数盲化和 retpoline 增加指令数。在生产环境中启用 bpf_jit_harden=2 后,JIT 代码的性能通常会有可感知的下降(具体幅度需 benchmark)。

八、小结

BPF JIT 编译器在架构设计上是精巧的折中:它在两轮编译中完成从 BPF 字节码到本地指令的翻译,利用与目标架构 ABI 的对齐来最小化函数调用开销,通过寄存器映射和指令模式匹配实现快速的 one-to-one 或 few-to-one 翻译。

x86-64 的 JIT 得益于 x86 丰富的寻址模式和 32-bit 操作的隐式零扩展,翻译最为高效。ARM64 JIT 面临 ALU32 清零的额外开销,但通过推迟 uxtw 延迟优化,在计算密集型场景中达到了接近 x86-64 的效率。

理解 JIT 的翻译策略对编写高性能 BPF 程序有两个直接价值:(1) 知道哪些 BPF 指令模式会生成低效的本地码(例如频繁的 ALU32→ALU64 切换),(2) 理解为什么某些架构上 JIT 后性能差距与 interpreter 基准不成比例。


上一篇与验证器共舞:常见拒绝模式与编程约束(第 04 篇)

下一篇Map 内核实现(上):hash / array / per-CPU 的数据结构与并发模型(第 06 篇)

同主题继续阅读

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

2026-06-12 · kernel / ebpf

【eBPF 内核实现深度拆解】从验证器到 JIT,从 BTF 到调度器

eBPF 内核虚拟机内部实现系统讲解:BPF 指令集与寄存器机器、验证器的抽象解释与状态裁剪、JIT 编译器后端、Map 各类型的并发与内存模型、helper 函数注册与类型检查、BTF 格式规范与 CO-RE 重定位引擎、libbpf 加载器工程、fentry/fexit 蹦床机制、sched_ext 调度器内核接口。面向想读懂 eBPF 内核源码、写生产级 BPF 程序的系统工程师。


By .