lilnux man page:epoll(翻译+注释)

头文件

#include <sys/epoll.h>

描述

epoll API执行与poll(2)类似的任务:监视多个文件描述符,查看任一描述符是否可以进行I/O。epoll API可以用作边缘触发或电平触发接口,并且扩展性好,可用于监视大量文件描述符。以下系统调用用于创建和管理epoll实例:

   * epoll_create(2) 创建一个新的epoll实例,并返回一个引用该实例的文件描述符。(更新式的epoll_create1(2)扩展了epoll_create(2)的功能。)

   * epoll_ctl(2) 可以注册对特定文件描述符的兴趣。当前在epoll实例中注册的文件描述符集合有时称为epoll集合。

   * epoll_wait(2) 等待I/O事件,如果当前没有事件,则阻塞调用线程。

电平触发和边缘触发 事件发生后信号维持的时间

epoll事件分发接口能够以边缘触发(ET)和电平触发(LT)两种方式工作。两种机制的区别可描述如下。假设发生以下情形:

   1. 代表管道读端的文件描述符(rfd)在epoll实例中注册。

   2. 管道写端在写端写入2kB的数据。

   3. 调用epoll_wait(2),它将返回rfd作为就绪文件描述符。
   
   4. 管道读取端从rfd读取1kB的数据。
   
   5. 调用epoll_wait(2)。
   
   如果使用EPOLLET(边缘触发)标志将rfd文件描述符添加到epoll接口,则在步骤5中对epoll_wait(2)的调用可能会挂起,尽管文件输入缓冲区中仍有可用数据;与此同时,远程对等方可能希望根据它已经发送的数据进行响应。这种情况的原因是边缘触发模式仅在监视的文件描述符发生更改时才传递事件。所以在步骤5,调用方可能最终等待一些已经存在于输入缓冲区中的数据。在上述示例中,由于步骤2中的写操作,rfd上将生成一个事件,并在步骤3中被消费。由于步骤4中的读操作并未消耗完整个缓冲区数据,所以在步骤5中对epoll_wait(2)的调用可能会无限期阻塞。

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

       i. 使用非阻塞文件描述符;以及
       ii. 仅在read(2)write(2)返回EAGAIN后等待事件。

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

   即使对于边缘触发的epoll,在接收到多个数据块时也可以生成多个事件,所以调用者可以指定EPOLLONESHOT标志,告诉epoll在通过epoll_wait(2)接收到一个事件后禁用相关的文件描述符。指定EPOLLONESHOT标志时,使用epoll_ctl(2)和EPOLL_CTL_MOD重新启用文件描述符是调用者的责任。

与autosleep的交互 不懂

如果系统通过/sys/power/autosleep进入autosleep模式,并且有一个事件唤醒设备离开睡眠状态,则设备驱动程序只会保持设备唤醒状态直到该事件排队。要使设备保持唤醒状态直到事件被处理,需要使用epoll_ctl(2) EPOLLWAKEUP标志。

当在struct epoll_event的events字段中设置EPOLLWAKEUP标志时,从事件排队开始,通过返回该事件的epoll_wait(2)调用,系统将一直保持唤醒状态,直到后续的epoll_wait(2)调用。如果事件需要在此之后继续让系统保持唤醒状态,则应在第二个epoll_wait(2)调用之前获取一个单独的wake_lock。

/proc接口 epoll注册消耗的内核内存

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

   /proc/sys/fs/epoll/max_user_watches (从Linux 2.6.28开始)
  此项指定用户可以在系统上所有epoll实例中注册的文件描述符总数限制。限制针对实际的用户ID。在32位内核上,每个已注册文件描述符大约消耗90字节,在64位内核上消耗大约160字节。当前,max_user_watches的默认值为可用低内存的1/25(4%),然后除以以字节为单位的注册成本。

用法实例

尽管将epoll用作水平触发接口时具有与poll(2)相同的语义,但边缘触发用法需要更多澄清,以避免应用程序事件循环中出现停滞。在此示例中,listener是一个已调用了listen(2)的非阻塞套接字。do_use_fd()函数使用新的就绪文件描述符,直到read(2)或write(2)返回EAGAIN。事件驱动的状态机应用程序在收到EAGAIN后,应记录其当前状态,以便在对do_use_fd()的下一次调用中继续从停止的地方read或write。

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

   /* 设置监听套接字'listen_sock'的代码(socket()、bind()、listen())省略 */

   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);
           }
       }
   }

将文件描述符添加到epoll接口内时(EPOLL_CTL_ADD),出于性能原因,可以通过指定(EPOLLIN|EPOLLOUT)一次性添加。这可以避免不断在EPOLLIN和EPOLLOUT之间切换以调用带有EPOLL_CTL_MOD的epoll_ctl(2)。

常见的坑

