global percpu data 是我近期向 BPF 社区贡献的一个特性:类似于 int data SEC(".data") 这样的全局变量,实现了 int pdata SEC(".percpu") per-CPU 的全局变量。

Demo

percpu data 不限制程序类型,任何程序类型都能使用它。

1
2
3
4
5
6
7
int counter SEC(".percpu");

SEC("xdp")
int entry(struct xdp_md *xdp)
{
    counter++;
}

用法就是这么简单。

实现原理

背景知识:请看我在 2026 年 eBPF 开发者大会上的演讲,给上游引入 global percpu data 特性_PPT

int counter SEC(".percpu") 运行时的背后是一个 PERCPU_ARRAY map,程序里访问 counter 时访问的就是对应 PERCPU_ARRAY map 里的内存。

而且,不同于内核里需要用 this_cpu_*() helper 才能访问 per-CPU 变量,在 BPF 里并不需要 this_cpu_*() 这些 helper,默认访问的就是当前 CPU 上的内存。

怎么做到的呢?核心在于 verifier 指令改写。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
diff --git a/kernel/bpf/fixups.c b/kernel/bpf/fixups.c
index 0caf1bbd9494..177a3fcbb63a 100644
--- a/kernel/bpf/fixups.c
+++ b/kernel/bpf/fixups.c

+                       /*
+                        * We are *skipping* first half of ld_imm64 insn
+                        * with 'i++;', patching over second half of it
+                        * with that same half + mov64_percpu_reg insn.
+                        * All because bpf_patch_insn_data() can only
+                        * replace one 8-byte insn, which does not work
+                        * well for ld_imm64 insn.
+                        */
+
+                       insn_buf[0] = insn[1];
+                       insn_buf[1] = BPF_MOV64_PERCPU_REG(insn->dst_reg, insn->dst_reg);
+                       cnt = 2;
+
+                       i++;
+                       new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);

其中,BPF_MOV64_PERCPU_REG() 是一个 BPF 内部专用的指令,用来给指针添加 this_cpu_off 偏移。

上面 diff 的意思:

  1. 当前 insn 是一个加载 map 内存指针的指令 ld_imm64ld_imm64 是一个占据 2 个 struct bpf_insn 的指令。
  2. 需要在该 ld_imm64 后面插入一个 BPF_MOV64_PERCPU_REG() 指令。
  3. i++ 将当前 insn 位置挪到 ld_imm64 后半部分。
  4. bpf_patch_insn_data()ld_imm64 后半部分的位置植入 ld_imm64 后半部分 + BPF_MOV64_PERCPU_REG() 指令。

最终,通过 bpftool 看,将得到:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# bpftool p d x i <ID>
; global_data++;
   6: (18) r1 = map[id:6][0]+0
   8: (bf) r1 = &(void __percpu *)(r1)
   9: (61) r2 = *(u32 *)(r1 +0)
  10: (07) r2 += 1

# bpftool p d j i <ID>
; global_data++;
  38: movabsq $46112983506704, %rdi
  42: addq %gs:88656, %rdi
  4b: movl (%rdi), %esi
  4e: addq $1, %rsi

小结

其实,早在 2024 年 12 月 24 日,便完成了 POC;在 2025 年 1 月 13 日发出该系列的 RFC;包括 RFC,共迭代了 13 个版本,最终在 2026 年 8 月 14 日合入 BPF 社区,将合入到内核 7.13 版本。

战线拉的比较长,期间完成了该系列依赖的 eBPF Talk: 新增 BPF_F_CPU 和 BPF_F_ALL_CPUS flag,以及该系列不依赖的 eBPF Talk: 震惊!居然有人扩展了 BPF syscall

该系列合入 BPF 社区的那一刻,挺振奋人心的,泪目!!!