poll和select必须遍历被监视的文件描述符列表,当这个列表很大时,每次调用时的遍历时间成为瓶颈。epoll就是为了优化poll和select。
创建一个epoll实例:
#include
int epoll_create (int size)
成功时,创建了一个epoll实例,返回值为与该实例关联的文件描述符。这个文件描述符与真正的文件没有关系,仅为了后续调用使用epoll而创建。
size参数告诉内核需要监听的文件描述符数,但不是最大值,传递一个适当的近似值可带来性能的提升,但不需要给出确切数字。
函数出错时,返回-1,并将errno设为以下值:
1.EINVAL:size不是正数。
2.ENFILE:系统达到打开文件数的上限。
3.ENOMEN:没有足够内存完成此次操作。
一个标准调用如下:
int epfd;
epfd = epoll_create(100);
if (epfd < 0) {
perror("epoll_create");
}
epoll.create()返回的文件描述符需要调用close()关闭。
向epoll上下文中加入或删除文件描述符:
#include
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
其中epoll_event结构体定义在头文件sys/epoll.h头文件中:
struct epoll_event {
__u32 events; /* events */
union {
void *ptr;
int fd;
__u32 u32;
__u64 u64;
} data;
};
epoll_ctl调用成功将关联epoll实例epfd和文件描述符fd,参数op指定对fd要进行的操作,event参数描述epoll更具体的行为。
参数op的有效值:
1.EPOLL_CTL_ADD:将fd添加到epfd指定的epoll实例监听集中,监听event中定义的事件。
2.EPOLL_CTL_DEL:把fd从epfd的监听集中删除。
3.EPOLL_CTL_MOD:使用event改变已有fd上的监听行为。
参数event结构体中的events成员列出了在给定文件描述符上的监听事件,多个事件可用位或运算同时指定,以下为有效值:
1.EPOLLERR:文件出错。即使没有设置,此事件也被监听。
2.EPOLLET:在监听文件上开启边沿触发。默认为水平触发。
3.EPOLLHUP:文件被挂起。即使没有设置,此事件也被监听。
4.EPOLLIN:文件可读。
5.EPOLLONESHOT:产生一次事件并被处理后,文件不再被监听。必须用EPOLL_CTL_MOD指定新事件,以便重新监听文件。
6.EPOLLOUT:文件可写。
7.EPOLLPRI:高优先级的带外数据可读。
event参数结构体的data参数由用户使用,确定监听事件后,data被返回给用户,通常将event.data.fd设为fd,这样就知道哪个文件描述符触发了事件。
epoll_ctl成功时返回0,失败时返回-1,并将errno置为:
1.EBADF:epfd不是一个有效epoll实例,或fd不是一个有效文件描述符。
2.EEXIST:op为EPOLL_CTL_ADD时,但fd已与epfd关联。
3.EINVAL:epfd不是一个epoll实例、epfd和fd相同、op无效。
4.ENOENT:op是EPOLL_CTL_MOD,或EPOLL_CTL_DEL,但fd没有与epfd相关联。
5.ENOMEN:内存不足。
6.EPERM:fd不支持epoll。
向epfd实例中加入一个fd指定的监听文件:
struct epoll_event event;
int ret;
event.data.fd = fd // return the fd to us later
event.events = EPOLLIN | EPOLLOUT;
ret = epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);
if (ret) {
perror("epoll_ctl");
}
修改epfd实例中一个fd上的监听事件:
struct epoll_event event;
int ret;
event.data.fd = fd;
event.events = EPOLLIN;
ret = epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &event);
if (ret) {
perror("epoll_ctl");
}
从epfd中移除一个监听的fd:
struct epoll_event event;
int ret;
event.data.fd = fd;
event.events = EPOLLIN;
ret = epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &event); // 2.9.6之后的内核中,此处的event参数可为NULL,这样写是为了兼容性,该指针不会被访
if (ret) {
perror("epoll_ctl");
}
等待给定epoll实例关联的文件描述符上的事件:
#include
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
参数timeout为等待的ms数。成功返回时,参数events指向包含多个epoll_event的结构体的内存,最多可有maxevents个。返回值是事件数,出错返回-1,并将errno置为以下值:
1.EBADF:epfd是无效描述符。
2.EFAULT:进程对events指向的内存无写权限。
3.EINTR:系统调用在完成前被信号中断。
4.EINVAL:epfd不是有效epoll实例,或maxevents小于0。
timeout参数:
1.0:即使没有事件发生,也立即返回。
2.-1:永久等待。
完整的epoll_wait例子:
#define MAX_EVENTS 64
struct epoll_event *events;
int nr_events, i, epfd;
events = malloc(sizeof(struct epoll_event) * MAX_EVENTS);
if (!events) {
perror("malloc");
return 1;
}
nr_events = epoll_wait(epfd, events, MAX_EVENTS, -1);
if (nr_events < 0) {
perror("epoll_wait");
free(events);
return 1;
}
for (i = 0; i < nr_events; ++i) {
printf("event=%ld on fd=%d\n", events[i].events, events[i].data.fd);
}
free(events);
如果epoll_ctl函数的参数event的成员events设置为EPOLLET,则fd上的监听称为边沿触发,默认为水平触发。这两者的区别以生产者消费者通过管道通信时情景举例:
1.生产者向管道写入1kb数据。
2.消费者在管道上调用epoll_wait,等待pipe出现数据,从而可读。
对于水平触发的监听,步骤2里对epoll_wait的调用将立即返回,以表明pipe可读;对于边沿触发的监听,epoll_wait调用会在1发生后才返回,即使调用epoll_wait时管道已经可读,调用仍然会等待,直到有数据写入,之后才返回。
水平触发也是poll和select函数的行为。