温暖的电波  

一 什么是perf buffer?

ebpf中提供了内核和用户空间之间高效地交换数据的机制--perf buffer,它是一种per-cpu的环形缓冲区,当我们需要将ebpf收集到的数据发送到用户空间记录或者处理时,就可以用perf buffer来完成。它还有如下特点:

  1) 能够记录可变长度数据记;

  2) 能够通过内存映射的方式在用户态读取读取数据,而无需通过系统调用陷入到内核去拷贝数据;

  3) 实现epoll通知机制

ebpf提供了专门的map和helper function来使用perf buffer,如下是最常用的两个:

map:BPF_MAP_TYPE_PERF_EVENT_ARRAY  /* 此类型map专门用于perfbuffer */
helper function:bpf_perf_event_output  /* 用于通知用户态拷贝数据 */

二 bpf中如何使用?

前面提到了这么多的概念可能比较迷糊,现在我们来实际操作一把,show me the code

2.1 内核ebpf部分

首先,在ebpf中定义一个BPF_MAP_TYPE_PERF_EVENT_ARRAY类型的map;这个map可以不用指定max_entries,因为在libbpf中会默认设置max_entries为系统cpu个数。

/* BPF perfbuf map */
struct {
    __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
    __uint(key_size, sizeof(int));
    __uint(value_size, sizeof(u32));
} my_map SEC(".maps");

然后,我们再构建一个用于通知用户态拷贝的prog。这个prog在sys_write挂接了一个kprobe处理程序,程序调用bpf_perf_event_output()来通知用户态拷贝数据data。

SEC("kprobe/sys_write")
int bpf_prog1(struct pt_regs *ctx)
{
        struct eb {
                u64 pid;
                u64 cookie;
        } data;

        data.pid = bpf_get_current_pid_tgid();
        data.cookie = 0x12345678;

        bpf_perf_event_output(ctx, &my_map, 0, &data, sizeof(data));

        return 0;
}

2.2 用户态部分

这部分代码是基于libbpf-bootstrapBPF skeleton 框架来编写的,这样可以使得整个代码实现更加简洁。

struct perf_buffer *pb = NULL;
struct perf_buffer_opts pb_opts = {};
struct perfbuf_output_bpf *skel;
    ...
pb_opts.sample_cb = handle_event;
pb = perf_buffer__new(bpf_map__fd(skel->maps.my_map), 8 /* 32KB per CPU */, &pb_opts);
if (libbpf_get_error(pb)) {
        err = -1;
        fprintf(stderr, "Failed to create perf buffer\n");
        goto cleanup;
}
  ...
while (!exiting) {
        err = perf_buffer__poll(pb, 100 /* timeout, ms */);
        /* Ctrl-C will cause -EINTR */
        if (err == -EINTR) {
            err = 0;
            break;
        }
        if (err < 0) {
            printf("Error polling perf buffer: %d\n", err);
            break;
        }
}

上面通过perf_buffer__new这个libbpf提供的库函数来创建一个struct perf_buffer;此外还通过pb_opts.samble_cb = handle_event设置了perf_buffer的通知回调处理函数。

然后通过perf_buffer__poll()等待ebpf的通知事件;当ebpf侧调用bpf_perf_event_output()发起通知时,poll等待的任务就会被唤醒进而调用pb_opts.samble_cb指向的回调函数handle_event

#define MAX_CNT 1000000000ll
struct eventbuf {
        __u64 pid;
        __u64 cookie;
} ;
void handle_event(void *ctx, int cpu, void *data, __u32 data_sz)
{
        static __u64 cnt;
        const struct eventbuf *eb = data;
        if (eb->cookie !=  0x12345678) {
                printf("BUG pid %llx cookie %llx sized %d\n", eb->pid, eb->cookie, data_sz);
                return;
        }
        cnt++;
        if (cnt == MAX_CNT) {
                printf("recv %lld events per sec\n",
                        MAX_CNT * 1000000000ll / (time_get_ns() - start_time));
        }
        return;
}

三 整体逻辑梳理

整理一下上面的逻辑

  • ebpf在代码通过SEC(".maps")声明一个BPF_MAP_TYPE_PERF_EVENT_ARRAY类型的map my_map;
  • ebpf在sys_write这个内核函数上定义一个kprobe,函数名为bpf_prog1;
  • skeleton框架借助libbpf调用xxx_bpf__open()解析上面定义在".maps" section的map my_map,并调用xxx_bpf__load()在内核中创建这个map
  • 用户态调用libbpf提供的库函数perf_buffer__new()创建struct perf_buffer *pb;
  • skeleton框架借助libbpf调用xxx_bpf__attach()将前面的kprobe函数bpf_prog1 attach到sys_write上
  • 用户态调用perf_buffer__poll(pb, xxx)监听ebpf上perf_buffer的事件
  • 一旦内核有发生sys_write()调用,bpf_prog1()就调用bpf_perf_event_output()通知poll等等perf buffer的任务
  • 在perf buffer上perf_buffer__poll等待的任务收到内核发来的通知,调用回调函数handle_event()拷贝数据并做处理

 

posted on 2023-01-31 21:50  温暖的电波  阅读(2236)  评论(0编辑  收藏  举报