EPOLL - Linux手册页

时间:2019-08-20 18:01:53  来源:igfitidea点击:

Linux程序员手册 第7部分
更新日期: 2019-03-06

名称

epoll-I / O事件通知工具

语法

#包括

说明

epoll API执行与poll(2)类似的任务:监视多个文件描述符以查看其中的任何一个是否可以进行I / O。 epoll API可以用作边缘触发或级别触发的接口,并且可以很好地扩展到大量监视的文件描述符。

epoll API的中心概念是epoll实例,这是一个内核内数据结构,从用户空间的角度来看,可以将其视为两个列表的容器:

*
兴趣列表(有时也称为epoll集):进程已在监视中注册了兴趣的文件描述符集。
*
就绪列表:已为I / O准备的文件描述符集。准备列表是兴趣列表中文件描述符的子集(或更精确地,是对它们的一组引用)。由于这些文件描述符上的I / O活动,内核会动态填充就绪列表。

提供了以下系统调用来创建和管理epoll实例:

*
epoll_create(2)创建一个新的epoll实例,并返回引用该实例的文件描述符。 (最新的epoll_create1(2)扩展了epoll_create(2)的功能。)
*
然后,通过epoll_ctl(2)注册对特定文件描述符的兴趣,这会将项目添加到epoll实例的兴趣列表中。
*
epoll_wait(2)等待I / O事件,如果当前没有可用的事件,则阻塞调用线程。 (可以将此系统调用视为从epoll实例的就绪列表中获取项目。)

Level-triggered and edge-triggered

epoll事件分发接口既可以充当边缘触发(ET),也可以充当水平触发(LT)。两种机制之间的差异可以描述如下。假设发生这种情况:

1.
代表管道(rfd)读取端的文件描述符在epoll实例上注册。
2.
管道编写器在管道的写入端写入2 kB数据。
3.
完成对epoll_wait(2)的调用,它将返回rfd作为就绪文件描述符。
4.
管道读取器从rfd读取1 kB数据。
5.
epoll_wait(2)的调用已完成。

如果已使用EPOLLET(边缘触发)标志将rfd文件描述符添加到epoll接口,则尽管文件输入缓冲区中仍然存在可用数据,但在第5步中对epoll_wait(2)的调用可能会挂起。同时,远程对等端可能希望根据已发送的数据做出响应。原因是边缘触发模式仅在监视的文件描述符上发生更改时才传递事件。因此,在步骤5中,调用者可能最终等待输入缓冲区中已经存在的某些数据。在上面的示例中,由于在2中完成写操作,因此将在rfd上生成一个事件,并且该事件在3中使用。由于在4中完成的读取操作不会消耗整个缓冲区数据,因此对epoll_wait(2)的调用已完成在第5步中可能会无限期屏蔽。

使用EPOLLET标志的应用程序应使用非阻塞文件描述符,以避免阻塞读写操作正在处理多个文件描述符的任务。建议将epoll用作边缘触发(EPOLLET)接口的方法如下:

a)
具有非阻塞文件描述符;和
b)
仅在read(2)或write(2)返回EAGAIN之后等待事件。

相反,当用作级别触发接口(默认情况下,未指定EPOLLET时),epoll只是一个更快的poll(2),并且可以在任何使用后者的地方使用,因为它具有相同的语义。

由于即使使用边缘触发的epoll,也可以在接收到多个数据块时生成多个事件,因此调用方可以选择指定EPOLLONESHOT标志,以告知epoll在使用epoll_wait()接收到事件后禁用关联的文件描述符。 2)。指定EPOLLONESHOT标志时,调用者有责任使用带有EPOLL_CTL_MOD的epoll_ctl(2)重新设置文件描述符。

如果多个线程(或进程,如果子进程已在fork(2)上继承了epoll文件描述符)在epoll_wait(2)中被阻塞,则等待相同的epoll文件描述符和兴趣列表中标记为edge-的文件描述符。触发(EPOLLET)通知准备就绪,只有一个线程(或进程)从epoll_wait(2)中唤醒。这在某些情况下提供了一种有用的优化方法,可以避免"打雷群"的唤醒。

Interaction with autosleep

如果系统通过/ sys / power / autosleep处于自动睡眠模式,并且发生了将设备从睡眠状态唤醒的事件,则设备驱动程序将仅在将该事件排队之前将设备保持唤醒状态。为了使设备在处理事件之前一直处于唤醒状态,必须使用epoll_ctl(2)EPOLLWAKEUP标志。

