epoll manpage翻译

xiaoxiao2021-02-28  34

之前闲得无聊,翻译了一下epoll的manpage。正文如下:

epoll通过监视多个fd来查看其中任何fd上I/O是否可用。epoll API可以选择边缘触发或水平触发,它在需要监控的fd数量巨大时也能工作。有以下三个系统调用函数用来创建和管理一个epoll实例: epoll_create创建一个epoll的实例并且返回一个代表它的fd。(epoll_create1扩展了它的功能)。 通过epoll_ctl来注册对特定fd的兴趣。当前注册在epoll实例上的fd集合有时被叫做epoll set。 epoll_wait等待I/O事件,如果当前没有事件将阻塞调用它的线程。

水平触发和边缘触发 epoll事件分布接口可以表现为边缘触发(ET)和水平触发(LT)。两种机制的区别如下。假设这个场景发生: 1.代表read side的fd(rfd)被注册到epoll实例上。 2.一个pipe writer将2kB的数据写到pipe的write side。 3.一个epoll_wait的调用完成,将返回rfd作为一个准备好的fd。 4.pipe reader从rfd读取1kB数据。 5.一个epoll_wait的调用完成。

如果rfd文件描述符被加到epoll接口时使用了EPOLLET(边缘触发)选项,步骤5中完成的epoll_wait调用可能阻塞,尽管可用的数据仍在文件输入缓存中;同时远端的一方可能在期待一个关于它已经发送的数据的回复。出现这种情况的原因是边缘触发模式只在被监控的文件描述符发生变化时交付事件。所以,在步骤5中的调用者可能最终一直在等待一些已经存在于输入缓存中的数据。在上述的例子中,一个rfd上的事件将因2中写操作的完成而产生,并且在3中被消耗。因为4中完成的读操作并没有完全消耗整个缓存中的数据,5中的epoll_wait调用将无限期的阻塞。 一个采取EPOLLET选项的应用应该使用非阻塞文件描述符来避免一个阻塞的读写导致一个处理多文件描述符的任务饥饿。在使用epoll作为边缘触发接口时的建议如下: i 用非阻塞文件描述符;和 ii 通过只在read或write返回EAGAIN之后才等待一个事件 相反的,当用作一个水平触发接口(默认的方式,没有设置EPOLLET时),epoll是个更快的poll,而且可以在任何使用poll的地方使用epoll,因为它们有着相同的语义。 因为使用边缘触发epoll时,可能会有多个事件因收到多个数据块而被生成,调用者可以选择设置EPOLLONESHOT选项,来告诉epoll在epoll_wait收到一个事件后使相关的文件描述符无效。当设定了EPOLLONESHOT选项,调用者要负责使用EPOLL_CTL_MOD来重新通过epoll_ctl来注册文件描述符。

和autosleep的互动 如果系统通过/sys/power/autosleep进入了autosleep模式,并且一个唤醒设备的事件发生时,设备的驱动只会保持设备唤醒直到事件入队列。为了保持设备在事件被处理完之前一直醒着,有必要使用epoll的EPOLLWAKEUP选项。 当一个epoll_struct的events字段设置了EPOLLWAKEUP选项,系统将从事件入队列开始保持设备处于唤醒状态,这是通过直到后续epoll_wait被调用才返回事件的epoll_wait实现的。如果事件应该保持系统唤醒超过那个时间,那么应该在第二次epoll_wait之前采用一个单独的wake_lock。

/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%),除以注册操作消耗的字节数。

推荐用法的例子 水平触发的epoll与poll有着同样的语义,边缘触发的用法则需要更多的说明来避免应用的事件循环出现停顿。在例子中,listener是一个listen调用生成的非阻塞socket。do_use_fd()函数直到read或write返回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; /*Code to set up listening socket, 'listen_sock', (socket(), bind(), listen())omitted*/ epollfd = epoll_create1(0); if(epollfd == -1){ perror("epoll_create"); 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_FAILUER); } for(;;){ nfds = epoll_wait(epollfd, eventd, 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 *)&local, &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)。这让你可以避免不断的调用设置了EPOLL_CTL_MOD的epoll_ctl在EPOLLIN和EPOLLOUT之间切换。

问题和答案: Q0 用于区分注册在一个epoll set中的文件描述符的关键是什么? A0 关键是结合文件描述符号和open file description(也被称为“打开文件句柄”,内核对一个打开的文件的内部表示)。

