自 5.6 kernel bpf dispatcher 被引入到 Linux 内核中。
到目前 6.9 kernel,仍然只有 XDP 程序在使用 bpf dispatcher。
bpf dispatcher 的引入,是为了解决间接调用叠加 retpoline 带来的性能损耗问题。
bpf dispatcher 简介
bpf dispatcher 是一个多路分支代码生成器,用于生成多个代码分支的跳转表。
用 C 代码来描述,bpf dispatcher 的代码逻辑大概如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
struct bpf_dispatcher {
struct mutex lock;
int num;
void *jump_table[48];
};
static struct bpf_dispatcher xdp_dispatcher = {};
static __always_inline void
bpf_dispatcher(struct bpf_prog *prog)
{
for (int i = 0; i < xdp_dispatcher.num; i++) {
if (prog == xdp_dispatcher.jump_table[i]) {
jump(xdp_dispatcher.jump_table[i]);
}
}
}
|
其中:
xdp_dispatcher
是一个全局变量,用于保存所有 XDP 程序的跳转表。
- 在向网络设备挂载 XDP 程序时,会将 XDP 程序添加到
xdp_dispatcher
的跳转表中。
jump
对应 JMP 这个机器指令。
bpf dispatcher 源码解析
在 kernel 里,bpf dispatcher 的实现比上面的代码片段复杂一些,但是原理是一样的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
// ${KERNEL}/include/linux/bpf.h
#define BPF_DISPATCHER_MAX 48 /* Fits in 2048B */
struct bpf_dispatcher_prog {
struct bpf_prog *prog;
refcount_t users;
};
struct bpf_dispatcher {
/* dispatcher mutex */
struct mutex mutex;
void *func;
struct bpf_dispatcher_prog progs[BPF_DISPATCHER_MAX];
int num_progs;
void *image;
void *rw_image;
u32 image_off;
struct bpf_ksym ksym;
#ifdef CONFIG_HAVE_STATIC_CALL
struct static_call_key *sc_key;
void *sc_tramp;
#endif
};
|
在挂载了一个 XDP 程序后,使用 drgn
工具可以看到 bpf_dispatcher_xdp
的结构:
1
2
3
4
5
6
7
8
9
10
11
|
>>> prog.variable('bpf_dispatcher_xdp')
(struct bpf_dispatcher){
// ...
.num_progs = (int)1,
.image = (void *)0xffffffffc037aa00,
.rw_image = (void *)0xffffffffc104c000,
.image_off = (u32)0,
// ...
.sc_key = (struct static_call_key *)__SCK__bpf_dispatcher_xdp_call+0x0 = 0xffffffff86788010,
.sc_tramp = (void *)__SCT__bpf_dispatcher_xdp_call+0x0 = 0xffffffff8535a9b0,
}
|
关注其中的 image
字段:
1
2
3
4
5
6
7
8
|
root@leon-vm ~# gdb -q -c /proc/kcore -ex 'x/18i 0xffffffffc037aa00' -ex 'quit'
0xffffffffc037aa00: cmp $0xffffffffc0360788,%rdx
0xffffffffc037aa07: je 0xffffffffc0360788
0xffffffffc037aa0d: jmp *%rdx
0xffffffffc037aa0f: int3
root@leon-vm ~# grep ffffffffc0360788 /proc/kallsyms
ffffffffc0360788 t bpf_prog_3b185187f1855c4c_dummy [bpf] # 正是刚才运行起来的 XDP bpf prog
|
static call?
这里为什么会用到 STATIC_CALL?
这是为了解决 bpf dispatcher 滥用 ftrace __fentry__
的问题;即不能同时使用 ftrace 了。
最终,kernel 在调用 bpf_dispatcher_xdp_func()
时,实际调用的是其 STATIC_CALL 对应的 trampoline。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
root@leon-vm ~/kernel-bpf (bpf-next_base)# grep dispatcher /proc/kallsyms
ffffffff8505a510 t bpf_dispatcher_nop_func
ffffffff85063630 T bpf_dispatcher_xdp_func
root@leon-vm ~# gdb -q -c /proc/kcore -ex 'x/18i 0xffffffff85063630' -ex 'quit'
0xffffffff85063630: nopl 0x0(%rax,%rax,1)
0xffffffff85063635: push %rbp
0xffffffff85063636: mov %rsp,%rbp
0xffffffff85063639: call 0xffffffffc037aa00 # 对应 xdp dispatcher 的 trampoline image
0xffffffff8506363e: pop %rbp
0xffffffff8506363f: xor %edx,%edx
0xffffffff85063641: xor %esi,%esi
0xffffffff85063643: xor %edi,%edi
0xffffffff85063645: ret
|
总结
bpf dispatcher 是一个多路分支代码生成器,用于生成多个代码分支的跳转表,用来解决间接调用叠加 retpoline 带来的性能损耗问题。
在 kernel 里,实际采用了 2 层 trampoline,即 STATIC_CALL trampoline + bpf dispatcher trampoline。
可以通过 drgn
和 gdb
工具来查看 bpf dispatcher 的实现细节。
后面,将会在《XDP 进阶手册》里详细讲解 bpf dispatcher 的源码细节。
想要更多 XDP 资料,请加入「eBPF Talk」知识星球来学习《XDP 进阶手册》吧。