1.基本概念
epoll 是 Linux 内核提供的一种高效的 I/O 事件通知机制,适用于监视多个文件描述符,以便在这些文件描述符上发生 I/O 事件时能够立即得到通知。epoll 的工作方式比传统的 select 和 poll 更加高效,尤其适合处理大量并发连接的场景。
epoll实例是由epoll_create 或 epoll_create1 创建的,它本质上是一个内核对象,用来跟踪和管理一组文件描述符的事件。创建epoll 实例时,会返回一个文件描述符,这个文件描述符用与后续的epoll操作。
2. 文件描述符(file descriptors)
在epoll中,文件描述符可以是任何支持非阻塞I/O的文件描述符,如套接字、管道、文件等。
可以通过 epoll_ctl 函数将这些文件描述符添加到epoll实例中,以监视它们上的事件。
3.事件:
epoll 支持监视多种事件,如 读事件(EPOLLIN)、写事件(EPOLLOUT)、错误事件等。
事件通过 structure epoll_event 结构体来描述。
4. epoll 的基本使用流程
4.1 创建epoll 实例
使用epoll_create 或 epoll_create1 创建一个epoll实例。返回的文件描述符用于标识这个实例。
int epfd = epoll_create1(EPOLL_CLOEXEC);
if(epfd < 0){
perror("epoll_create1");
return -1;
}
4.2 添加、修改或删除文件描述符:
使用epoll_ctl 函数添加(EPOLL_CTL_ADD)、修改(EPOLL_CTL_MOD)或 删除(EPOLL_CTL_DEL)要监视的文件描述符。
struct epoll_event ev;
ev.events = EPOLLIN; // 监视读事件
ev.data.fd = fd; // 需要监视的文件描述符
if(epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev) < 0){
perror("epoll_ctl");
return -1;
}
4.3 等待事件
使用epoll_wait 来等待文件描述符上的事件。该函数会阻塞直到一个或多个事件发生,或者超时。
struct epoll_event events[MAX_EVENTS];
int nfds = epoll_wait(epfd, events, MAX_EVENTS, -1); // epoll 等待超时,也就是没有等到集合中文件描述符上发生指定的事件,将返回 0;
if(nfds < 0){
perror("epoll_wait");
return -1;
}
for(int i=0; i < nfds; ++i){
if(events[i].events & EPOLLIN){
//处理读事件
}
if(events[i].events & EPOLLOUT){
//处理写事件
}
// 处理其他事件,或许有错误发生
}
5. 代码分析【正文】
5.1 函数说明
static int nni_posix_pollq_create(nni_posix_pollq *pq)
这段代码的功能是创建一个文件描述符,并将其添加到 epoll 实例中,以便在程序需要自我唤醒时,能够使用该文件描述符触发 epoll 事件。 主要步骤包括创建事件文件描述符,设置文件描述符属性、 配置 epoll 事件并将其添加到 epoll 实例中。详细信息如下:
if((pq->epfd = epoll_create1(EPOLL_CLOEXEC) < 0){
return (nni_plat_erron(errno))
}
这段代码的作用是创建一个epoll 实例,并将文件描述符存储在 pq 结构体的 epfd 中。 参数EPOLL_CLOEXEC 的表示在执行exec系列函数时,自动关闭这个文件描述符。(请自行参考exec系列函数,exit...)。
5.2 nni_posix_pollq_add_eventfd(pq)
在nni_posix_pollq_create(nni_posix_pollq *pq)中调用了 nni_posix_pollq_add_eventfd(pq)这个函数。
1. 定义epoll_event ev
2. 创建事件文件描述符, 使用eventfd 函数创建一个文件描述符 fd, 初始值唯0 , 标志为EFD_NONBLOCK (非阻塞)
if((fd = eventfd(0, EFD_NONBLOCK) < 0 ){
return (nni_plat_errno(errno));
}
3. 设置文件描述符属性
(void) fcntl(fd, F_SETFD, FD_CLOEXEC);
(void) fcntl(fd, F_SETFL, O_NONBLOCK);
使用fcntl 设置文件描述符的标志,FD_CLOEXEC,使其在执行exec()系列函数后关闭。
再次使用fcntl 函数设置文件描述符为非阻塞模式。
4. 设置 epoll 事件
ev.events = EPOLLIN;
ev.data.ptr = 0;
设置 epoll 事件唯 EPOLLIN,表示监听读事件。 将ev.data.ptr 设置为 0, 这个指针可以用来存储用户数据,此处设置为0.
5. 向 epoll 实例中添加事件。
if(epoll_ctl(pq->epfd, EPOLL_CTL_ADD, fd, &ev) != 0){
(void) close(fd);
return (nni_plat_errno(errno);
}
6. 保存文件描述符并返回成功
将文件描述符 fd 保存到pq结构体的evfd 字段中。并返回成功。
pq->evfd = fd;
return (0);
总结一下就是:这段代码的功能是创建一个事件文件描述符,并将其添加到 epoll
实例中,以便在程序需要自我唤醒时能够使用该文件描述符触发 epoll
事件。主要步骤包括创建事件文件描述符、设置文件描述符属性、配置 epoll
事件并将其添加到 epoll
实例中。
6. nni_thr_init(&pq->thr, nni_posix_poll_thr, pq) 初始化
(待续)
7. 设置 pq->thr名称
8. 执行nni_thr_run函数