简介
一提到linux高性能服务器编程,epoll就是绕不开的话题,当前网络库在linux上实现也主要是以epoll为主。epoll的主要优点有:
- 当检查大量的文件描述符时,epoll的性能比select和poll要高很多。
- epoll 既支持水平触发也支持边沿触发。select 和 poll只支持水平触发,而信号驱动I/O只支持边缘触发。
- 避免复杂的信号处理流程
- 灵活性高,可以指定希望检查的事件类型
API
epoll由以下api构成:epoll_Create1, epollctl以及epoll_wait。
epoll_Create1
epoll_Create1 用于创建一个epoll实例,并返回代表该实例的文件描述符,epoll_Create1 支持传入一个可用来修改系统调用行为的flags参数,目前支持一个flag标志:EPOLL_CLOEXEC, 使得内核在新的文件描述符上启动执行即关闭标志。
epoll_ctl
epoll_ctl用来修改epoll实例中的兴趣列表,函数声明:
#include <sys/epoll.h> int epoll_ctl(int epfd, int op, int fd, struct epoll_event* ev); return 0 on success, or -1 on error 复制代码
参数简介:
- epfd - epoll实例
- op 指定需要执行的操作
操作 | 描述 |
---|---|
EPOLL_CTL_ADD | 将描述符fd添加到opell实例中的兴趣列表中,如果文件描述符已经存在,则epoll_ctl返回EEXIST错误 |
EPOLL_CTL_MOD | 修改描述符fd上设定的事件,如果描述符不存在,epoll_ctl返回ENOENT错误 |
EPOLL_CTL_DEL | 将描述符fd从epoll实例兴趣列表中删除,如果描述符不存在,epoll_ctl返回ENOENT错误, 关闭文件描述符会自动的将其从所有的epoll实例兴趣列表中移除 |
- fd - 文件描述符,可以是管道、FIFI、套接字、POSIX消息队列、终端、设备等,但不能是普通文件或目录的文件描述符(会出现EPERM错误)
- ev
ev 是指向结构体epoll_event的指针,结构体定义如下:
struct epoll_event{ uint32_t events; epoll_data_t data; }; 复制代码
events字段是一个位掩码, 表示描述符fd上感兴趣的事件集合。 data字段是一个联合体,联合体成员可以用来指定传回给调用进程的信息, 其类型为:
typedef union epoll_data{ void *ptr; int fd; uint32_t u32; uint64_t u64; }epoll_data_t; 复制代码
- max_user_watches 上限
因为每个注册epoll实例中的描述符需要占用一小段不能被交换的内核内存空间,因此, 内核提供了一个接口用来定义每个用户可以注册到epoll实例上的文件描述符总数,这个上限值可以通过max_user_watches来查看和修改。 max_user_watches是linux系统/proc/sys/fs/epoll目录下的一个文件,上限值可以根据可用的系统内存来计算得出。
epoll_wait
epoll_wait返回epoll实例中处于就绪状态的文件描述符信息,单次epoll调用可以返回多个就绪态文件描述符信息。
#include <sys/epoll.h> int epoll_wait(int epfd, struct epoll_event *evlist, int maxevents, int timeout); 复制代码
参数简介:
- epfd - epoll实例
- evlist - 指向包含就绪状态文件描述符的结构体数组,列表中每个元素代表就绪的文件描述符,events字段代表已经就绪的事件掩码,data字段是使用epoll_ctl注册事件时ev.data中所指定的值,也是同事件相关联的文件描述符。
- maxevents - evlist的个数
- timeout - 用来决定epoll_wait的行为:
timeout | epoll_wait的行为 |
---|---|
-1 | epoll_wait将一直阻塞,直到兴趣列表中的文件描述符有事件产生 |
0 | epoll_wait 执行一次非阻塞式的检查 |
>0 | epoll_wait 至多阻塞timeout毫秒 |
调用成功之后,epoll_wait返回数组evlist中的元素个数,如果超时或者没有文件描述符就绪,则返回0, 出错时返回-1,并在errno中设定错误码以表示错误原因。 可以在一个线程中使用epoll_ctl将文件描述符添加到另一个线程中由epoll_wait所监视的epoll实例的兴趣列表中,并且,修改的兴趣列表将立刻得到处理。epoll_wait也会返回新加入文件描述符的就绪信息。
epoll 事件
epoll事件可以通过ev.events中的位掩码指定,events字段上的位掩码值
位掩码 | 作为epoll_ctl的输入 | 作为epoll_wait的返回 | 描述 |
---|---|---|---|
EPOLLIN | 是 | 是 | 可读取非高优先级的数据 |
EPOLLPRI | 是 | 是 | 可读取高优先级数据 |
EPOLLRDHUP | 是 | 是 | 套接字对端关闭 |
EPOLLOUT | 是 | 是 | 普通数据可写 |
EPOLLET | 是 | 采用边缘触发事件通知 | |
EPOLLONESHOT | 是 | 完成事件通知之后禁用检查 | |
EPOLLERR | 是 | 有错误发生 | |
EPOLLHUP | 是 | 出现挂断 |
- EPOLLONESHOT 一旦描述符设置了该标志,epoll_wait只返回一次事件通知,然后将文件描述符标记为非激活状态,如果希望继续收到事件通知,需要使用epoll_ctl的EPOLL_CTL_MOD操作重新激活对这个文件描述符的检查,而不能使用EPOLL_CTL_ADD操作。
LT和ET模式
epoll有水平触发(LT)和边沿触发(ET)两种模式,默认的是水平触发。
- 水平触发:有事件就绪时,epoll_wait会一直返回该事件直到该事件被处理
- 边沿触发:有事件就绪时,epoll_wait只会返回一次,降低同一事件被重复触发的次数,效率比LT高。
要使用边沿触发模式,需要在调用epoll_ctl时在ev.events字段中指定EPOLLET标志。
ET模式下的文件描述符饥饿现象
当epoll工作在ET模式并监视多个文件描述符时,其中一个文件描述符有大量的输入存在,在该文件描述符处于就绪态后,我们尝试通过非阻塞式的读操作将所有的输入都读取,那么此时就会有其它处于就绪的文件描述符得不到处理的风险,而处于饥饿状态。
饥饿现象的解决方案
应用程序维护一个列表,列表中存放着已经被通知位就绪态的文件描述符,通过一个循环按照如下方式不断处理:
- 调用epoll_wait监视文件描述符,并将处于就绪状态的描述符添加到应用程序维护的列表中,如果这个文件描述符已经注册到应用程序维护列表中,那么监视的时间应该设为较小的值或者0,这样应用程序就可以迅速进行到下一步,去处理已经处于就绪态的文件描述符。
2、应用程序只在那些已经注册为就绪态文件描述符上进行一定限度的I/O操作,而不是每次epoll_wait调用后都从列表头开始处理,当相关的非阻塞I/O系统调用出现EAGAIN或EWOULDBLOCK错误时,文件描述符就可以从应用程序维护的列表中移除了。
作者:用户3541072051383
链接:https://juejin.cn/post/6926767244747735048