epoll_create()
epoll_create()创建一个epoll“实例”,size参数目前被忽略。 epoll_create()返回一个引用新的epoll实例的文件描述符。 当不再需要时,应该使用close()关闭epoll_create()返回的文件描述符。epoll_create1()
如果falgs为0,epoll_create1()与epoll_create()相同。 标志中可以包含以下值以获得不同的行为: EPOLL_CLOEXEC 在新的文件描述符上设置关闭执行(FD_CLOEXEC)标志。 有关这可能有用的原因,请参阅open(2)中的O_CLOEXEC标志的描述。epoll_wait()
epoll_wait()系统调用等待文件描述符epfd引用的epoll实例上的事件。 maxevents参数必须大于零。 该调用超时等待时间为timeout毫秒。 timeout为-1时,会使epoll_wait()无限期地等待; timeout为 0时,即使没有可用的事件(返回码等于零),epoll_wait()也会立即返回。 struct epoll_event定义如下: typedef union epoll_data { void *ptr; int fd; uint32_t u32; uint64_t u64; } epoll_data_t; struct epoll_event { uint32_t events; /* epoll事件 */ epoll_data_t data; /* 用户数据变量 */ }; The data of each returned structure will contain the same data the user set with an epoll_ctl(2) (EPOLL_CTL_ADD,EPOLL_CTL_MOD) while the events member will contain the returned event bit field. event成员可以下面的事件类型进行位运算“|”组成: EPOLLIN 相关文件可用于read(2)操作。 EPOLLOUT 相关文件可用于write(2)操作。 EPOLLRDHUP(自Linux 2.6.17开始) 流套接字对等封闭连接,或关闭写的一半连接。 (这个标志对于编写简单的代码特别有用,在使用边缘触发监视时检测对等体关闭。) EPOLLPRI 有可用于read(2)操作的紧急数据。 EPOLLERR 关联的文件描述符发生错误情况。 epoll_wait(2)总是等待这个事件; 没有必要将它设置为事件。 EPOLLHUP 在相关的文件描述符上发生挂起。 epoll_wait(2)总是等待这个事件; 没有必要将它设置为事件。 EPOLLET 设置相关文件描述符的边缘触发行为。 epoll的默认行为是水平触发级别。 有关Edge和Level Triggered事件分布架构的更多详细信息,请参阅epoll(7)。 EPOLLONESHOT(自Linux 2.6.2起) 设置相关文件描述符的单次行为。 这意味着在用epoll_wait(2)提取事件之后,相关的文件描述符被内部禁用,并且epoll接口不会报告其他事件。 用户必须使用EPOLL_CTL_MOD调用epoll_ctl()来重新构建新的事件掩码的文件描述符。epoll_pwait()
epoll_wait()和epoll_pwait()之间的关系类似于select(2)和pselect(2)之间的关系:像pselect(2)。 epoll_pwait()允许应用程序安全地等待,直到文件描述符准备就绪或直到信号被捕获。 以下epoll_pwait()调用: ready = epoll_pwait(epfd,&events,maxevents,timeout,&sigmask); 相当于原子地执行以下调用: sigset_t origmask; sigprocmask(SIG_SETMASK,&sigmask,&origmask); ready = epoll_wait(epfd,&events,maxevents,timeout); sigprocmask(SIG_SETMASK,&origmask,NULL); sigmask参数可以指定为NULL,在这种情况下epoll_pwait()等价于epoll_wait()。epoll - I / O事件通知工具
epoll的使用流程
epoll是poll(2)的变体,可以用作边缘触发或水平触发的接口,并可以很好地扩展到大量的监视文件描述符。 提供以下系统调用来创建和管理一个epoll实例: (1).由epoll_create(2)创建的epoll实例,它返回引用epoll实例的文件描述符。 (最近的epoll_create1(2)扩展了epoll_create(2)的功能) (2).通过epoll_ctl(2)注册对特定文件描述符的兴趣。 当前在epoll实例上注册的文件描述符集合有时被称为epoll集。 (3).最后,实际的等待由epoll_wait(2)开始。Level-Triggered and Edge-Triggered
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中完成写入,并且事件在3中消耗,所以将生成(rfd)上的事件。 由于4中完成的读取操作未消耗整个缓冲区数据,所以在步骤5中完成的对epoll_wait(2)的调用可能会无限期地阻止。EPOLLET
使用EPOLLET标志的应用程序应使用非阻塞文件描述符,以避免阻塞读取或写入使处理多个文件描述符的任务被饿死。 使用epoll作为边缘触发(EPOLLET)接口的建议方法如下: 1.使用非阻塞文件描述符; 2.直到EAGAIN被read(2)或write(2)返回才退出。EPOLLLT
相比之下,当用作水平触发的接口(默认情况下,当EPOLLET未指定时),epoll只是一种更快的poll(2), 并且可以在后者被使用的地方使用,因为它共享相同的语义。EPOLLONESHOT
由于即使使用边缘触发的epoll,在接收到多个数据块时也可以生成多个事件, 调用者可以选择指定EPOLLONESHOT标志,以便在epoll_wait(2)收到事件之后告诉epoll禁用相关的文件描述符。 当指定EPOLLONESHOT标志时,调用者有责任使用epoll_ctl(2)与EPOLL_CTL_MOD重新配置文件描述符。/proc接口限制
/proc接口 以下接口可用于限制epoll消耗的内核内存量: /proc/sys/fs/epoll/max_user_watches(自Linux 2.6.28开始) 这指定用户可以在系统上的所有epoll实例上注册的文件描述符总数的限制。 限制是根据实际的用户ID。 每个注册的文件描述符在32位内核上大致为90字节,在64位内核上大约为160字节。 目前,max_user_watches的默认值是可用最低内存的1/25(4%),除以注册成本(以字节为单位)。示例程序
#define MAX_EVENTS 10 struct epoll_event ev, events[MAX_EVENTS]; int listen_sock, conn_sock, nfds, epollfd; /* Set up listening socket, 'listen_sock' (socket(), bind(), listen()) */ epollfd = epoll_create(10); if (epollfd == -1) { perror("epoll_create"); exit(EXIT_FAILURE); } ev.events = EPOLLIN; // listen_sock是否需要使用EPOLLET? 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 (;;) { // timeout为-1时,会使epoll_wait()无限期地等待 nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1); if (nfds == -1) { perror("epoll_pwait"); 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); } } }当用作边缘触发接口时,出于性能原因,可以通过指定(EPOLLIN | EPOLLOUT)将epoll接口(EPOLL_CTL_ADD)中的文件描述符添加一次。 避免在EPOLLIN和EPOLLOUT之间连续切换,通过EPOLL_CTL_MOD调用epoll_ctl(2)。
问题零
Q:用于区分在epoll注册的文件描述符的标示是什么? A:文件描述符号和打开的文件描述(也称为“打开文件句柄”,内核的打开文件的内部表示形式)的组合。问题一
Q:如果在epoll实例上注册相同的文件描述符两次,会发生什么? A:你可能会得到EEXIST。 然而,可以向相同的epoll实例添加一个重复(dup(2),dup2(2),fcntl(2)F_DUPFD)描述符。 如果重复的文件描述符使用不同的事件掩码进行注册,这可能是过滤事件的有用技术。问题二
Q:两个epoll实例可以等待相同的文件描述符吗? 如果是这样,事件是否报告给这两个epoll文件描述符? A:是的,事件将被报告给两者。 但是,可能需要仔细编程才能正确执行。问题三
Q:epoll文件描述符本身是否poll/epoll可选? A:是的 如果epoll文件描述符有事件等待,那么它将被指示为可读。问题四
Q:如果尝试将epoll文件描述符放入自己的文件描述符集中会发生什么? A:epoll_ctl(2)调用将失败(EINVAL)。 但是,您可以在另一个epoll文件描述符集中添加epoll文件描述符。问题五
Q:我可以通过Unix域套接字将epoll文件描述符发送到另一个进程吗? A:是的,但是这样做是没有意义的,因为接收过程不会在epoll集合中包含文件描述符的副本。问题六
Q:关闭文件描述符会使其从所有epoll集中自动删除吗? A:是的,但请注意以下几点。 文件描述符是对打开的文件描述的引用(参见open(2))。 每当通过dup(2),dup2(2),fcntl(2)F_DUPFD或fork(2)复制描述符时, 创建引用相同打开文件描述的新文件描述符。 打开的文件描述继续存在,直到引用它的所有文件描述符都已关闭。 只有在引用底层打开的文件描述的所有文件描述符已经关闭之后 (或者如果使用epoll_ctl()EPOLL_CTL_DEL)显式删除描述符之前,才会从epoll集合中删除文件描述符。 这意味着即使在作为epoll集合的一部分的文件描述符被关闭之后, 如果引用相同的底层文件描述的其他文件描述符保持打开,那么也可以为该文件描述符报告事件。问题七
Q:如果在epoll_wait(2)调用之间发生多个事件,它们是否组合或单独报告? A:将合并。问题八
Q:对文件描述符的操作是否影响已经收集但尚未报告的事件? A:您可以对现有文件描述符执行两个操作。 对于这种情况,删除将是无意义的。 修改将重新读取可用的I/O。问题九
Q:使用EPOLLET标志(边缘触发)时,是否需要持续读/写文件描述符至EAGAIN? A:从epoll_wait(2)接收事件应该暗示您:这个文件描述符已就绪进行I/O操作。 ( You must consider it ready until the next (non-blocking) read/write yields EAGAIN.) 您必须考虑它的就绪状态,直到下一个(非阻塞)read/write生EAGAIN。 使用文件描述符的时间和方式完全取决于您。 (1).对于分组/面向令牌的文件(例如,数据报套接字,规范模式下的终端),检测I/O空间结束的唯一方法是继续读/写直到EAGAIN。 (2).对于面向流的文件(例如管道,FIFO,流套接字),也可以通过检查从目标文件描述符读取的数据量或写入目标文件描述符的数据量来检测I/O空间耗尽的情况。 例如,如果调用read(2)要求读取一定量的数据,但是read(2)返回较少的字节数,则可以确保已耗尽文件描述符的I/O空间。 使用write(2)写入时也是如此。(如果不能保证受监视的文件描述符始终引用一个面向流的文件,请避免使用这种技术。)饥饿(边缘触发)
如果有大量的I/O,如果尽力处理一个文件描述符,其他的文件描述符得不到处理,会导致饥饿。 (这个问题不是特定于epoll。) 解决方案是维护一个就绪列表,并将文件描述符在其关联的数据结构中标记为就绪状态。 从而允许应用程序记住那些文件需要被处理但仍处于就绪状态的文件句柄。 你也可忽略那些已通知你就绪的文件描述符。如果使用事件缓存
如果您使用事件缓存或存储从epoll_wait(2)返回的所有文件描述符,请确保提供一种动态标记其关闭(即由先前事件处理引起的)的方法。 假设您从epoll_wait(2)收到100个事件,而在事件#47中,一个条件导致事件#13被关闭。 如果删除结构并关闭事件#13的文件描述符,那么事件缓存可能仍然会说有事件等待该文件描述符,从而导致混淆。 一个解决方案是在事件47的处理期间调用epoll_ctl(EPOLL_CTL_DEL)来删除文件描述符13并close(2),然后将其关联的数据结构标记为已删除,并将其链接到清除列表。如果在批处理中找到文件描述符13的另一个事件,您将发现文件描述符以前已被删除,并且不会有任何混淆。(1). listenfd是否需要使用EPOLLET
(2). epoll_wait()的timeout参数
timeout 超时等待时间(毫秒) timeout为-1时,会使epoll_wait()无限期地等待; timeout为 0时,即使没有可用的事件(返回码等于零),epoll_wait()也会立即返回。(3). 使用epoll作为边缘触发(EPOLLET)接口的建议方法如下:
使用非阻塞文件描述符; 直到EAGAIN被read(2)或write(2)返回才退出。(3). epoll_ctl()
当用作边缘触发接口时,出于性能原因,可以通过指定(EPOLLIN | EPOLLOUT)将epoll接口(EPOLL_CTL_ADD)中的文件描述符添加一次。 避免在EPOLLIN和EPOLLOUT之间连续切换,通过EPOLL_CTL_MOD调用epoll_ctl(2)。(4). 可能的陷阱和规避方法
饥饿(边缘触发) 事件缓存