Epoll实施,第4部分

这是关于实现的四篇文章系列(第1部分第2部分第3部分)中的最后一篇epoll在这里,我们将讨论它如何epoll将事件从内核空间转移到用户空间,以及如何实现边缘和水平触发模式。 本文的撰写晚于其他文章。当我开始研究第一篇材料时,最新的稳定Linux内核是3.16.1。在撰写本文时,它已经是4.1版。本文基于此内核版本的代码。但是,代码并没有太大变化,因此前几篇文章的读者可能不必担心实现中的某些变化。







epoll



与用户空间互动



在前面的文章中,我花了很多时间解释内核中的事件处理系统是如何工作的。但是,如您所知,内核需要将有关事件的信息传递给在用户空间中运行的程序,以便程序使用此信息。这主要是通过epoll_wait(2)系统调用完成的



可以在文件的1961行上找到此功能的代码fs/eventpoll.c函数本身非常简单。经过相当正常的检查后,它eventpoll仅从文件描述符中获取指向的指针并调用以下函数:



error = ep_poll(ep, events, maxevents, timeout);


Ep_poll()函数



该函数ep_poll()在同一文件的第1585行声明。首先检查用户是否设置了值timeout。如果是这样,该函数将初始化等待队列,并将超时设置为用户指定的值。如果用户不想等待,即, timeout = 0,则函数将立即转到带有标签的代码块,该标签check_events:负责复制事件。



如果用户指定了一个值timeout,并且没有可以报告给他的事件(它们的存在是通过call来确定的ep_events_available(ep)),则该函数ep_poll()会将自身添加到等待队列中ep->wq(请记住我们在本系列的第三篇文章中所讨论的内容)。在那里我们提到了ep_poll_callback()在该过程中,它激活了队列中正在等待的所有过程。ep->wq...



然后,该函数通过调用进入待机状态schedule_hrtimeout_range()在某些情况下,“睡眠”过程可以“唤醒”:



  1. 超时已过期。
  2. 该过程收到信号。
  3. 发生了一个新事件。
  4. 什么也没发生,调度程序只是决定激活该过程。


在场景1、2和3中,该函数设置适当的标志并退出等待循环。在后一种情况下,该功能只需再次进入待机模式。



完成这部分工作后,它将ep_poll()继续执行块代码check_events:



在此块中,首先检查事件的存在,然后进行下一次调用,在此发生最有趣的事件。



ep_send_events(ep, events, maxevents)


函数ep_send_events()声明上线1546年它是在调用之后,调用函数ep_scan_ready_list(),传递一个回调ep_send_events_proc()。该函数ep_scan_ready_list()遍历就绪文件描述符的列表,并ep_send_events_proc()为找到的每个就绪事件调用。在下文中将变得很清楚,需要一种涉及使用回调的机制来确保安全性和代码重用。



该函数ep_send_events()首先将结构的现成文件描述符列表中的数据eventpool放入其局部变量。然后,将ovflist结构字段设置eventpoolNULL(默认值为EP_UNACTIVE_PTR)。



为什么epoll使用作者ovflist?这样做是为了确保高效率epoll!您可能注意到了准备文件描述符的名单已经从结构而之后eventpool,它被ep_scan_ready_list()设置ovflistNULL。这导致ep_poll_callback()不尝试将传递给用户空间的事件附加回ep->rdllist,这可能会导致大问题。通过使用该ovflist功能,在将事件复制到用户空间时,ep_scan_ready_list()无需保持锁定ep->lock。结果,解决方案的整体性能得以提高。



之后,它将ep_send_events_proc()绕过它具有的就绪文件描述符的列表,然后再次调用它们的方法。poll()为了确保事件确实发生。为什么要epoll再次在这里检查事件?这样做是为了确保用户注册的事件仍然可用。考虑一种情况,EPOLLOUT在用户程序写入该描述符时,文件描述符按事件被添加到了就绪文件描述符列表中。程序完成写入后,文件描述符可能不再可写。Epoll您需要正确处理这种情况。否则,用户将EPOLLOUT在写操作被阻止的那一刻收到



但是,这里值得一提。功能ep_send_events_proc()尽一切努力确保用户空间程序收到准确的事件通知。尽管不太可能,但在ep_send_events_proc()触发之后,一组事件的可用性可能会发生变化poll()在这种情况下,用户空间程序可能会收到有关不再存在的事件的通知。这就是为什么在应用时始终使用非阻塞套接字被认为是正确的epoll这样可以防止您的应用程序被意外阻止。



检查事件掩码后,它ep_send_events_proc()仅将事件结构复制到用户空间程序提供的缓冲区中。



边沿触发和水平触发



现在,我们终于可以讨论边缘触发(ET)和电平触发(LT)之间的区别。



else if (!(epi->event.events & EPOLLET)) {
    list_add_tail(&epi->rdllink, &ep->rdllist);
}


非常简单!该函数ep_send_events_proc()将事件添加回就绪文件描述符列表中。结果,在下一次调用时,ep_poll()将再次检查相同的文件描述符。由于它ep_send_events_proc()总是在将文件poll()返回给用户空间应用程序之前调用文件,因此ET如果文件描述符不再可用,这会稍微增加系统开销(与相比)。但是,如上所述,所有这些的目的是不报告不再可用的事件。完成事件复制



ep_send_events_proc(),该函数返回复制到它的事件数,以使用户空间应用程序保持最新状态。



功能ep_send_events_proc()完成后,功能ep_scan_ready_list()需要清理一点。首先,它将未通过函数处理的事件返回到就绪文件描述符列表ep_send_events_proc()如果可用事件的数量超过用户程序提供的缓冲区的大小,则会发生这种情况。它还将ep_send_events_proc()所有事件ovflist(如果有的话)快速附加到就绪文件描述符列表中。此外,ovflist再次记录in EP_UNACTIVE_PTR结果,新事件将附加到主等待列表(rdllist)。在存在任何其他可用事件的情况下,该功能将通过激活任何其他“休眠”进程来退出。



结果



这是实施系列的第四篇也是最后一篇文章的结尾epoll在撰写这些文章时,Linux内核代码的作者为实现最大的效率和可伸缩性而进行的大量脑力劳动给我留下了深刻的印象。我感谢Linux代码的所有作者通过共享工作结果与需要它的每个人共享知识。



您对开源软件有何看法?










All Articles