
点击上方蓝色“Linux News搬运工”关注我们~
Redesigned workqueues for io_uring
By Jonathan Corbet
October 25, 2019
本文译自:https://lwn.net/Articles/803070/
异步I/O近期有了一个新的interface,名为io_uring,出现在5月份的5.1 kernel之中。后来它的功能以及用户继续快速增长。成长过快也带来一些问题,导致一些支持io_uring的kernel功能模块无法跟上。因此Jens Axboe (io_uring maintainer) 提出了一个针对io_uring的新的workqueue子系统,似乎能给我们将来的发展方向一些有趣的提示。
Workqueue在kernel里面广泛应用于在进程上下文执行异步任务。过去多年来,workqueue进行了非常多的修改,完善对kernel里面应用场景的需求,能够让放到队列里的work request可以在同一个处理器上并发执行,互相竞争CPU时间。近来这方面的实现已经比较稳定了,也就是说大多数需要的功能都已经实现好了。
io_uring机制主要是允许user space创建异步线程来分别执行,因此毫无疑问workqueue在这种场景下应用得也非常广泛。不过随着时代发展,workqueue在这种场景下的一些限制变得越来越明显了。Workqueue主要解决的是何时何地来执行work function,而io_uring则可以从更高层的角度来管控这个任务是如何完成的。因此,新的机制被称为"io-wq"。
可以用下面函数来创建这种新的workqueue(也就是"io_wq"):
struct io_wq *io_wq_create(unsigned concurrency, struct mm_struct *mm);这里的concurrency是指在一个给定的NUMA node上所能够执行的worker线程的最大数量,而mm是跟这个queue关联在一起的memory-management context(内存管理上下文)。io_uring中,针对每一次io_uring_setup()调用,都会创建这些workqueue的其中之一,然后mm会指向调用者进程的mm_struct结构。把mm_struct跟io_wq关联起来能够更加简单的解决一些内存管理问题。现有的workqueue机制就没有这种mm关联关系,哪怕创建的是private workqueue也没有。
io_wq可以传递给io_wq_destroy()来销毁之。
如果想把某个work放进io_wq从而延后执行,就可以先填好下面这个结构:
struct io_wq_work {
struct list_head list;
void (*func)(struct io_wq_work **);
unsigned flags;
};这里主要做的事情是填好func,指向后面要完成这个任务的函数;flags则应该初始化为0,然后把此结构用下面的函数来加入队列:
void io_wq_enqueue(struct io_wq *wq, struct io_wq_work *work);
void io_wq_enqueue_hashed(struct io_wq *wq, struct io_wq_work *work,
void *val);调用io_wq_enqueue()就能把work加到queue里,从而后续会得到运行。io_wq_enqueue_hashed()这个变种函数,其实是我们之所以要创建这一新机制的原因之一,它能够确保拥有相同val值的两个任务不会同时执行。假如某个应用程序提交了很多buffered I/O request来访问(LWN评论中作者澄清这里特指写入操作)同一个文件,那么很明显这些操作不应该同时进行,否则就会因为拿锁竞争从而导致互相阻碍。而针对不同文件的buffered I/O操作则可以(也应该)同时执行。采用"Hashed"方法处理过的work任务就能够让io_uring更容易采用优化方法来管理并发访问。
以io_wq为参数来调用io_wq_flush()则会让本线程进入阻塞状态,直到queue队列的所有work任务都离开队列为止。请牢记,这并不代表这些work都已经完成操作了,而只是说明它们都已经开始执行了。
引入io-wq的另一个原因就是能做更好的取消操作。io_uring机制必须能允许user space来取消pending request,也就是说必须要能采用可控的方式来取消一个io-wq work request。目前kernel中,如果取消network socket上的一个request的话,有概率可能会导致死锁。这太不让人放心了,因此一定需要一个新方案。新的取消函数是下面这样:
void io_wq_cancel_all(struct io_wq *wq);
enum io_wq_cancel io_wq_cancel_work(struct io_wq *wq,
struct io_wq_work *cwork);调用第一个函数的话,会取消指定io_wq上的所有操作。第二个函数则会只取消指定的work request。总之,这都是通过对每个正在运行的worker thread发送SIGINT信号来实现的。当信号发出去之后,此函数就会返回,不会去等worker thread的相应。针对io_wq_cancel_work()来说,返回值可能是IO_WQ_CANCEL_OK(work request在开始执行之前就被取消了),IO_WQ_CANCEL_RUNNING(work request正在执行中,SIGINT已经发给它),IO_WQ_CANCEL_NOTFOUND(没找到这个work request,应该早就已经执行完了)。
上述就是这些新增的io-wq API了。不是很确定,如果其他kernel subsystem也迁移到这个机制的话,会不会有些什么好处。可能io_uring会是今后一段时间内的唯一用户了。不过io_uring能得到改善的话,很多用户都会奔走相告双手赞同了。
并且,这其实还意味着一些更好的苗头。LWN的长期读者可能还记得2007年的一系列讨论,不论是被称为fibrils, threadlets, 还是syslet,其实都是同一种机制。这个机制的目的就是希望能改善异步I/O支持的,不过同时也有另一个目标:允许user space来异步地调用任何系统调用(system call)。上述这些方案没有能达到合入kernel的程度,不过看来它们也并没有被大家所遗忘。在io-wq的第二版patch set中,Axboe就评论说在io_uring中使用io-wq的话“会让我们比起以前期望的异步执行任意系统调用的想法走近了一步”。因此,我们可以认为io_uring会能有助于我们获得13年前就期待着的功能了。大家拭目以待吧。
全文完
LWN文章遵循CC BY-SA 4.0许可协议。
极度欢迎将文章分享到朋友圈
热烈欢迎转载以及基于现有协议修改再创作~
长按下面二维码关注:Linux News搬运工,希望每周的深度文章以及开源社区的各种新近言论,能够让大家满意~

1669

被折叠的 条评论
为什么被折叠?



