通过 IO 多路复用技术,系统内核缓冲 IO 数据,当某个 IO 准备好后,系统通知应用程序该 IO 可读或可写,这样应用程序可以马上完成相应的 IO 操作,而不需要等待系统完成相应 IO 操作,从而应用程序不必因等待 IO 操作而阻塞。
这里简单介绍下 Linux 中 IO 多路复用的使用。
简介
在 Linux 中,会将很多资源通过文件描述符表示,包括了文件系统、网络、物理设备等等。
文件描述符就是一个整数,默认会选择最小未使用的数值,其中有三个比较固定,比较特殊的值,0 是标准输入,1 是标准输出,2 是标准错误输出。
0、1、2 是整数表示的,对应的 FILE* 结构的表示就是 stdin、stdout、stderr。
select
select 可以用来监视多个文件句柄的状态变化,程序会阻塞在 select 直到被监视的文件句柄有一个或多个发生了状态改变,然后通知应用程序,应用程序 轮询 所有的 FD 集合,判断监控的 FD 是否有事件发生,并作相应的处理。
select 相关的声明和宏,通过宏来设置参数,相应的执行流程如下。
int select(int maxfd, fd_set * readfds, fd_set * writefds, fd_set * exceptfds, struct timeval * timeout);
FD_CLR(inr fd, fd_set* set); // 清除描述词组set中相关fd的位
FD_ISSET(int fd, fd_set *set); // 用来测试描述词组set中相关fd的位是否为真
FD_SET(int fd, fd_set*set); // 用来设置描述词组set中相关fd的位
FD_ZERO(fd_set *set); // 用来清除描述词组set的全部位
struct timeval {
time_t tv_sec;
time_t tv_usec;
};
----- 调用流程
fd_set readfso; // 1.1 定义需要监视的描述符
FD_ZERO(&readfso); // 1.2 清空
FD_SET(fd, &readfso); // 1.3 设置需要监控的描述符,如fd=1
while(1) {
readfs = readfso;
ret = select(maxfd+1, &readfds, NULL, NULL, timeout); // 2.1 监听所关注的事件,在此为监听读事件
if (ret > 0) // 3.1.1 监听事件发生
for (i = 0; i > FD_SETSIZE; i++) // 3.1.2 需要遍历所有fd
if (FD_ISSET(fd, &readfds))
handleEvent();
else if (ret == 0) // 3.2 超时
handle timeout
else if (ret < 0) // 3.3 发生错误
handle error
}
在 select 函数中,各个参数的含义如下。
- maxfd 为最大文件描述符+1,因此在程序中需要知道最大描述符的值。
- 接着的三个参数分别设置所监听的读、写、异常事件对应的描述符。该值传入需要监控事件的描述符,返回值保存了发生了的事件对应的描述符。
- 设置超时时间,NULL 表示一直等待。
返回值:成功则返回文件描述符状态已改变的总数;如果返回 0 代表在描述词状态改变前已超过 timeout 时间;当有错误发生时则返回 -1,错误原因存于 errno ,此时参数 readfds, writefds, exceptfds 和 timeout 的值变成不可预测。
在有返回值时,需要通过 FD_ISSET 循环遍历各个描述符,从而文件描述符越多,效率也就越低。而且,每次调用 select 时都需要重新设置需要监控的文件描述符。
fd_set 在 sys/select.h
中定义,大致可以简化为如下的结构,也就是实际用数组表示,其中每一位代表一个文件描述符,最大可以表示 1024 个,也就是说 select 能监视的描述符最大为 1024 。最大可以监控的文件描述符数可以通过 FD_SETSIZE 获得,描述符需要通过对应的宏来配置。
typedef struct {
long int __fds_bits[__FD_SETSIZE(1024) / __NFDBITS(8 * sizeof(long int))];
} fd_set;
poll
和 select() 不一样,poll() 没有使用低效的三个基于位的文件描述符 set ,而是采用了一个单独的结构体 pollfd 数组,由 fds 指针指向这个数组,采用链表保存文件描述符。
struct pollfd {
int fd; /* 文件描述符 */
short events; /* 等待的事件 */
short revents; /* 实际发生了的事件 */
} ;
typedef unsigned long nfds_t;
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
struct pollfd fds[MAX_CONNECTION] = {0};
fds[0].fd = 0;
fds[0].events = POLLIN;
ret = poll(fds, sizeof(fds)/sizeof(struct pollfd), -1)
if (ret > 0)
for (int i = 0; i < MAX_CONNECTION; i++)
if (fds[0].revents & POLLIN)
handleEvent(allConnection[i]);
else if (ret == 0)
handle timeout
else if (ret > 0)
handle error
fds 表示需要监控的文件描述符;nfds 表示 fds 的大小,也即需要监控的描述符数量。如果 fd 为负,则忽略 events,revents 返回 0。
返回值与 select 的返回值含义相同。
epoll
epoll 是 Linux 内核为处理大批量句柄而作了改进的 poll,是 Linux 下多路复用 IO 接口 select/poll 的增强版本,它能显著减少程序在大量并发连接中只有少量活跃的情况下的系统 CPU 利用率。
对比
select 模型的缺点:
- 最大并发数限制,因为一个进程所打开的文件描述符是有限制的,由 FD_SETSIZE 设置,默认值是 1024/2048 ,因此 select 模型的最大并发数就被相应限制了。
- 效率问题,每次 select 调用返回都需要线性扫描全部的 FD 集合,确定那个描述符发生了相应事件,这样效率就会呈现线性下降。
- 内核/用户空间内存拷贝问题,在通知用户事件发生时采用内存拷贝。
- 触发方式采用的是水平触发,应用程序如果没有完成对一个已经就绪的文件描述符的 IO 操作,那么之后每次 select 调用还是会将这些文件描述符通知进程。
对于 poll 而言,2 和 3 都没有改掉,相比来说,epoll 要好的多。
- epoll 没有最大并发连接的限制,上限是最大可以打开文件的数目,这个数字一般远大于 2048, 一般来说这个数目和系统内存关系很大 ,具体数目可以在 /proc/sys/fs/epoll/max_user_watches 中查看。
- 效率提升,epoll 最大的优点就在于它只管你“活跃”的连接,而跟连接总数无关,因此在实际的网络环境中,epoll 的效率就会远远高于 select 和 poll。
- 内存拷贝,epoll 在这点上使用了“共享内存”,这个内存拷贝也省略了。mmap 加速内核与用户空间的信息传递,epoll 是通过内核于用户空间 mmap 同一块内存,避免了无谓的内存拷贝。
函数接口
epoll 相关的函数主要有如下三种,分别用于创建,注册、修改、删除,等待事件发生。
创建
创建 epoll 文件描述符,int epoll_create (int size)
。
需要注意的是,当创建好 epoll 句柄后,它就是会占用一个 fd 值,在 linux 下如果查看 /proc/<PID>/fd/
,是能够看到这个 fd 的,所以在使用完 epoll 后,必须调用 close() 关闭,否则可能导致 fd 被耗尽。
调用成功则返回与实例关联的文件描述符,该文件描述符与真实的文件没有任何关系,仅作为接下来调用的函数的句柄,实际就是申请一个内核空间,用来保存所关注的 fd 上发生的时将。size 为了与之前兼容,应该大于 0 ,现在是动态分配,而非指定支持事件的数目。正常返回值大于 0;发生错误时,返回 -1,errno 表明错误类型。
修改
控制文件描述符,int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
。
类似于相对于 select 模型中的 FD_SET 和 FD_CLR 宏,用于注册、修改、删除;与 select 不同的是,select 在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。
其中 参数 epfd 是 epoll_create() 创建 epoll 专用的文件描述符;op 用于表示对应的操作(EPOLL_CTL_ADD、EPOLL_CTL_MOD、EPOLL_CTL_DEL);fd 表示需要监控的描述符;event 表示需要监控的事件。
等待
等待事件 int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
,内核通过 events 返回发生事件的集合,maxevents 告之内核最多可以返回的事件数,该函数返回需要处理的事件数目,如返回 0 表示已超时。
等待 IO 事件的发生,epfd 是由 epoll_create() 生成的 epoll 专用的文件描述符;epoll_event 用于回传事件发生对应的数组;maxevents 表示能处理的最多的事件数;timeout 等待 IO 事件发生的超时值,-1 表示永久。
epoll_wait() 首先判断参数的有效性,最后会调用 ep_epoll() 函数。epoll 不仅会告诉应用程序有 IO 事件到来,还会告诉应用程序相关的信息,这些信息是应用程序填充的,因此根据这些信息应用程序就能直接定位到事件,而不必遍历整个 FD 集合。
epoll() 的使用
如下是 epoll 使用的数据结构,执行过程。
struct epoll_event {
__uint32_t events; // Epoll events
epoll_data_t data; // User data variable
};
typedef union epoll_data {
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;
struct epoll_event event;
struct epoll_event *events;
int efd = epoll_create(1); // 1. 创建文件描述符,大于0即可,实际内核中动态分配
if (efd == -1 || error == 0)
error
event.data.fd = fd; // 2.1 设置需要监听的fd
event.events = EPOLLIN | EPOLLET; // 2.2 设置需要监听的事件
ret = epoll_ctl(efd, EPOLL_CTL_ADD, 0, &event) // 2.3 可以添加、修改、删除
if (ret == -1)
error
events = calloc(MAX_EVENTS, sizeof event); // 3.1 申请内存,可返回的最大事件数
while (1) {
n = epoll_wait(efd, events, MAX_EVENTS, -1); // 3.2 等待事件发生
if (n == 0) // 3.3 超时
timeout
else if (n < 0) // 3.4 发生错误
if(EINTT == errno) // 3.5 由于中断,忽略
n = 0; continue;
else
error
else // 3.6 处理发生的事件
for(i = 0; i < n; i++)
if (events[i].data.fd == fd)
handleEvent
else if (enents[i].events & EPOLLIN) // 某些事件发生
//
}
free(events);
close(fd);
结构体 epoll_event 用于注册所感兴趣的事件和回传所发生待处理的事件。epoll_data 联合体用来保存触发事件相关的描述符所对应的信息,可以保存很多类型的信息,如 fd 、指针等等,有了它,应用程序就可以直接定位目标了。
对于 epoll_data ,如一个 client 连接到服务器时,服务器通过调用 accept 函数可以得到 client 对应的 socket 文件描述符,并通过 epoll_data 的 fd 字段返回。
epoll_event 结构体的 events 字段是表示感兴趣的事件和被触发的事件,可能的取值为:
EPOLLIN :对应的文件描述符可以读;
EPOLLOUT:对应的文件描述符可以写;
EPOLLPRI:对应的文件描述符有紧急的数据可读;
EPOLLERR:对应的文件描述符发生错误;
EPOLLHUP:对应的文件描述符被挂断;
EPOLLET :对应的文件描述符有事件发生。
内核实现
如下是与 epoll 相关的结构设计,先看下与结构体相关的结构图。
其中 struct epitem
是 epoll 的基本单元,对于每一个事件,都会建立一个 epitem 结构体,下面分别介绍一下几个主要的变量的含义:
struct epitem{
struct rb_node rbn; // 红黑树节点
struct list_head rdllink; // 双向链表节点,当epitem对应fd已经ready时,会在ep_poll_callback()函数中将该
// 结点链接到eventpoll中的rdllist循环链表中去,这样就将ready的epitem都串连起来了
struct epoll_filefd ffd; // 事件句柄信息,包含了一个fd以及fd对应的file指针
struct eventpoll *ep; // 指向其所属的eventpoll对象,用于获取与epitem对应的eventpoll
struct epoll_event event; // 期待发生的事件类型,对应了epoll_ctl()中的指针,用于存储用户空间的epoll_event拷贝
}
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
当某一进程调用 epoll_create()
方法时,内核会创建一个 eventpoll
结构体,这个结构体中有两个成员与 epoll 的使用方式密切相关。
struct eventpoll{
struct rb_root rbr; // 红黑树的根节点,这颗树中存储着所有添加到epoll中的需要监控的事件
// 它的结点都为epitem变量,通过它可以很方便的增删改查epitem
struct list_head rdlist; // 双链表中则存放着将要通过epoll_wait返回给用户的满足条件的事件
// 链表中的每个结点即为epitem中的rdllink
};
每一个 epoll 对象都有一个独立的 eventpoll
结构体,用于存放通过 epoll_ctl()
方法向 epoll 对象中添加进来的事件,这些事件都会挂载在红黑树中。
而所有添加到 epoll 中的事件都会与设备+网卡驱动程序建立回调关系,也就是说,当相应的事件发生时会调用这个回调方法。这个回调方法在内核中就是 ep_poll_callback()
它会将发生的事件添加到 rdlist 双链表中。
接着再看下 struct eppoll_entry
结构体,它主要有这样几个变量:
struct eppoll_entry {
struct epitem *base; // 指向其对应的epitem
wait_queue_t wait; // 等待队列的项,wait中有一个唤醒回调函数指针,该指针被初始化为ep_poll_callback
};
如上的 wait 成员会被挂在到设备的等待队列中,等待设备的唤醒,当设备因状态改变唤醒 wait 时,会执行 ep_poll_callback
,而该函数会做这样一件事 list_add_tail(&epi->rdllink,&ep->rdllist)
。
初始化
epoll 的几个接口实际都对应于 kernel 的 API ,主要位于 fs/eventpoll.c
文件中。在分析 epoll 时发现有 fs_initcall()
这样的调用,以此为例分析一下 Linux 的初始化。
fs_initcall(eventpoll_init); // fs/eventpoll.c
#define fs_initcall(fn) __define_initcall(fn, 5) // include/linux/init.h
#define __define_initcall(fn, id) \ // 同上
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" #id ".init"))) = fn
// 最后展开为
static initcall_t __initcall_eventpoll_init5 __used
__attribute__((__section__(".initcall5.init"))) = eventpoll_init;
也就是在 .initcall5.init
段中定义了一个变量 __initcall_eventpoll_init5
并将改变量赋值为 eventpoll_init
,内核中对初始化的调用过程如下。
arch/x86/kernel/head_64.S
|-x86_64_start_kernel() arch/x86/kernel/head[64|32].c
|-start_kernel() init/main.c
|-rest_init()
|-kernel_init() 通过内核线程实现
|-kernel_init_freeable()
|-do_basic_setup()
|-do_initcalls()
对于 epoll 来说,实际是在初始化过程中对变量进行初始化。
static int __init eventpoll_init(void)
{
struct sysinfo si;
si_meminfo(&si);
/*
* Allows top 4% of lomem to be allocated for epoll watches (per user).
*/
max_user_watches = (((si.totalram - si.totalhigh) / 25) << PAGE_SHIFT) /
EP_ITEM_COST;
BUG_ON(max_user_watches < 0);
/*
* Initialize the structure used to perform epoll file descriptor
* inclusion loops checks.
*/
ep_nested_calls_init(&poll_loop_ncalls);
/* Initialize the structure used to perform safe poll wait head wake ups */
ep_nested_calls_init(&poll_safewake_ncalls);
/* Initialize the structure used to perform file's f_op->poll() calls */
ep_nested_calls_init(&poll_readywalk_ncalls);
/*
* We can have many thousands of epitems, so prevent this from
* using an extra cache line on 64-bit (and smaller) CPUs
*/
BUILD_BUG_ON(sizeof(void *) <= 8 && sizeof(struct epitem) > 128);
/* Allocates slab cache used to allocate "struct epitem" items */
epi_cache = kmem_cache_create("eventpoll_epi", sizeof(struct epitem),
0, SLAB_HWCACHE_ALIGN | SLAB_PANIC, NULL);
/* Allocates slab cache used to allocate "struct eppoll_entry" */
pwq_cache = kmem_cache_create("eventpoll_pwq",
sizeof(struct eppoll_entry), 0, SLAB_PANIC, NULL);
return 0;
}
主要是进行一些初始化配置,同时创建了 2 个内核 cache 用于存放 epitem 和 epoll_entry 。
创建对象
通过 epoll_create()
创建一个 epoll 实例,同时创建并初始化一个 struct eventpoll
,其中返回值 epfd 所对应的 file 的 private_data
指针即指向了 eventpoll
变量,因此,知道 epfd 就可以拿到 file,即拿到了 eventpoll
变量。
SYSCALL_DEFINE1(epoll_create, int, size) // epoll_create函数带一个整型参数
{
if (size <= 0)
return -EINVAL;
return sys_epoll_create1(0); // 实际上是调用epoll_create1
}
/* Open an eventpoll file descriptor. */
SYSCALL_DEFINE1(epoll_create1, int, flags)
{
int error;
struct eventpoll *ep = NULL;
/* Check the EPOLL_* constant for consistency. */
BUILD_BUG_ON(EPOLL_CLOEXEC != O_CLOEXEC);
if (flags & ~EPOLL_CLOEXEC)
return -EINVAL;
/* Create the internal data structure ("struct eventpoll"). */
error = ep_alloc(&ep); // 分配eventpoll结构体
if (error < 0)
return error;
/*
* Creates all the items needed to setup an eventpoll file. That is,
* a file structure and a free file descriptor.
*/
// 创建与eventpoll结构体相对应的file结构,ep保存在file->private_data结构中,其中
// eventpoll_fops 为该文件所对应的操作函数
error = anon_inode_getfd("[eventpoll]", &eventpoll_fops, ep, O_RDWR | (flags & O_CLOEXEC));
if (error < 0)
ep_free(ep); // 如果出错则释放该eventpoll结构体
return error;
}
anon_inode_getfd()
创建与 struct eventpoll
对应的 file 结构,其中 ep 保存在 file->private_data
结构中,同时为该新文件定义操作函数。
从这几行代码可以看出,epoll_create()
主要做了两件事:
- 创建并初始化一个
struct eventpoll
变量; - 创建 epoll 的 file 结构,并指定 file 的
private_data
指针指向刚创建的eventpoll
变量,这样,只要根据epoll
文件描述符 epfd 就可以拿到 file 进而就拿到了eventpoll
变量,该eventpoll
就是epoll_ctl()
和epoll_wait()
工作的场所。
对外看来,epoll_create()
就做了一件事,那就是创建一个 epoll 文件,事实上,更关键的是,它创建了一个 struct eventpoll
变量,该变量为 epoll_ctl()
和 epoll_wait()
的工作打下了基础。
添加监控
epoll_ctl()
主要是针对 epfd 所对应的 epoll 实例进行增、删、改操作,一个新创建的 epoll 文件带有一个 struct eventpoll
,同时该结构体上再挂一个红黑树,红黑树上的每个节点挂的是 struct epitem
,这个红黑树就是每次 epoll_ctl()
时 fd 存放的地方。
/*
* epfd为该epoll套接字实例,op表示对应的操作,fd表示新加入的套接字,
* 结构体epoll_event 用于注册fd所感兴趣的事件和回传在fd上所发生待处理的事件
*/
SYSCALL_DEFINE4(epoll_ctl, int, epfd, int, op, int, fd,
struct epoll_event __user *, event)
{
int error;
int did_lock_epmutex = 0;
struct file *file, *tfile;
struct eventpoll *ep;
struct epitem *epi;
struct epoll_event epds;
error = -EFAULT;
// 将用户传入的event_poll拷贝到epds中
if (ep_op_has_event(op) && copy_from_user(&epds, event, sizeof(struct epoll_event)))
goto error_return;
/* Get the "struct file *" for the eventpoll file */
error = -EBADF;
file = fget(epfd); // 获取该epoll套接字实例所对应的文件描述符
if (!file)
goto error_return;
/* Get the "struct file *" for the target file */
tfile = fget(fd);
if (!tfile)
goto error_fput;
/* The target file descriptor must support poll */
error = -EPERM;
if (!tfile->f_op || !tfile->f_op->poll)
goto error_tgt_fput;
/*
* We have to check that the file structure underneath the file descriptor
* the user passed to us _is_ an eventpoll file. And also we do not permit
* adding an epoll file descriptor inside itself.
*/
error = -EINVAL;
if (file == tfile || !is_file_epoll(file))
goto error_tgt_fput;
/*
* At this point it is safe to assume that the "private_data" contains
* our own data structure.
*/
ep = file->private_data; // 获取epoll实例所对应的eventpoll结构体
/*
* When we insert an epoll file descriptor, inside another epoll file
* descriptor, there is the change of creating closed loops, which are
* better be handled here, than in more critical paths.
*
* We hold epmutex across the loop check and the insert in this case, in
* order to prevent two separate inserts from racing and each doing the
* insert "at the same time" such that ep_loop_check passes on both
* before either one does the insert, thereby creating a cycle.
*/
if (unlikely(is_file_epoll(tfile) && op == EPOLL_CTL_ADD)) {
mutex_lock(&epmutex);
did_lock_epmutex = 1;
error = -ELOOP;
if (ep_loop_check(ep, tfile) != 0)
goto error_tgt_fput;
}
mutex_lock(&ep->mtx);
/*
* Try to lookup the file inside our RB tree, Since we grabbed "mtx"
* above, we can be sure to be able to use the item looked up by
* ep_find() till we release the mutex.
* ep_find即从ep中的红黑树中根据tfile和fd来查找epitem
*/
epi = ep_find(ep, tfile, fd);
error = -EINVAL;
switch (op) {
case EPOLL_CTL_ADD: // 对应于socket上事件注册
if (!epi) { // 红黑树中不存在这个节点
// 确保"出错、连接挂起"被当做事件,将出错信息返回给应用
epds.events |= POLLERR | POLLHUP;
error = ep_insert(ep, &epds, tfile, fd);
} else
error = -EEXIST;
break;
case EPOLL_CTL_DEL: // 删除
if (epi) // 存在则删除这个节点,不存在则报错
error = ep_remove(ep, epi);
else
error = -ENOENT;
break;
case EPOLL_CTL_MOD: // 修改
if (epi) { // 存在则修改该fd所对应的事件,不存在则报错
epds.events |= POLLERR | POLLHUP;
error = ep_modify(ep, epi, &epds);
} else
error = -ENOENT;
break;
}
mutex_unlock(&ep->mtx);
error_tgt_fput:
if (unlikely(did_lock_epmutex))
mutex_unlock(&epmutex);
fput(tfile);
error_fput:
fput(file);
error_return:
return error;
}
对于往 epoll 实例中添加新的套接字,其实现主要通过 ep_insert()
完成,先分析 epoll_wait
再回过头来分析 ep_insert()
。
等待事件
epoll_wait()
用来等待 epoll 文件上的 IO 事件发生,其代码如下:
/*
* Implement the event wait interface for the eventpoll file. It is the kernel
* part of the user space epoll_wait(2).
*/
SYSCALL_DEFINE4(epoll_wait, int, epfd, struct epoll_event __user *, events,
int, maxevents, int, timeout)
{
int error;
struct file *file;
struct eventpoll *ep;
/* The maximum number of event must be greater than zero */
if (maxevents <= 0 || maxevents > EP_MAX_EVENTS)
return -EINVAL;
/* Verify that the area passed by the user is writeable */
if (!access_ok(VERIFY_WRITE, events, maxevents * sizeof(struct epoll_event))) {
error = -EFAULT;
goto error_return;
}
/* Get the "struct file *" for the eventpoll file */
error = -EBADF;
file = fget(epfd);
if (!file)
goto error_return;
/*
* We have to check that the file structure underneath the fd
* the user passed to us _is_ an eventpoll file.
*/
error = -EINVAL;
if (!is_file_epoll(file))
goto error_fput;
/*
* At this point it is safe to assume that the "private_data" contains
* our own data structure.
*/
ep = file->private_data; // 获取struct eventpoll结构
/* Time to fish for events ... */
error = ep_poll(ep, events, maxevents, timeout); // 核心代码
error_fput:
fput(file);
error_return:
return error;
}
可以看出该函数主要时通过 epfd 获取对应的 struct eventpoll
结构,然后调用 ep_poll()
函数,下面来看 ep_poll()
的实现。
/**
* ep_poll - Retrieves ready events, and delivers them to the caller supplied
* event buffer.
*
* @ep: Pointer to the eventpoll context.
* @events: Pointer to the userspace buffer where the ready events should be
* stored.
* @maxevents: Size (in terms of number of events) of the caller event buffer.
* @timeout: Maximum timeout for the ready events fetch operation, in
* milliseconds. If the @timeout is zero, the function will not block,
* while if the @timeout is less than zero, the function will block
* until at least one event has been retrieved (or an error
* occurred).
*
* Returns: Returns the number of ready events which have been fetched, or an
* error code, in case of error.
*/
static int ep_poll(struct eventpoll *ep, struct epoll_event __user *events,
int maxevents, long timeout)
{
int res = 0, eavail, timed_out = 0;
unsigned long flags;
long slack = 0;
wait_queue_t wait;
ktime_t expires, *to = NULL;
/* The call waits for a maximum time of timeout milliseconds.
* Specifying a timeout of -1 makes epoll_wait() wait indefinitely,
* while specifying a timeout equal to zero makes epoll_wait()
* to return immediately even if no events are available (return
* code equal to zero).
*/
if (timeout > 0) {
struct timespec end_time = ep_set_mstimeout(timeout);
slack = select_estimate_accuracy(&end_time);
to = &expires;
*to = timespec_to_ktime(end_time);
} else if (timeout == 0) {
/*
* Avoid the unnecessary trip to the wait queue loop, if the
* caller specified a non blocking operation.
*/
timed_out = 1;
spin_lock_irqsave(&ep->lock, flags);
goto check_events;
}
fetch_events:
spin_lock_irqsave(&ep->lock, flags);
// 如果rdllist中还没有epitem时,就开始等待了
if (!ep_events_available(ep)) {
/*
* We don't have any available event to return to the caller.
* We need to sleep here, and we will be wake up by
* ep_poll_callback() when events will become available.
*/
// 初始化等待队列,等待队列项对应的线程即为当前线程
init_waitqueue_entry(&wait, current);
// 先将当前线程挂到等待队列上,之后在调用schedule_timeout时,就开始了超时等待了
__add_wait_queue_exclusive(&ep->wq, &wait);
for (;;) {
/*
* We don't want to sleep if the ep_poll_callback() sends us
* a wakeup in between. That's why we set the task state
* to TASK_INTERRUPTIBLE before doing the checks.
*/
// 因为会被阻塞,这里先设置线程状态为可中断
set_current_state(TASK_INTERRUPTIBLE);
// 整个循环的核心,其实就在看rdllist中是否有数据,或者等待超时
// 应征了前面的说明,epoll_wait只需要等着收集数据即可
if (ep_events_available(ep) || timed_out)
break;
if (signal_pending(current)) {
res = -EINTR;
break;
}
spin_unlock_irqrestore(&ep->lock, flags);
if (!schedule_hrtimeout_range(to, slack, HRTIMER_MODE_ABS))
timed_out = 1;
spin_lock_irqsave(&ep->lock, flags);
}
__remove_wait_queue(&ep->wq, &wait);
set_current_state(TASK_RUNNING);
}
check_events:
/* Is it worth to try to dig for events ? */
eavail = ep_events_available(ep);
spin_unlock_irqrestore(&ep->lock, flags);
/*
* Try to transfer events to user space. In case we get 0 events and
* there's still timeout left over, we go trying again in search of
* more luck.
*/
if (!res && eavail &&
!(res = ep_send_events(ep, events, maxevents)) && !timed_out)
goto fetch_events;
return res;
}
调用 epoll_wait()
检查是否有事件发生时,只需检查 eventpoll
中 rdlist
链表是否有 epitem
元素即可。如果 rdlist 不为空,则把发生的事件复制到用户态,同时将事件数量返回给用户。