epoll
Event Poll
由于poll()和select()的局限,Linux2.6内核引入了event poll(epoll)机制。虽然epoll的实现比poll()和select()要复杂的多,epoll解决了前两个都存在的基本性能问题,并增加了一些新的特性
对于poll()和select()每次调用时都需要所有被监听的文件描述符列表。内核必须遍历所有被监视的文件描述符、当这个文件描述符列表变得很大时,每次调用都要遍历列表就编程规模上的瓶颈
epoll把监听注册从实际监听中分离出来,从而结局了这个问题。一个系统调用会初始化epoll上下文,另一个从上下文中加入或删除监视的文件描述符,第三个执行真正的等待事件。
创建新的epoll示例
通过epoll_create1()创建epoll上下文:
1 |
|
调用成功时,epoll_create1()会创建新的epoll实例,并返回和该实例关联的文件描述符。这个文件描述符和真正的文件没有关系,仅仅是为了后续调用epoll而创建的
参数flags支持修改epoll的行为,当前,只有EPOLL_CLOEXEC是个合法的flag,他表示进程被替换时关闭文件描述符
出错时,返回-1,并设置errno为下列值之一:
EINVAL : 参数flags非法
EMFILE : 用户打开的文件数达到上限
ENFILE : 系统打开的文件数达到上限
ENOMEN : 内存不足,无法完成本次操作
epoll_create()时老版本的epoll_create1()的实现,现已废弃。
epoll的标准调用方式如下:
1 |
|
当完成监视后,epoll_create1()返回的文件描述符需要用close()调用关闭
控制epoll
epoll_ctl()函数可以向指定的epoll上下文中加入或删除文件描述符:
1 |
|
头文件<sys/epoll.h>中定义了epoll_event结构体:
1 |
|
epoll_ctl()调用如果执行成功,会控制和文件描述符epfd关联的epoll实例。参数op指定对fd指向的文件所执行的操作。参数event进一步描述epoll更具体的行为
以下是参数op的有效值:
EPOLL_CTL_ADD : 把文件描述符fd所指向的文件添加到epfd指定的epoll监听实例集中,监听event中定义的事件
EPOLL_CTL_DEL : 把文件描述符fd所指向的文件冲epfd指定的epoll监听实例集中删除
EPOLL_CTL_MOD : 使用event指定的更新事件修改在已有fd上的监听行为
epoll_events结构体中的events变量列出了在指定文件描述符上要监听的事件。多个事件可以通过位或运算同时指定。以下是events的有效值:
EPOLLERR : 文件出错。即使没设置,这个事件也被监听
EPOLLET : 在监听文件上开启边缘触发。默认是条件触发
EPOLLHUP : 文件被挂起。即使没设置,这个事件也被监听
EPOLLIN : 文件为阻塞,可读
EPOLLONESHOT : 在事件生成并处理后,文件不会再被监听。必须通过EPOLL_CTL_MOD指定新的事件掩码,以便重新监听文件
EPOLLOUT : 文件为阻塞,可写
EPOLLPRI : 存在高优先级的带外数据可读
event_poll 中的data变量是由用户私有使用。当接收到请求事件后,data会被返回给用户。通常用法是把event.data.fd设置为fd,这样可以很容易查看哪个文件描述符触发了事件。
当成功时,epoll_ctl()返回0。失败时,返回-1,并设置errno为下值:
EBADF : epfd不是有效的epoll实例,或者fd不是有效的文件描述符
EEXIST : op值设置为EPOLL_CTL_ADD,但fd已经与epfd关联
EINVAL : epfd不是epoll实例,epfd和fd相同,或op无效
ENOENT : op值设置为EPOLL_CTL_MOD或EPOLL_CTL_DEL,但是fd没有和epfd关联
ENFILE : 系统打开的文件数达到上限
ENOMEN : 内存不足,无法完成本次操作
EPERM : fd不支持epoll
在下面的例子中,在epoll实例epfd中加入fd所指向文件的监听事件,代码如下:
1 |
|
修改epfd实例中的fd上的一个监听事件,代码如下:
1 |
|
相反,从epoll实例中删除在fd上的一个监听事件,代码如下:
1 |
|
注意,当op设置为EPOLL_CTL_DEL时,由于没有提供事件掩码,event参数可能为NULL,在2.6.9以前的内核中,会检查该参数是否非空,为了和老版本兼容,必须传递一个有效的非空指针,不能只是声明。内核2.6.9版本修复了这个bug
等待epoll事件
系统调用epoll_wait()会等待和指定epoll实例关联的文件描述符上的事件:
1 |
|
当调用epoll_wait()时,等待epoll实例epfd中的文件fd上的事件,时限为timeout毫秒。成功时,events指向描述每个事件的epoll_event结构体的内存,且最多可以有maxevents个事件,返回值是事件个数;出错时,返回-1,并将errno设置为下值:
EBADF : epfd是一个无效的文件描述符
EFAULT : 进程对events所指向的内存没有写权限
EINTR : 系统调用在完成前发生信号中断或超时
EINVAL : epfd不是有效的epoll实例,或者maxevents小于或等于0
如果timeout为0,即使没有事件发生,调用也会立即返回0.如果timeout为-1,调用将一直等待到有事件发生才返回
当调用返回时,epoll_event结构体中的events变量描述了发生的事件。data变量保留了用户在调用epoll_ctl()前的所有内容
一个完整的例子如下:
1 |
|
边缘触发事件和条件触发事件
如果epoll_ctrl()的参数event中的events项设置为EPOLLET,fd上的监听方式为边缘触发(ET),否则为条件触发(LT)
考虑下面的生产者和消费者在通过UNIX管道通信时的情况
- 生产者向管道写入1KB数据
- 消费者在管道上调用epoll_wait(),等待管道上有数据并可读
通过条件触发监视时,在步骤2中epoll_wait()会立刻返回,表示管道可读。通过边缘触发监视时,需要在步骤1发生后,步骤2才会返回
条件触发是默认行为,poll()和select()就是采用这种模式,也是大多数开发者所期望的。边缘触发需要不同的编程解决方案,通常是非阻塞I/O,而且需要仔细检查EAGAIN