LWN:epoll也要用ring buffer来跟用户程序交互了

栏目: 后端 · 发布时间: 5年前

内容简介:ByMay 30, 2019epoll这一组system call系统调用设计目的就是希望能大大增强对大量I/O events的polling能力。它能减少针对每次system call的准备工作,直接返回多个events,这样系统调用的使用次数也会减少。不过有些用户仍然不满意,觉得不能支撑自己更大规模场景下的使用。相应的,Roman Penyaev提出了一组patch,给kernel增加了另一个ring-buffer接口。

A ring buffer for epoll

By Jonathan Corbet

May 30, 2019

epoll这一组system call系统调用设计目的就是希望能大大增强对大量I/O events的polling能力。它能减少针对每次system call的准备工作,直接返回多个events,这样系统调用的使用次数也会减少。不过有些用户仍然不满意,觉得不能支撑自己更大规模场景下的使用。相应的,Roman Penyaev提出了一组patch,给kernel增加了另一个ring-buffer接口。

poll()和select()这两个系统调用会等待,直到一组file descriptor中的某一个可以开始进行I/O操作。每次调用的时候,都需要kernel准备一个内部数据结构,这样当某个file descriptor状态改变的时候能得到通知。epoll就是对这里的一个改进,它把准备和等待这两个阶段分离开,内部的数据结构就可以一直保留着。

应用程序先用epoll_create1()来创建file descriptor(后续简称fd),后续步骤需要用到。这个API基本上取代了epoll_create(),把那个用不到的参数换成了一个flag参数。接下来调用epoll_ctl()来把各个需要监控的fd加到epoll的监控集合里面。最后调用epoll_wait(),会一直阻塞住,直到至少有一个fd有状态变化才返回。这个流程比poll()多了几个步骤,但当应用程序在监控大量的fd的时候,就能有很大的性能改善。

其实,还能够有性能改善的空间。尽管epoll比此前的方案都更加高效,不过application还是需要通过调用system call才能让下一组fd准备好进行I/O。在繁忙系统里,如果能够不用调用系统调用就能拿到最新的event,肯定会提高效率。Penyaev的patch set就是这样实现的。他创建了一个ring buffer,让application和kernel公用,可以用来传递events。

epoll_create() — the third time is the charm

想要利用这个机制的application,第一步需要告诉kernel后续会开始用polling机制了,以及需要多大的ring buffer。epoll_create1()没有这些参数,因此他创建了一个新的system call,epoll_create2():

int epoll_create2(int flags, size_t size);

也增加了一个新的flag,EPOLL_USERPOLL,目的是告诉kernel要用ring buffer来传递events。size参数就是指明ring buffer需要容纳多少项。size会被增长到按2的幂次,然后用于设置这个epoll命令能监控的 fd 最大数量。目前的patch set里面限制了不能超过65536个项目。

接下来仍旧使用epoll_ctl()来把所关心的多个fd加入polling集合。这里还是会有一些限制,因为某些操作跟user-space polling不兼容,例如每个fd都需要用EPOLLET flag来指定成边沿触发的条件。当某个fd报告说已经准备就绪的时候,只有一个event会被放入ring buffer,这里肯定不能用电平触发的方式来持续不断的放event进入ring buffer。EPOLLWAKEUP flag(用于防止系统在某些event被处理的过程中进入suspend)在这个模式下也无法工作。EPOLLEXCLUSIVE也不支持。

后面还需要两三次mmap()调用,来将ring buffer映射到user space。第一次调用需要指定0作为offset、length是一个page的大小,这样就能获得一个包含如下信息的结构:

struct epoll_uheader {
	u32 magic;          /* epoll user header magic */
	u32 header_length;  /* length of the header + items */
	u32 index_length;   /* length of the index ring, always pow2 */
	u32 max_items_nr;   /* max number of items */
	u32 head;           /* updated by userland */
	u32 tail;           /* updated by kernel */

	struct epoll_uitem items[];
    };