Q1 如果你在一个epoll实例上注册同一个文件描述符两次会发生什么? A1 你可能会得到EEXIT。然而,也可能会在同一个epoll实例上增加一个重复(dup、dup2、fcntl F_DUPFD)描述符。这在过滤事件时将是一个有用的技术,如果重复的文件描述符被注册时有着不一样的事件mask。

Q2 两个epoll实例能不能等待同一个文件描述符?如果能,那事件是不是会被报告给这两个epoll文件描述符? A2 是的,事件会被报告给两者。然而需要小心的编程来正确的做到。

Q3 epoll文件描述符本身是否poll/epoll/selectable? A3 是的,如果一个epoll文件描述符有事件在等待,那么它将被认为是readable的。

Q4 如果有人尝试将一个epoll文件描述符放到它自己的文件描述符集合中会发生什么? A4 epoll_ctl调用将失败(EINVAL)。然而,你可以将一个epoll文件描述符加到另一个epoll文件描述符集合中。

Q5 我能不能通过UNIX domain socket将一个epoll文件描述符发送给另一个进程? A5 可以,但是没什么意义,因为接受方进程没有(发送方)epoll集合中文件描述符的拷贝。

Q6 关闭一个文件描述符会不会自动将它从epoll集合中删除? A6 是的,但是注意以下事项。一个文件描述符是一个打开的文件描述的引用。任何时候一个描述符通过dup、dup2、fcntl F_DUPFD或者fork被重复,一个引用同一文件描述的新的文件描述符将被创建。一个文件描述将持续存在直到所有引用它的文件描述符都被关掉。一个文件描述符只在所有引用潜在打开文件描述的文件描述符被关闭后才从epoll集合中删除(或者显式的通过epoll_ctl EPOLL_CTL_DEL删除)。这意味着即使一个epoll集合中的文件描述符被关掉,如果还有其他打开的文件描述符引用同一个文件描述,该文件描述符的事件依然会被报告。

Q7 如果多余一个事件在epoll_wait之间发生,它们是被结合着的还是分开着的报告的? A7 结合着的。

Q8 一个文件描述符上的操作是否会影响已经收集但尚未报告的事件? A8 你可以在一个存在的文件描述符上做两个操作。删除没有什么意义。修改将重读可用的I/O。

Q9 我在使用EPOLLET选项时是否需要持续的read/write一个文件描述符直到EAGAIN? A9 从epoll_wait收到一个事件建议了你该文件描述符准备好进行请求的I/O操作。你必须在下一个(非阻塞)read/write产生EAGAIN之前将这个文件描述符视为准备好的。何时和怎样使用文件描述符完全由你决定。 对于packet/token-oriented files(e.g., datagram socket, terminal in canonical mode),检测到read/write I/O空间结束的唯一办法是持续的read/write直到EAGAIN。 对于stream-oriented filed(e.g., pipe, FIFO, stream socket),也可以通过检查read from/ written to目标文件描述符的数据量来检测到read/write I/O 空间的耗尽。比如说,如果你通过要求一定数量的数据来调用read并且read返回较少的字节,你可以确定文件描述符的read I/O space耗尽。write也亦然。(避免使用第二种方法如果你不能保证监视的文件描述符永远引用一个stream-oriented file)。

可能的陷阱和避免它们的方法 饥饿(边缘触发) 如果存在大量的I/O space,有可能会因尝试排空它而导致其他文件将得不到处理,称为饥饿。(这个问题不针对与epoll)。 解决方案是维护一个ready列表,并在相关的数据结构中将文件描述符标记为ready,从而允许应用程序记住哪些文件需要处理,但仍然在所有准备好的文件中循环。 这也支持忽略接收到的已准备好的文件描述符的后续事件。

如果使用事件缓存 如果使用事件缓存或存储从epoll_wait返回的所有文件描述符,请确保提供动态标记其闭包的方式(即由前一个事件的处理引起)。假设你从epoll_wait收到100个事件,并且在事件#47中的一个条件将造成事件#13被关闭。如果删除了结构并close事件#13的文件描述符,那么事件缓存可能仍然会说有事件正在等待该文件描述符导致混淆。 一个解决方法是在处理事件47时调用epoll_ctl(EPOLL_CTL_DEL)来删除文件描述符13并且close,然后讲它相关的数据结构标记为已删除并把它链接到一个cleanup list。如果你在批处理中发现另一个文件描述符13的事件,你将发现文件描述符之前已经被删除,就不会造成混淆。

转载请注明原文地址: https://www.6miu.com/read-2603192.html

最新回复(0)