Q0: 用于区分注册在epoll集合中的文件描述符的关键信息是什么?

A0: 关键信息是文件描述符编号(file descriptor)和内核表(open file descriptor)(也称为“打开文件句柄”,内核对打开文件的内部表示)的组合。

Q1: 如果在同一个epoll实例中两次注册相同的文件描述符会发生什么?

A1: 可能会得到EEXIST。但是,可以向同一个epoll实例添加文件描述符的重复项(dup(2)、dup2(2)、fcntl(2) F_DUPFD)。如果使用不同的事件掩码注册重复文件描述符,这可以是一种用于事件过滤的有用技术。

Q2: 两个epoll实例可以等待同一个文件描述符吗?如果可以,事件会报告给两个epoll文件描述符吗?

A2: 可以,事件会报告给两个实例。但是,需要仔细编程才能正确地执行此操作。

Q3: epoll文件描述符本身是否可poll/epoll/selectable?

A3: 是的。如果epoll文件描述符有等待的事件,则它会指示为可读。

Q4: 如果试图将epoll文件描述符放入它自己的文件描述符集会发生什么?

A4: epoll_ctl(2)调用会失败(EINVAL)。但是,可以将epoll文件描述符放入另一个epoll文件描述符集中。

Q5: 我可以通过UNIX域套接字将epoll文件描述符发送到另一个进程吗?

A5: 可以,但这没有意义,因为接收进程不会有epoll集中文件描述符的副本。

Q6: 关闭文件描述符会自动从所有epoll集中将其删除吗?

A6: 是的,但要注意以下一点。文件描述符是对打开文件描述的引用(参见open(2))。每当通过dup(2)、dup2(2)、fcntl(2) F_DUPFD或fork(2)复制文件描述符时,都会创建一个引用相同内核表的新文件描述符。内核表会一直存在,直到引用它的所有文件描述符都已关闭。只有引用底层内核表的所有文件描述符都已关闭后(或者使用epoll_ctl(2) EPOLL_CTL_DEL明确删除之前),文件描述符才会从epoll集中删除。这意味着即使epoll集的一部分的文件描述符已关闭,如果引用相同内核表的其他文件描述符仍打开,该文件描述符上也可能会报告事件。

Q7: 如果epoll_wait(2)调用之间发生多个事件,这些事件会合并还是单独报告?

A7: 它们会被合并。

Q8: 对文件描述符的操作会影响已经收集但尚未报告的事件吗?

A8: 您可以对现有文件描述符执行两项操作。删除在这种情况下是无意义的。修改将重新读取可用的I/O。

Q9: 在使用EPOLLET标志(边缘触发行为)时,是否需要连续读取/写入文件描述符直到EAGAIN?

A9: 从epoll_wait(2)接收到事件应该让您认为这样的文件描述符已准备好进行请求的I/O操作。在下一次(非阻塞)读/写产生EAGAIN之前,您应该认为它已准备好。如何以及何时使用文件描述符完全取决于您。

对于包/令牌导向文件(例如数据报套接字、规范模式下的终端),检测读/写I/O空间结束的唯一方法是继续读/写直到EAGAIN

对于流导向文件(例如管道、FIFO、流套接字),通过检查从/写入目标文件描述符的数据量,也可以检测读/写I/O空间用尽的条件。例如,如果调用read请求读取一定量的数据,而read返回更少的字节数,则可以确定文件描述符的读I/O空间已用尽。使用write写入时也是如此。(如果无法保证所监视的文件描述符始终引用流导向文件,请避免后一种技术。)(n=read(x); n<x

可能的坑

边缘触发

如果有大量I/O空间,则可能在试图耗尽它时其他文件无法处理,从而导致饥饿。(这不是epoll特有的问题。)

解决方案是维护一个就绪列表,并在相关数据结构中将文件描述符标记为就绪,从而允许应用程序记住哪些文件需要处理,但仍在所有就绪文件之间轮询。这也支持忽略您已收到的文件描述符的后续事件,这些文件描述符已经就绪。

使用事件缓存

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

处理事件47时调用epoll_ctl(EPOLL_CTL_DEL)以删除文件描述符13并close(2),然后将其关联的数据结构标记为已删除并链接到清理列表,这是解决此问题的一种方法。如果在批处理中找到文件描述符13的另一个事件,将发现该文件描述符先前已被删除,也就不会产生困惑。

注意

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

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

区别 link-unlink open-close

这是两套系统。open系列用户操作进程表;link操作inode。

close - 关闭文件描述符;如果所有文件描述符都关闭了,则删除内核表占用的资源。
unlink - 删除链接;如果所有链接都删除了,且没有打开的文件描述符,则删除inode占用的资源。如果还有打开的文件描述符,则需要等到所有文件描述符都关闭,才能释放资源。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值