LWN: 为了io_uring重新设计workqueue

点击上方蓝色“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搬运工,希望每周的深度文章以及开源社区的各种新近言论,能够让大家满意~

int battery_type_check(int *battery_type) { int value = 0; int ret = 0; int ret_value = 0; int battery_id = 0; int battery_type_check = 0; if (batt_id == NULL) { bm_debug("[battery_type_check]: batt_id iio channel is null []\n"); *battery_type = BAT_TYPE__ATL_4400mV; battery_id = 0; return battery_id; } ret = iio_read_channel_processed(batt_id, &ret_value); if (ret < 0) bm_debug( "[battery_type_check] read channel err = %d,\n", ret); bm_debug( "[battery_type_check]: ret = %d,ret_value[%d]\n", ret, ret_value); value = ret_value; if(is_fuelgauge_apply() == true) { switch(battery_id) case 0: if (value >= BAT_TYPE_COS_4450mV_ADC_MIN && value <= BAT_TYPE_COS_4450mV_ADC_MAX) { *battery_type = BAT_TYPE__COS_4450mV; battery_id = BAT_COS_BATT_ID; break; case 1: if (value >= BAT_TYPE_ATL_4450mV_ADC_MIN && value <= BAT_TYPE_ATL_4450mV_ADC_MAX) { *battery_type = BAT_TYPE__ATL_4450mV; battery_id = BAT_ATL_BATT_ID; break; case 2: if (value >= BAT_TYPE_LWN_4450mV_ADC_MIN && value <= BAT_TYPE_LWN_4450mV_ADC_MAX) { *battery_type = BAT_TYPE__LWN_4450mV; battery_id = BAT_LWN_BATT_ID; break; case 3: *battery_type = BAT_TYPE__UNKNOWN; battery_id = BAT_ENC_BATT_ID; break; default:【兼容原来的方案】 if (value >= BAT_TYPE_ATL_4450mV_ADC_MIN && value <= BAT_TYPE_ATL_4450mV_ADC_MAX) { *battery_type = BAT_TYPE__ATL_4450mV; battery_id = BAT_ATL_BATT_ID; } else if (value >= BAT_TYPE_LWN_4450mV_ADC_MIN && value <= BAT_TYPE_LWN_4450mV_ADC_MAX) { *battery_type = BAT_TYPE__LWN_4450mV; battery_id = BAT_LWN_BATT_ID; } else if (value >= BAT_TYPE_COS_4450mV_ADC_MIN && value <= BAT_TYPE_COS_4450mV_ADC_MAX) { *battery_type = BAT_TYPE__COS_4450mV; battery_id = BAT_COS_BATT_ID; } else { *battery_type = BAT_TYPE__UNKNOWN; battery_id = BAT_ENC_BATT_ID; } } else { *battery_type = BAT_TYPE__UNKNOWN; battery_id = BAT_ENC_BATT_ID; } printk(KERN_ERR "[battery_type_check]: adc_value[%d], battery_type[%d], g_fg_battery_id[%d]\n", value, *battery_type, battery_id); return battery_id; }
08-21
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值