当在结构epoll_event的事件字段中设置了EPOLLWAKEUP标志时,从事件排队起,系统将通过epoll_wait(2)调用保持唤醒状态,该调用返回事件直到随后的epoll_wait(2)调用。如果该事件应使系统在该时间之后仍保持唤醒状态,则应在第二次epoll_wait(2)调用之前进行单独的唤醒锁定。

/proc interfaces

以下接口可用于限制epoll占用的内核内存量:

/proc/sys/fs/epoll/max_user_watches(since Linux 2.6.28)
这指定了用户可以在系统上所有epoll实例之间注册的文件描述符总数的限制。限制是每个真实用户ID。每个注册文件描述符在32位内核上大约花费90个字节,在64位内核上大约花费160个字节。当前,max_user_watches的默认值为可用低内存的1/25(4%)除以字节的注册成本。

Example for suggested usage

尽管将epoll用作级别触发接口时确实具有与poll(2)相同的语义,但是边缘触发的用法需要更多说明,以避免应用程序事件循环中的停顿。在此示例中,侦听器是一个非阻塞套接字,已在其上调用listen(2)。函数do_use_fd()使用新的就绪文件描述符,直到EAGAIN由read(2)或write(2)返回。事件驱动的状态机应用程序应在收到EAGAIN后记录其当前状态,以便在下一次对do_use_fd()的调用时,它将继续从其先前停止的位置读取(2)或写入(2)。

#define MAX_EVENTS 10
struct epoll_event ev, events[MAX_EVENTS];
int listen_sock, conn_sock, nfds, epollfd;

/* Code to set up listening socket, aqlisten_sockaq,
   (socket(), bind(), listen()) omitted */

epollfd = epoll_create1(0);
if (epollfd == -1) {
    perror("epoll_create1");
    exit(EXIT_FAILURE);
}

ev.events = EPOLLIN;
ev.data.fd = listen_sock;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == -1) {
    perror("epoll_ctl: listen_sock");
    exit(EXIT_FAILURE);
}

for (;;) {
    nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
    if (nfds == -1) {
        perror("epoll_wait");
        exit(EXIT_FAILURE);
    }

    for (n = 0; n < nfds; ++n) {
        if (events[n].data.fd == listen_sock) {
            conn_sock = accept(listen_sock,
                               (struct sockaddr *) &addr, &addrlen);
            if (conn_sock == -1) {
                perror("accept");
                exit(EXIT_FAILURE);
            }
            setnonblocking(conn_sock);
            ev.events = EPOLLIN | EPOLLET;
            ev.data.fd = conn_sock;
            if (epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock,
                        &ev) == -1) {
                perror("epoll_ctl: conn_sock");
                exit(EXIT_FAILURE);
            }
        } else {
            do_use_fd(events[n].data.fd);
        }
    }
}

当用作边缘触发接口时,出于性能原因,可以通过指定(EPOLLIN | EPOLLOUT)在epoll接口(EPOLL_CTL_ADD)中添加一次文件描述符。这样可以避免在EPOLLIN和EPOLLOUT之间连续切换,并使用EPOLL_CTL_MOD调用epoll_ctl(2)。

Questions and answers

