介绍
BPF的ringbuf被实现为2的指数大小的环形缓冲区,它有两个总是递增的计数器:consumer_pos、producer_pos;
producer_pos是生产者计数器,每当一个record被成功存储,producer_pos就会增加;consumer_pos是消费者计数器,每当在用户空间读取一个record,consumer_pos就会增加;
每一个计数器都存放在独立的页中,因此对用户空间而言,producer_pos是仅可读的,consumer_pos是可读可写的;
相关数据结构
ringbuf


ringbuf的结构比较复杂,从第二张图中可以看到两个计数器被强制对齐在了不同的页中;
ringbuf的mask成员表示环形缓冲区的大小,也就是通过&mask来实现求模,从而实现环形缓冲区的效果;
bpf_ringbuf_map

bpf_ringbuf_hdr

关于这个pg_off:


可以看到pg_off记录了hdr和其ringbuf之间的页面偏移量;
重点函数分析
ringbuf的空间创建
首先分配meta空间和两倍的data空间的物理页:

之后进行虚拟地址空间和物理地址空间之间的映射:

通过上述代码中的pages[nr_data_pages + i] = page可知,将来会有两个虚拟地址映射同一片物理地址:

这样在处理环形的问题的时候会有一定用处。
reserve
reserve用于从ringbuf中预定一片空间,以供后续write使用;
用户态接口如下:(flags必须为0)

主要处理函数如下:


可以看到是从ringbuf的data中分割出一片区域构造一个hdr+content,然后设置hdr的meta值;
最后会返回这样一个空间的指针(其实很像glibc)
submit
函数原型:

关键函数:

这个函数的作用是修改hdr中size的属性位,在eBPF的verifier中会在调用了reserve之后检查后续是否调用了submit(或者discard),如果没有调用则会报错:
BPF_FUNC_ringbuf_reserve 0x83
BPF_FUNC_ringbuf_submit 0x84

用户态借助eBPF写ringbuf
reserve 预留空间
利用eBPF汇编指令STX写入数据
submit提交

mmap接口


对ringbuf的mapfd调用mmap则会走到ringbuf_map_mmap函数,

可以直接使用mmap映射ringbuf的mapfd,然后在用户态直接修改consumer_pos:
expmap_fd = bpf_map_create(BPF_MAP_TYPE_RINGBUF, 0, 0, 0x4000); |
调试技巧
在 __bpf_ringbuf_reserve 函数下断点,执行到此后finish,查看返回值rax就是分配的一个ringbuf中的空间,-8就是该chunk的hdr。