首页天道酬勤linux 队列(linux内核进程队列)

linux 队列(linux内核进程队列)

admin 12-02 10:22 334次浏览

前面我们讲了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系统调用源码分析(三)

解绑防火墙 负载均衡 ULB2核/2GB 内存/40GB SSD/RocketMQ 消息失败重试 解析——图解用透视图渲染HTML元素?云数据库快速上手 PostgreSQL UDBXcode11BetaSwiftUI预览不显示Docker发布镜像到DockerHub雷士灯具管理系统
() linux查看队列命令(linux工作队列)
相关内容