63.4 epoll编程接口
同select()
、poll()
和信号驱动IO一样,Linux的epoll API可以检查多个文件描述符上的IO就绪状态。
- 当检查大量的文件描述符时,epoll 的性能延展性比select()和poll()高很多。
- epoll API既支持水平触发也支持边缘触发。
epoll API是Linux系统专有的,在2.6版中新增。
epoll API的核心数据结构称作epoll实例,它和一个文件描述符相关联,这个文件描述符是内核数据结构的句柄。这些内核数据结构实现了两个目的:
- 记录了在进程中声明过的感兴趣的文件描述符列表—interest list(兴趣列表)。
- 维护了处于I/O 就绪态的文件描述符列表—ready list(就绪列表)。
epoll API由以下3个系统调用组成:
- 系统调用
epoll_create()
创建一个epoll 实例,返回代表该实例的文件描述符。 - 系统调用
epoll_ctl()
操作同epoll 实例相关联的兴趣列表。通过参数指定宏完成不同操作。 - 系统调用
epoll_wait()
返回与epoll 实例相关联的就绪列表中的成员个数。值-参数返回对应信息。
63.4.1 创建epoll实例:epoll_create()
系统调用epoll_create()创建了一个新的epoll 实例,其对应的兴趣列表初始化为空。
#include <sys/epoll.h>
int epoll_create(int size);
// 成功返回新创建的epoll实例的文件描述符,失败返回-1并设置errno
-
参数
size
现已无意义,只要指定一个大于0的数字即可。 -
epoll_create()
返回了代表新创建的epoll实例的文件描述符,这个文件描述符在其它几个epoll系统调用中用来表示epoll实例。当这个文件描述符不再需要时,**应该通过close()
来关闭。**当所有与epoll 实例相关的文件描述符都被关闭时,实例被销毁,相关的资源都返还给系统。(有争议,TPLI P1114上说要关闭epollfd)。
63.4.2 修改epoll实例:epoll_ctl()
系统调用epoll_ctl()能够修改由文件描述符epfd 所代表的epoll 实例中的兴趣列表。
#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *ev);
// 成功返回0, 失败返回-1并设置errno
-
参数
fd
指明要修改兴趣列表中的哪一个文件描述符的设定。如果fd
是普通文件或目录的文件描述符,会设置EPERM
。 -
参数
op
用来指定需要执行的操作,如下:-
EPOLL_CTL_ADD
:将描述符fd 添加到epoll 实例epfd 中的兴趣列表中去。对于fd 上我们感兴趣的事件,都
指定在ev 所指向的结构体中。 -
EPOLL_CTL_MOD
:修改描述符fd 上设定的事件,需要用到由ev 所指向的结构体中的信息。 -
EPOLL_CTL_DEL
:将文件描述符fd 从epfd 的兴趣列表中移除。该操作忽略参数ev
。关闭一个文件描述符会自动将其从所有的epoll实例的兴趣列表中移除。(注意要看关闭的这个文件描述符的引用计数是否为0,为0才会被移除)。
-
-
参数ev是指向结构体
epoll_event
的指针,结构体定义如下:struct epoll_event{ uint32_t events; // epoll events(bits mask) epoll_data_t data; // User data } typedef union epoll_data{ void *ptr; int fd; uint32_t u32; uint64_t u64; } epoll_data_t;
events
字段是一个位掩码,它指定了我们为待检查的描述符fd
上所感兴趣的事件集合。data
字段是一个联合体,当描述符fd
稍后成为就绪态时,联合体的成员可用来指定传回给调用进程的信息。
63.4.3 事件等待:epoll_wait()
系统调用epoll_wait()返回epoll 实例中处于就绪态的文件描述符信息。
#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *evlist, int maxevents, int timeout);
// 成功返回数组evlist中的元素个数,失败返回-1并设置errno
- 参数
evlist
所指向的结构体数组中返回的是有关就绪文件描述符的信息,是一个**“值-参数”**。数组evlist
的空间由调用者负责申请,所包含的元素个数在参数maxevents
中指定。 - 返回的数组
evlist
中,每个元素返回的都是单个就绪态文件描述符的信息。events
字段返回了在该描述符上已经发生的事件掩码。data
字段返回的是我们在描述符上使用epoll_ctl
注册感兴趣的事件时在ev.data
中所指定的值。data
字段是唯一可获知同这个事件相关的文件描述符的途径,因此当我们调用epoll_ctl
时,应该要么将ev.data.fd
设为文件描述符号,要么将ev.data.ptr
设为指向包含文件描述符号的结构体。
- 参数
timeout
用来确定epoll_wait
的阻塞行为。timeout == -1
,调用将一直阻塞,直到兴趣列表中的文件描述符上有事件发生。timeout == 0
,执行一次非阻塞式的检查。timeout > 0
,调用将阻塞至多timeout
毫秒。
epoll事件
63.4.4 深入探究epoll的语义
当我们通过epoll_create()
创建一个epoll
实例时,内核在内存中创建了一个新的inode
并打开文件描述,随后在调用进程中为这个打开的文件描述分配一个新的文件描述符。同epoll
实例的兴趣列表相关联的是打开的文件描述或者说文件表象,而不是epoll
文件描述符。因此:dup()
和fork()
复制的文件描述符和原来的文件描述符所指向的epoll
数据结构是相同的。
63.4.5 epoll与select、poll的性能对比
epoll
性能好的原因:
- 每次调用
select()
和poll()
时,内核必须线性检查所有在调用中指定的文件描述符。而epoll
采用的的回调机制,每当执行IO操作使得文件描述符成为就绪态时,内核就在epoll
描述符的就绪列表中添加一个元素。 - 每次调用
select()
和poll()
时,我们都要传递一个标记了所有待监视的文件描述符的数据结构给内核。而epoll
中这是两个操作,epoll_ctl
可以修改兴趣列表,稍后每次调用epoll_wait
就不需要在传递任何与文件描述符有关的信息给内核了。 - 除此之外,
select()
每次调用前都要初始化输入数据。而且无论是select()
还是poll()
,我们都要对返回的数据结构做检查,以此找出这些文件描述符中有哪些是处于就绪态的。不过相比起来,系统同时监视N个文件描述符花费的时间大得多。
63.4.6 边缘触发通知
边缘触发通知通常和非阻塞的文件描述符结合使用。采用epoll
的边缘触发通知机制的程序框架如下:
- 将监视的文件描述符设置为非阻塞的。
- 通过
epoll_ctl()
构建epoll
的兴趣列表。 - 通过如下的循环处理IO事件。
- 调用
epoll_wait()
取得处于就绪态的描述符列表。 - 针对每一个处于就绪态的文件描述符,不断进行IO处理直到相关的系统调用返回
EAGAIN
或EWOULDBLOCK
错误。
- 调用