龙空技术网

Linux工作队列workqueue源码分析(二)

虫虫飞亚 275

前言:

此刻咱们对“initqueue函数”可能比较关怀,看官们都想要分析一些“initqueue函数”的相关内容。那么小编同时在网络上网罗了一些有关“initqueue函数””的相关资讯,希望同学们能喜欢,小伙伴们快快来学习一下吧!

前文我们讲了USB Hub使用工作队列的例子,一个模块通过创建队列,初始化任务,任务入队列三个接口就可以使用它。这篇文章我们分析一下工作队列接口的实现及相关对象之间的关系。

1、创建队列

工作队列(workqueue_struct)保存了相关的参数,列表,及与线程池之间的关系。通过alloc_workqueue宏创建,代码如下图所示:

alloc_workqueue宏最终调用__alloc_workqueue_key()实现:

这个函数的代码比较长,如果每一行都解析不知道如何下手,也没有必要,所以我只显示了骨干代码,其他的都折叠起来了,这样有助于我们理解主要流程。在创建workqueue_struct的的时候,实现三个关键的步骤,分别是分配空间及初始化、分配pool_workqueue对象并连接到线程池及把队列加入全局队列列表中。

分别是分配空间及初始化

3853-3879行,分配workqueue_struct对象空间,分配的时候指定GFP_KERNEL参数,说明内存不足的时候有可能会造成睡眠,所以不宜在中断上下文中使用。分配成功后,设置相关的属性,初始化pwq列表,mayday列表等,这些参数都是在任务调度的时候用到的,调度的细节我们另文分析。

分配pool_workqueue对象并连接到线程池

pool_workqueue对象是连接工作队列(workqueue_struct)与线程池(worker_pool)的对象。我们先说一下线程池,在Linux内核中,worker_pool主要分为两种,一种和CPU绑定,另一种不和CPU绑定。和CPU绑定中的线程只在绑定的CPU上运行,这对CPU亲缘性及局部热缓存有帮助;不和CPU绑定的线程管理比较灵活,可以在任何一个CPU中运行,对平衡CPU性能有帮助,但在切换线程上下文的时候会引起缓存失效。内核启动时候给每个CPU创建两个线程池,一个高优先级,一个低优先级,然后再创建两个非绑定的线程池。pool_workqueue负责把工作队列和线程池连接起来。

alloc_and_link_pwqs()创建pool_workqueue对象并与线程池连接。

连接到绑定CPU的线程池

3783行,cpu_pwqs是__percpu类型的变量,这种类型的变量为每一个CPU都分配一个副本,CPU根据自己的索引计算偏移量取出自己的副本来使用,这样可以有效降低锁的使用,提高cache利用率。

3788-3791行,取出每个CPU副本对象指针。

3793-3797行,根据队列的优先级选择相应的线程池并绑定起来。

连接到非绑定线程池

非绑定线程池又分为有无__WQ_ORDERED标志,用于保障顺序调度,实现逻辑基本相同。

3800-3809行,非绑定线程池是通过队列属性来区分的,也就是说不同属性的队列会有相应的线程池进行调度。

2、任务初始化

任务(work_struct)对象在使用前要先初始化,代码如下所示:

203-213行,如果定义了CONFIG_LOCKDEP,则定义多一些数据用于线程死锁检测。我们暂时不去分析死锁检测原理, 且CONFIG_LOCKDEP默认也是关闭的。

215-220行,INIT_WORK的具体实现,首先用一个do…while(0)把宏括起来,这是常见的宏定义语法,目的让宏代码更好地嵌入到使用场景中而不至于混淆展开后的代码。

217行,WORK_DATA_INIT宏把work的data成员设成枚举类型WORK_STRUCT_NO_POOL的值,表示当前还没有加入到任何线程池里。

218行,初始化entry链表,工作者线程通过此链表遍历任务列表。

219行,设置任务执行的函数,任务调度一次此函数执行一次,如果需要反复执行应在外层重复的提交任务,不能在函数内做无限循环处理,这样会堵塞线程调度影响其他任务执行。

3、任务入队列

我们先看一下入队列的接口

queue_work是一个inline函数,内核中很多接口采用这种用法,在头文件中定义一个inline函数包装一下实际业务的函数,这样过度一下可以有效的降低代码的耦合度。

实际执行的函数是queue_work_on,代码如下:

1455行,1462行,关闭/打开本地中断,防止work的data并发设置。

1457行,设置work->data的WORK_STRUCT_PENDING_BIT,表示任务已经在处理了,完成之前不能重复提交。

1458行,调用入队列函数。

__queue_work函数比较长,同样折叠了部分代码,如下:

1365-1368行,这一部分主要获得pool_workqueue对象,跟据work_struct的标志有没有指定WQ_UNBOUND获取相应的pwq指针。

1424-1431行,判断pwq中已经激活的线程数是否小于最大线程数,如果是则加入任务队列调度执行;否则,说明线程都在忙碌的工作中,应该把任务加到延迟工作队列中,之后再调度执行。

工作队列和每一个线程池都会连接在一起,但任务在一个时刻只会插入到一个pwq对象中去,这样在执行调度的时候可以更方便的管理。

以上就是工作队列三个接口的主要实现及对象间的关系,后续我们再另文分析线程池的管理及线程的调度。

(arch:arm64,kernel:v4.4)

相关文章

Linux工作队列workqueue源码分析(一)

Linux系统调用源码分析(四)

Linux系统调用源码分析(三)

标签: #initqueue函数