这里的header_length成员变量其实包含了epoll_uheader结构和它的数据数组的长度。可以参照这个例子程序(https://github.com/rouming/test-tools/blob/master/userpolled-epoll.c  ),后续application会把header结构map出来,得到实际的长度,把这个page unmap掉,然后重新用header_length的长度来再次map出来,得到全部数据的数组。

大家可能以为item就是这个ring buffer,其实这里有点绕。还需要再调用mmap()一次才能得到真正的ring buffer,传入的参数里offset是header_length,length是index_length。mmap获取的结果就是一组数据,里面每一项都存放着指向items数组的一个整形索引数字,这才是真正的ring buffer。

最终表示每个event的结构如下:

struct epoll_uitem {
	__poll_t ready_events;
	__poll_t events;
	__u64 data;
    };

其中,events是epoll_ctl()调用时提供的那组events集合。ready_events就是真正已经触发的event集合。这里data成员直接来自epoll_ctl()调用时加入的fd。

如果head和tail两个值不等,说明ring buffer里至少有一个event存在了。application需要拿到这个event,它就直接从head指向的位置读出内容,一直等到读出内容非0为止。这里实际上就是在等kernel把数据写入ring buffer。最后读出的数值就是item数组里面的一个索引值(其实是索引值+1)。把这一项的data复制出来之后,ready_events设置为0,最后head指针就自己加一指向后一项。

简单来说,代码类似下面这样:

while (header->tail == header->head)
        ;  /* Wait for an event to appear */
    while (index[header->head] == 0)
        ;  /* Wait for event to really appear */
    item = header->items + index[header->head] - 1;
    data = item->data;
    item->ready_events = 0;  /* Mark event consumed */
    header->head++;

这只是个示例,实际操作中,这里的代码应该用 C语言 的原子操作,而不是简单的read和write。这里head增加到位之后需要从0开始。不过总体来说概念应该展示得很清楚了。

这里会在看到ring buffer是空的时候,会忙等在这里,这肯定不是最优的方案。如果application觉得没有什么能做了的,就可以调用epoll_wait()来阻塞住,直到有event出现。这个调用只有在传入events数组为NULL并且maxevents是0的时候才能成功,其他情况下,epoll_wait()会阻塞住,但不会返回任何event给调用者,而很有可能会返回ESTALE,提醒说ring buffer里面有events在等待处理。这组patch set已经是第三版了,目前看来没有更多反对意见了。目前还没能进入linux-next,不过很有可能能够赶上 Linux 5.3的合入窗口。

一些感触

理解上述流程,是通过仔细研读代码才得到的。这是一个挺复杂的新API,不过几乎没有任何文档。这里导致了使用困难,不过在这之前,首先导致了API的review也很困难。很怀疑直到目前未知,除了作者,没有其他人真正试用过这组API。到底社区是否真正理解了它的实现,也是个问好。

很可惜的是,可能它会成为kernel的众多ring-buffer接口中的一个最新例子。其他的还包括perf events,ftrace,io_uring,AF_XDP,等等等等。这些接口,每一个都是从头创建出来,需要相对应的领域里的user-space开发者来仔细理解以及实现。这里是不是应该让kernel来定义一组ring buffer管理标准,专门针对user space交互场景的,这样不用每次都创建一个完全不同的一套新的机制?我们不能责备当前的patch set有这个问题,毕竟在作者开始实现的时候还没有其他参考。不过这里确实展示了Linux kernel API设计中的一个缺点,经常缺乏一个一致性很强的整体设计。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

终极算法

终极算法

[美] 佩德罗·多明戈斯 / 黄芳萍 / 中信出版集团 / 2017-1-1 / 68.00元

算法已在多大程度上影响我们的生活? 购物网站用算法来为你推荐商品,点评网站用算法来帮你选择餐馆,GPS系统用算法来帮你选择最佳路线,公司用算法来选择求职者…… 当机器最终学会如何学习时,将会发生什么? 不同于传统算法,现在悄然主导我们生活的是“能够学习的机器”,它们通过学习我们琐碎的数据,来执行任务;它们甚至在我们还没提出要求,就能完成我们想做的事。 什么是终极算法? ......一起来看看 《终极算法》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具