首先我们看eventhandler的注册函数
void evhandler_register(evhandler_t *evh) {
evh->type = next_type++;
//向queue中添加对应的handler
TAILQ_INSERT_TAIL(&evh_list, evh, node);
}
然后我们看一下关于事件处理对应的逻辑, 函数的参数是事件
static int event_handle(event_t *ev, size_t size) {
evhandler_t *evh;
//首先根据对应的类型查找对应的事件handler
evh = evhandler_find(ev->type);
//调用处理函数
return evh->handle(ev, evh->priv);
}
下面这个函数是事件管道的初始化函数
- 首先我们获取对应的CPU核心数量, 这个主要通过sysconf这个函数来做的,
- 然后我们根据对应的CPU核心数量去分配内存, 分配的内存主要用于拉取对应的事件,和对应的事件。
- 其次ebpf内核模块通过perf_event向用户态程序发送消息,需要使用一种特殊的map
BPF_MAP_TYPE_PERF_EVENT_ARRAY
,我们使用bpf_map_create
进行创建 - 然后队列的初始化函数这个函数,我们会使用对应的perf_open函数来完成相应操作
int evpipe_init(evpipe_t *evp, size_t qsize) {
uint32_t cpu;
int err;
if (G.dump) {
evp->mapfd = 0xeeee;
return 0;
}
//首先获取系统的cpu核心数量
evp->ncpus = sysconf(_SC_NPROCESSORS_ONLN);
//创建一个性能事件数组
evp->mapfd = bpf_map_create(BPF_MAP_TYPE_PERF_EVENT_ARRAY,
sizeof(uint32_t), sizeof(int), evp->ncpus);
//首先根据cpu核心数量分配对应队列的内存
evp->q = calloc(evp->ncpus, sizeof(*evp->q));
evp->poll = calloc(evp->ncpus, sizeof(*evp->poll));
//创建对应数量的队列
for (cpu = 0; cpu < evp->ncpus; cpu++) {
//创建队列
err = evqueue_init(evp, cpu, qsize);
}
}
首先ebpf map位于内核态,如果想通知对应的信息给用户,那么就必须借助对应的perf_event,perf是 linux 内核提供的一项与用户进行交互的机制,主要用于内核机制的监视分析, 这是一种软件事件, 同时是用于对应的 bpf 输出。
attr.type = PERF_TYPE_SOFTWARE;
attr.config = PERF_COUNT_SW_BPF_OUTPUT;
attr.sample_type = PERF_SAMPLE_RAW;
attr.wakeup_events = 1;
然后更新对应的id, 通过这种索引关系,我们可以根据所用下标获取对应的perf_event
bpf_map_update(evp->mapfd, &cpu, &q->fd, BPF_ANY);
然后分配对应性能映射内存
sysconf(_SC_PAGESIZE)
:这个调用用来获取系统的页面大小,这通常是内存映射操作需要考虑的一个参数。size += sysconf(_SC_PAGESIZE);
:这行代码将当前的size变量增加页面大小,可能是为了确保分配足够的内存。q->mem = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, q->fd, 0);
:这行代码尝试将文件描述符q->fd
指向的文件映射到内存中。PROT_READ | PROT_WRITE
指定了映射区域的保护属性,允许读写访问。MAP_SHARED
表示映射是共享的,对映射区域的修改将反映到文件上。
int evqueue_init(evpipe_t *evp, uint32_t cpu, size_t size) {
//首先定义一个性能事件的结构体
struct perf_event_attr attr = { 0 };
//根据cpu号查询对应队列
struct evqueue *q = &evp->q[cpu];
int err;
//设置对应的属性, 分别是软件事件
attr.type = PERF_TYPE_SOFTWARE;
attr.config = PERF_COUNT_SW_BPF_OUTPUT;
attr.sample_type = PERF_SAMPLE_RAW;
attr.wakeup_events = 1;
//打开一个性能事件, 这个事件用于创建对应性能事件队列
q->fd = perf_event_open(&attr, -1, cpu, -1, 0);
//更新bpf的map值, 它的键是cpu号码, 它的值是创建的队列
err = bpf_map_update(evp->mapfd, &cpu, &q->fd, BPF_ANY);
size += sysconf(_SC_PAGESIZE);
//分配对应的内存
q->mem = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, q->fd, 0);
evp->poll[cpu].fd = q->fd;
evp->poll[cpu].events = POLLIN;
return 0;
}
有了这些基础的配置, 我们就可以通过事件循环来不断的拉取对应的事件了, 关于这点且听下回分解