内联模拟函数调用
内核在4.2版本之前,由于BPF虚拟机不支持函数调用,BPF程序要想实现函数调用或者代码复用的效果,只能采用内联的方式,在头文件中声明 :inline __attribute__((always_inline));但这样做的有一个很大的缺点,会因内联操作越多导致BPF对象文件的大小越是膨胀;
尾调用(tail call)
内核到了4.2版本的时候,BPF提供了tail调用特性,可以允许一个 BPF 程序调用另一个程序,但这样的调用无法返回;其本质是由一个长跳转来实现,并复用同一个栈帧和寄存器R6-R9。但这样的调用是有调用次数限制的,由内核宏 MAX_TAIL_CALL_CNT 指定,目前限制为 32 次。虽然还是无法向函数调用一样的灵活,但已可以利用这个特性来减少代码规范,把复杂的逻辑分担到多个eBPF程序中,更好的管理和维护代码;
尾调用(tail call)
是比较复杂的,需要用户态和内核态配合:
首先需要再用户态定义一个BPF_MAP_TYPE_PROG_ARRAY
类型的map,把需要调用的程序句柄和对应的索引关系保存起来;
然后在内核态通过bpf_tail_call函数和指定的索引值,跳转到
另一个eBPF程序;
其中用户态和内核态通过这个BPF_MAP_TYPE_PROG_ARRAY
类型的map关联起来,需要跳转的eBPF程序和索引index一一对应,索引顺序用户自定义即可;
如果整个尾调用成功,则内核会立刻运行被调用的eBPF程序ÿ