Linux man-pages 6.19把epoll实例描述为一个内核数据结构。从用户空间观察,可以把它看成两份列表的容器:interest list登记进程希望监视的文件描述符,ready list则保存已经就绪的文件描述符引用。后者是前者的子集,由内核随着这些描述符上的I/O活动动态填充。兴趣列表交代监视范围,就绪列表交代其中哪些对象目前有相应事件;它们并不是两份相同的登记表。[1]
手册据此安排了三种接口职责。创建接口取得指向epoll实例的描述符,控制接口负责兴趣列表中的登记,等待接口从就绪列表取得事件。这条联系里,登记某个目标不等于它已经出现在返回事件中,取得一批事件也不等于兴趣登记已经被整体移走。实例、登记对象和某次返回结果各有位置,才能解释后面的通知差别。[1]
边沿触发的纸面例子给出了关键差别:写端向管道写入2 kB,随后等待返回了可读事件,但读端只取走1 kB。尽管还有数据留在缓冲区,下一次等待仍可能挂起。手册将这种情形联系到边沿触发只在被监视对象发生变化时交付事件;已经收到的那次通知,没有因为一次读取尚未取尽数据而自动变成另一条新变化。这里的例子解释通知条件,并没有把剩余数据说成已经消失。[1]
电平触发是该接口的默认方式,手册把它的语义与poll相联系;边沿触发则具有上述变化通知的特点。EPOLLONESHOT又不是边沿触发的别名:它表示在收到一次事件之后,对相应描述符的后续事件报告会被禁用。原文另一方面也提醒,边沿触发并不意味着永远只有一个事件,接收到多块数据仍可能产生多个事件。一边限定报告一次后的状态,另一边说明变化通知可能出现多少次,不能都缩成“只通知一回”。[1]
返回结构本身也有两种资料。epoll_wait的说明写道,data成员保留最近一次为该描述符登记的资料,events成员则是发生了哪些事件的位掩码。一个把原登记资料带回,另一个描述本次发生的事件;返回值另外统计准备好相应I/O的文件描述符数量。因而,同一返回记录里的登记资料、事件种类和就绪对象计数,不能被统一解释成此次读出了多少字节。[2]
一批返回结果与整个就绪集合之间,还可能存在数量差别。NOTES说明,当就绪描述符多于本次结果上限时,后续等待会在这组就绪描述符之间轮转。这里仍然是同一就绪集合有待分批返回,而不是把未进入第一批的目标从兴趣列表删除。另一项说明则从空集合开始:即使兴趣列表暂时为空,等待仍可存在,后来由另一线程加入并变为就绪的目标,可以使它结束等待。前者解释已有就绪对象怎样分批出现,后者解释后来出现的对象怎样进入等待关系。[2]
多个等待者也有明确限定。手册说,当多个线程或进程等待同一个epoll描述符,而以边沿触发方式登记的目标变为就绪时,只会唤醒其中一个等待者。这里同时带有同一实例和边沿触发两个条件;它没有给每个等待者承诺相同份额,也没有把这种唤醒数量等同于每个缓冲区里的数据量。[1]
本文只解释两份手册的通知与返回资料,未运行程序或给出系统调用、非阻塞循环和调优步骤。条目日期与HTML生成日期不作为首次发表日。
信息来源
- Linux man-pages project / Michael Kerrisk:epoll(7) — Linux manual page
- Linux man-pages project / Michael Kerrisk:epoll_wait(2) — Linux manual page
本文基于上述公开资料整理,未使用来源页面的图片、视频或嵌入媒体。