【摘要】本文详解了中断服务下半部之工作队列实现机制。介绍了工作队列的特点、其与tasklet和softirq的区别以及其使用场合。接着分析了工作 队列的三种数据结构的组织形式,在此基础之上分析了工作队列执行流程。最后介绍了工作队列相关的API,如何编写自己的工作队列处理程序及定义一个 work对象并向内核提交等待调度运行。
【关键字】中断下半部,工作队 列,workqueue_struct,work_struct,DECLARE_WORK,schedule_work,schedule_delayed_work ,flush_workqueue,create_workqueue,destroy_workqueue
1 工作队列概述
工作队列(work queue)是另外一种将工作推后执行的形式,它和我们前面讨论的所有其他形式都不相同。工作队列可以把工作推后,交由一个内核线程去执行—这个下半部分 总是会在进程上下文执行,但由于是内核线程,其不能访问用户空间。最重要特点的就是工作队列允许重新调度甚至是睡眠。
通常,在工作队列和软中断/tasklet中作出选择非常容易。可使用以下规则:
2 如果推后执行的任务需要睡眠,那么只能选择工作队列;
2 如果推后执行的任务需要延时指定的时间再触发,那么使用工作队列,因为其可以利用timer延时;
2 如果推后执行的任务需要在一个tick之内处理,则使用软中断或tasklet,因为其可以抢占普通进程和内核线程;
2 如果推后执行的任务对延迟的时间没有任何要求,则使用工作队列,此时通常为无关紧要的任务。
另外如果你需要用一个可以重新调度的实体来执行你的下半部处理,你应该使用工作队列。它是惟一能在进程上下文运行的下半部实现的机制,也只有它才可以睡 眠。这意味着在你需要获得大量的内存时、在你需要获取信号量时,在你需要执行阻塞式的I/O操作时,它都会非常有用。
实际上,工作队列的本质就是将工作交给内核线程处理,因此其可以用内核线程替换。但是内核线程的创建和销毁对编程者的要求较高,而工作队列实现了内核线程 的封装,不易出错,所以我们也推荐使用工作队列。
2 工作队列的实现
2.1 工作者线程
工作队列子系统是一个用于创建内核线程的接口,通过它创建的进程负责执行由内核其他部分排到队列里的任务。它创建的这些内核线程被称作工作者线程 (worker thread)。工作队列可以让你的驱动程序创建一个专门的工作者线程来处理需要推后的工作。不过,工作队列子系统提供了一个默认的工作者线程来处理这些 工作。因此,工作队列最基本的表现形式就转变成了一个把需要推后执行的任务交给特定的通用线程这样一种接口。
默认的工作者线程叫做events/n,这里n是处理器的编号,每个处理器对应一个线程。比如,单处理器的系统只有events/0这样一个线程。而双处 理器的系统就会多一个events/1线程。
默认的工作者线程会从多个地方得到被推后的工作。许多内核驱动程序都把它们的下半部交给默认的工作者线程去做。除非一个驱动程序或者子系统必须建立一个属 于它自己的内核线程,否则最好使用默认线程。不过并不存在什么东西能够阻止代码创建属于自己的工作者线程。如果你需要在工作者线程中执行大量的处理操作, 这样做或许会带来好处。处理器密集型和性能要求严格的任务会因为拥有自己的工作者线程而获得好处。
2.2 工作队列的组织结构
2.2.1 工作队列workqueue_struct
外部可见的工作队列抽象,用户接口,是由每个CPU的工作队列组成的链表
64struct workqueue_struct {
65 struct cpu_workqueue_struct *cpu_wq;
66 const char *name;
67 struct list_head list; /* Empty if single thread */
68};
2 cpu_wq:本队列包含的工作者线程;
2 name:所有本队列包含的线程的公共名称部分,创建工作队列时的唯一用户标识;
2 list:链接本队列的各个工作线程。
在早期的版本中,cpu_wq是用数组维护的,即对每个工作队列,每个CPU包含一个此线程。改成链表的优势在于,创建工作队列的时候可以指定只创建一个 内核线程,这样消耗的资源较少。
在该结构体里面,给每个线程分配一个cpu_workqueue_struct,因而也就是给每个处理器分配一个,因为每个处理器都有一个该类型的工作者 线程。
2.2.2 工作者线程cpu_workqueue_struct
这个结构是针对每个CPU的,属于内核维护的结构,用户不可见。
43struct cpu_workqueue_struct {
44
45 spinlock_t lock;
46
47 long remove_sequence; /* Least-recently added (next to run) */
48 long insert_sequence; /* Next to add */
49
50 struct list_head worklist;
51 wait_queue_head_t more_work;
52 wait_queue_head_t work_done;
53
54 struct workqueue_struct *wq;
55 struct task_struct *thread;
56
57 int run_depth; /* Detect run_workqueue() recursion depth */
58} ____cacheline_aligned;
2 lock:操作该数据结构的互斥锁
2 remove_sequence:下一个要执行的工作序号,用于flush
2 insert_sequence:下一个要插入工作的序号
2 worklist:待处理的工作的链表头
2 more_work:标识有工作待处理的等待队列,插入新工作后唤醒对应的内核线程
2 work_done:处理完的等待队列,没完成一个工作后,唤醒可能等待通知处理完成通知的线程
2 wq:所属的工作队列节点
2 thread:关联的内核线程指针
2 run_depth:run_workqueue()循环深度,多处可能调用此函数
所有的工作者线程都是用普通的内核线程实现的,它们都要执行worker thread()函数。在它初始化完以后,这个函数执行一个死循环并开始休眠。当有操作被插入到队列里的时候,线程就会被唤醒,以便执行这些操作。当没有 剩余的操作时,它又会继续休眠。
2.2.3 工作work_struct
工作用work_struct结构体表示:
linux+v2.6.19/include/linux/workqueue.h
14struct work_struct {
15 unsigned long pending;
16 struct list_head entry;
17 void (*func)(void *);
18 void *data;
中断服务下半部之workqueue详解
最新推荐文章于 2024-04-28 16:16:38 发布