BPF ringbuf vs. BPF perfbuf

BPF ringbuf vs. BPF perfbuf

参考资料

BPF ring buffer (nakryiko.com)

正文

本文对比了 BPF kernel space 与 user space 间的两种数据传输方法:ringbufperfbuf

perfbuf:perf 缓冲区为每个 CPU 分配了一个独立的缓冲区,这就引入了如下几个问题

  • 内存使用效率低下:大部分时间只有个别 CPU 的缓冲区得到应用,其余缓冲区处于闲置状态,当短时间内大量信息写入某个特定缓冲区时,就会造成数据的溢出,其他 CPU 上的缓冲区无法提供帮助;
  • 事件重排序:当 eBPF 程序跟踪的是生命周期这类对象时,事件的顺序就格外重要了;如果相关事件在不同的 CPU 上连续发生(几毫秒内),就可能造成乱序;
  • 额外的复制:准备好的数据,首先要复制到一个全局变量,或者 Per-CPU array,然后再复制到 perfbuf,这样就引入了两次复制,如果 perfbuf 空间不足导致数据无法插入缓冲区,那么第一次复制就会被浪费;

ringbuf:环形缓冲区,维护了一个所有 CPU 共享的缓冲区,解决了上面 perfbuf 面临的问题

  • Memory overhead:由于所有 CPU 共享缓冲区,因此在分配同样的大小的缓冲区时,使用 ringbuf 的缓冲区峰值更大,这时某个 CPU 发生连续事件时缓冲区也不会很快被填满;此外,当 CPU 数量增多时,例如从 16 扩展到 32 时,ringbuf 不需要像 perfbuf 那样将缓冲区扩大两倍;
  • Event ordering:由于 ringbuf 只有一个共享的缓冲区,因此当事件 A 比事件 B 提前提交给缓冲区时,那么事件 A 也就会比事件 B 提前写入缓冲区;
  • Wasted work and extra data copying:ringbuf 支持 reservation/submit,允许提前预留一块内存供程序直接用于准备数据,这样就节省了一次复制且不会出现复制浪费的问题;
  • BPF ringbuf 内部使用非常轻量级的自旋锁,在高并发应用中可能导致数据的丢失;

代码样例

perfbuf

struct bpf_map_def SEC("maps") pb = {
    .type = BPF_MAP_TYPE_PERF_EVENT_ARRAY,
    .key_size = sizeof(int),
    .value_size = sizeof(u32),
};

struct data {
    u32 cwnd;
    u32 timestamp;
}

// kern.c
SEC("kprobe/tcp_set_state")
int fun(struct pt_regs *ctx)
{
    struct sock *sk = (struct sock*)PT_REGS_PARM1(ctx);
    struct tcp_sock *tp = (struct tcp_sock*)sk;
    
	struct data d = {
        .timestamp = bpf_ktime_get_ns();
    }
    bpf_probe_read(&d.cwnd, sizeof*(u32), &tp->snd_cwnd);
	bpf_perf_event_output(ctx, &pb, BPF_F_CURRENT_CPU, &d, sizeof(struct data));

	return 0;
}


// user.c
static void handle_event (void *ctx, int cpu, void *data, __u32 size) {
	struct data d = data;
}

int main()
{
    int buff_fd;
    struct perf_buffer *pb = NULL;
    struct perf_buffer_opts pb_opts = {};

    buff_fd = bpf_object__find_map_fd_by_name(obj, "pb");
    // 新版本为 6 参数,第三个参数写入回调函数,后面三个参数传入 NULL 即可
    pb = perf_buffer__new(buff_fd, 8 /* 32KB per CPU */, handle_event, NULL, NULL, NULL);
    /* 老版本为 3 参数,
     * pb_opts.sample_cb = handle_event;
     * pb = perf_buffer__new(buff_fd, 8, &pb_opts);
     */
    while ((ret = perf_buffer__poll(pb, 100 /* timeout, ms */)) >= 0) {}
    return 0;
}
补充说明
关于 perf_buffer_new

在 5.15 内核中,该函数签名如下:

LIBBPF_API struct perf_buffer *
perf_buffer__new(int map_fd, size_t page_cnt,
		 const struct perf_buffer_opts *opts);

在 5.17 内核中,该函数改为宏定义,如下所示:

#define perf_buffer__new(...) ___libbpf_overload(___perf_buffer_new, __VA_ARGS__)
#define ___perf_buffer_new6(map_fd, page_cnt, sample_cb, lost_cb, ctx, opts) \
	perf_buffer__new(map_fd, page_cnt, sample_cb, lost_cb, ctx, opts)
#define ___perf_buffer_new3(map_fd, page_cnt, opts) \
	perf_buffer__new_deprecated(map_fd, page_cnt, opts)

在老版本内核中,perf_buffer__new 函数支持 3 个参数,分别传入 map 的编号,每个 CPU 分配的空间(页面数量),和 perf_buffer_opts ,其中指定了数据到达时的回调函数。
在新版本内核中,perf_buffer__new 为了兼容性改为宏定义,同时支持 3 个参数和 6 个参数,但使用 3 个参数时 libbpf 会提示 warning,6 个参数分别表示 map 编号、每个 CPU 分配的空间、数据到达回调函数、数据丢失回调函数、传递的上下文,以及选项。

ringbuf

struct bpf_map_def SEC("maps") rb = {
    .type = BPF_MAP_TYPE_RINGBUF,
    .max_entries = 256 * 1024 /* 256 KB */
};

struct data {
    u32 cwnd;
    u32 timestamp;
}

// kern.c
SEC("kprobe/tcp_set_state")
int fun(struct pt_regs *ctx)
{
    struct sock *sk = (struct sock*)PT_REGS_PARM1(ctx);
    struct tcp_sock *tp = (struct tcp_sock*)sk;
    
	struct data d = {
        .timestamp = bpf_ktime_get_ns();
    }
    bpf_probe_read(&d.cwnd, sizeof*(u32), &tp->snd_cwnd);
	bpf_ringbuf_output(&rb, &d, sizeof(struct data), 0);	
	return 0;
}


// user.c
int handle_event(void *ctx, void *data, size_t data_sz) {
    struct data d = data;
	return 0;
}

int main()
{
    int buff_fd;
    struct perf_buffer *pb = NULL;
    struct perf_buffer_opts pb_opts = {};

    buff_fd = bpf_object__find_map_fd_by_name(obj, "pb");
    rb = ring_buffer__new(buff_fd, handle_event, NULL, NULL);
    while ((ret = ring_buffer__poll(rb, 100 /* timeout, ms */)) >= 0) {}
    return 0;
}

另一个关键点是,内核调用 bpf_ringbuf_output 时不需要提供堆栈上下文信息,如果在 BPF to BPF call 中调用 bpf_ringbuf_output 函数时,就不用一直传递 struct pt_regs *ctx,也能向用户态发送数据了;

其他更细节的内容请查看 BPF ring buffer (nakryiko.com)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值