linux 队列(linux内核进程队列)
前面我们讲了USB Hub使用工作队列的例子。一个模块可以通过三个接口使用它:创建队列、初始化任务和将任务输入队列。本文分析了工作队列接口的实现以及相关对象之间的关系。
1.创建队列
工作队列(workqueue_struct)保存相关的参数、列表以及与线程池的关系。由alloc_workqueue宏创建,代码如下图所示:
alloc_workqueue宏最终调用__alloc_workqueue_key()来实现:
这个函数的代码比较长,如果每一行都解析的话就没有必要了,所以我只展示骨干代码,其余的都是折叠的,有助于我们理解主要流程。创建workqueue_struct时,实现了三个关键步骤,即分配空间和初始化、分配pool_workqueue对象并将其连接到线程池、将队列添加到全局队列列表。
分别分配空间和初始化。
第353-3879行,分配workqueue_struct对象空间,分配时指定GFP_KERNEL参数,表示内存不足时可能会导致休眠,所以不应该在中断上下文中使用。分配成功后,设置相关属性,初始化pwq列表、求救列表等。这些参数都用于任务调度,我们将分别分析调度的细节。
分配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对象并与线程池连接。
线程池连接到绑定的中央处理器
第383行,cpu_pwqs是__percpu类型的变量。这种类型的变量为每个CPU分配一个副本,CPU根据自己的索引计算偏移量取出自己的副本使用,可以有效减少锁的使用,提高缓存的利用率。
第3788-3791行,取出每个CPU复制对象的指针。
第393-3797行,根据队列的优先级选择对应的线程池并绑定。
连接到未绑定线程池
未绑定线程池分为有无__WQ_ORDERED标志,用于保证顺序调度,实现逻辑基本相同。
第300-3809行,未绑定线程池通过队列属性来区分,也就是说,不同属性的队列会有对应的线程池进行调度。
2.任务初始化
任务(work_struct)对象应该在使用前初始化。代码如下:
第203-213行,如果定义了CONFIG_LOCKDEP,则为线程死锁检测定义了更多的数据。我们暂时不分析死锁检测原理,CONFIG_LOCKDEP默认也是关闭的。
15-220行,INIT_WORK的具体实现,首先把宏用do…while(0)括起来,这是一种常见的宏定义语法,为了更好地将宏代码嵌入到使用场景中,又不混淆扩展后的代码。
第27行,WORK_DATA_INIT宏将WORK的数据成员设置为枚举类型WORK_STRUCT_NO_POOL的值,表示它还没有加入任何线程池。
第28行,初始化条目链表,工作线程通过这个链表遍历任务列表。
29行,设置
置任务执行的函数,任务调度一次此函数执行一次,如果需要反复执行应在外层重复的提交任务,不能在函数内做无限循环处理,这样会堵塞线程调度影响其他任务执行。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系统调用源码分析(三)