0.
区分兴趣列表中注册的文件描述符的关键是什么?
关键是文件描述符号和打开文件描述(也称为"打开文件句柄",即内核在内部表示的打开文件)的组合。
1.
如果在epoll实例上两次注册相同的文件描述符,会发生什么情况?
您可能会得到EEXIST。但是,可以将重复的文件描述符(dup(2),dup2(2),fcntl(2)F_DUPFD)添加到同一epoll实例。如果重复的文件描述符使用不同的事件掩码注册,则这对于筛选事件可能是一种有用的技术。
2.
两个epoll实例可以等待相同的文件描述符吗?如果是,事件是否同时报告给两个epoll文件描述符?
是的,事件将报告给双方。但是,可能需要仔细编程才能正确执行此操作。
3.
epoll文件描述符本身是否可以轮询/ epoll /可选?
是。如果epoll文件描述符中有等待事件,则它将指示为可读。
4.
如果有人尝试将epoll文件描述符放入其自己的文件描述符集中,会发生什么情况?
epoll_ctl(2)调用失败(EINVAL)。但是,您可以在另一个epoll文件描述符集中添加一个epoll文件描述符。
5.
我可以通过UNIX域套接字将epoll文件描述符发送到另一个进程吗?
是的,但是这样做没有任何意义,因为接收过程不会在兴趣列表中包含文件描述符的副本。
6.
关闭文件描述符会导致将其从所有epoll兴趣列表中删除吗?
是的,但是请注意以下几点。文件描述符是对打开文件描述的引用(请参见open(2))。每当通过dup(2),dup2(2),fcntl(2)F_DUPFD或fork(2)复制文件描述符时,都会创建一个引用相同打开文件描述的新文件描述符。打开的文件描述将继续存在,直到所有引用它的文件描述符都被关闭为止。
仅在关闭了所有引用基础打开文件描述的文件描述符之后,才从兴趣列表中删除文件描述符。这意味着即使关闭了作为兴趣列表一部分的文件描述符,如果其他引用相同基础文件描述的文件描述符保持打开状态,则可能会报告该文件描述符的事件。为了防止这种情况发生,必须在复制文件描述符之前将其从兴趣列表中明确删除(使用epoll_ctl(2)EPOLL_CTL_DEL)。或者,应用程序必须确保关闭所有文件描述符(如果使用dup(2)或fork(2)的库函数在后台复制文件描述符,则可能会很困难)。
7.
如果在epoll_wait(2)调用之间发生多个事件,它们是合并还是单独报告?
他们将被合并。
8.
文件描述符上的操作是否会影响已收集但尚未报告的事件?
您可以对现有文件描述符执行两项操作。在这种情况下,删除将毫无意义。修改将重新读取可用的I / O。
9.
使用EPOLLET标志(边沿触发的行为)时,是否需要连续读取/写入文件描述符直到EAGAIN
epoll_wait(2)接收到一个事件应该提示您该文件描述符已为请求的I / O操作做好了准备。您必须考虑到它准备就绪,直到下一次(非阻塞)读/写产生EAGAIN为止。何时以及如何使用文件描述符完全取决于您。
对于面向数据包/令牌的文件(例如,数据报套接字,标准模式下的终端),检测读/写I / O空间结束的唯一方法是继续读/写直到EAGAIN。
对于面向流的文件(例如管道,FIFO,流套接字),还可以通过检查从目标文件描述符读取/写入到目标文件描述符的数据量来检测读取/写入I / O空间已用尽的情况。例如,如果通过要求读取一定数量的数据来调用read(2),而read(2)返回的字节数较少,则可以确定已经耗尽了文件描述符的读取I / O空间。使用write(2)进行写入时也是如此。 (如果不能保证受监视的文件描述符始终引用面向流的文件,请避免使用后一种技术。)

Possible pitfalls and ways to avoid them

o Starvation (edge-triggered)

如果有大量的I / O空间,则可能由于试图将其耗尽而无法处理其他文件,从而导致饥饿。 (此问题并非特定于epoll。)

解决方案是维护一个就绪列表,并在其关联的数据结构中将文件描述符标记为就绪,从而使应用程序能够记住需要处理哪些文件,但仍要在所有就绪文件中循环使用。这也支持忽略针对已准备好的文件描述符收到的后续事件。

o If using an event cache...

如果您使用事件缓存或存储从epoll_wait(2)返回的所有文件描述符,请确保提供一种方法来动态标记其关闭(即,由上一个事件的处理引起)。假设您从epoll_wait(2)收到100个事件,并且在事件#47中,某种情况导致事件#13关闭。如果删除结构并关闭(2)事件#13的文件描述符,则事件缓存可能仍会说有事件在等待该文件描述符,从而引起混乱。

一种解决方案是在事件47的处理期间调用epoll_ctl(EPOLL_CTL_DEL)删除文件描述符13和close(2),然后将其关联的数据结构标记为已删除并将其链接到清除列表。如果在批处理中找到文件描述符13的另一个事件,您将发现该文件描述符先前已被删除,不会造成混淆。

版本

epoll API在Linux内核2.5.44中引入。在版本2.3.2中添加了对glibc的支持。

遵循规范

epoll API是特定于Linux的。其他一些系统提供了类似的机制,例如,FreeBSD具有kqueue,而Solaris具有/ dev / poll。

备注

可以通过进程的/ proc / [pid] / fdinfo目录中epoll文件描述符的条目来查看通过epoll文件描述符监视的文件描述符集。有关更多详细信息,请参见proc(5)。

kcmp(2)KCMP_EPOLL_TFD操作可用于测试epoll实例中是否存在文件描述符。

另外参见

epoll_create(2),epoll_create1(2),epoll_ctl(2),epoll_wait(2),poll(2),select(2)

出版信息

这个页面是Linux手册页项目5.08版的一部分。有关项目的说明、有关报告错误的信息以及此页面的最新版本,请访问https://www.kernel.org/doc/man-pages/