工作队列(work queue)是另外一种将工作推后执行的形式。工作队列可以把工作推后,交由一个内核线程去执行----这个下半部分总是会在进程上下文执行。这样,通过工作队列执行的代码能占尽进程上下文的所有优势。最重要的就是工作队列允许重新调度甚至是睡眠。
如果推后执行的任务需要睡眠,那么就选择工作队列;如果推后执行的任务不需要睡眠,那么就选择软中断或tasklet。实际上,工作队列通常可以用内核线程替换。但那是由于内核开发者们非常反对创建新的内核线程,所以推荐使用工作队列。如果需要用一个可以重新调度的实体来执行你的下半部处理,应该使用工作对列。它是唯一能在进程上下文运行的下半部实现的机制,也只有它才可以睡眠。这意味着在你需要获得大量的内存时、在你需要获取信号量时,在你需要执行阻塞式的I/O操作时,它都会非常有用。如果你不需要用一个内核线程来推后执行工作,那么就考虑使用tasklet吧。
下面介绍工作队列的实现
工作队列子系统是一个用于创建内核线程的接口,通过它创建的进程负责执行由内核其他部分排到队列的任务。它创建的这些内核线程被称作工作者线程(worker thread)。工作队列可以让你的驱动程序创建一个专门的工作者线程来处理需要推后的工作。不过,工作对垒子系统提供了一个默认的工作者线程来处理这些工作。因此,工作队列最基本的表现形式就转变成了一个把需要推后执行的任务交给特定的通用线程这样一种接口。
默认的工作者线程叫做events/n,这里n是处理器的编号,每个处理器对应一个线程。默认的工作者线程会从多个地方得到被推后的工作。许多内核驱动程序都把它们的下半部交给默认的工作者线程去做。除非一个驱动程序或者子系统必须建立一个属于它自己的内核线程,否则最好使用默认线程。
如果要执行大量的处理操作的话,创建属于自己的工作者线程是个很好的选择。处理器密集型和性能要求严格的任务会因为拥有自己的工作者线程而获得好处。此时,这样做有助于减轻默认线程的负担,避免工作队列中其他需要完成的工作处于饥饿状态。
1.表示线程的数据结构
- 在<kernel/workqueue.c>中
- struct workqueue_struct {
- struct cpu_workqueue_struct *cpu_wq;
- const char *name;
- struct list_head list;
- };
该结构内是一个由cpu_workqueue_struct结构组成的数组,数组的每一项对应系统中的一个处理器。由于系统中每个处理器对应一个工作者线程。所以对于给定的某台计算机来说,就是每个处理器,每个工作者线程对应一个这样的cpu_workqueue_struct结构体。
- 在<kernel/workqueue.c>中
- struct cpu_workqueue_struct {
- spinlock_t lock;
- long remove_sequence;
- long insert_sequence;
- struct list_head worklist;
- wait_queue_head_t more_work;
- wait_queue_head_t work_done;
- struct workqueue_struct *wq;
- struct task_struct *thread;
- int run_depth;
- int freezeable;
- } ____cacheline_aligned;
注意,每个工作者线程类型关联一个自己的workqueue_struct。在结构里面,给每个线程分配一个cpu_workqueue_struct ,因而也就是给每个处理器分配一个,因为每个处理器都有一个该类型的工作者线程。
2.表示工作的数据结构
所有的工作者线程都是用普通的内核线程实现的,它们都要执行worker_thread()函数。在它初始化完之后,这个函数执行一个死循环并开始休眠。当有操作被插入到队列里的时候,线程就会被唤醒,以便执行这些操作。当没有剩余的操作时,它又会继续休眠。
- 在<linux/workqueue.c>中
- static int worker_thread(void *__cwq)
- {
- struct cpu_workqueue_struct *cwq = __cwq;
- DECLARE_WAITQUEUE(wait, current);
- struct k_sigaction sa;
- sigset_t blocked;
- if (!cwq->freezeable)
- current->flags |= PF_NOFREEZE;
- set_user_nice(current, -5);
-
- sigfillset(&blocked);
- sigprocmask(SIG_BLOCK, &blocked, NULL);
- flush_signals(current);
-
- numa_default_policy();
-
- sa.sa.sa_handler = SIG_IGN;
- sa.sa.sa_flags = 0;
- siginitset(&sa.sa.sa_mask, sigmask(SIGCHLD));
- do_sigaction(SIGCHLD, &sa, (struct k_sigaction *)0);
- set_current_state(TASK_INTERRUPTIBLE);
- while (!kthread_should_stop()) {
- if (cwq->freezeable)
- try_to_freeze();
- add_wait_queue(&cwq->more_work, &wait);
- if (list_empty(&cwq->worklist))
- schedule();
- else
- __set_current_state(TASK_RUNNING);
- remove_wait_queue(&cwq->more_work, &wait);
- if (!list_empty(&cwq->worklist))
- run_workqueue(cwq);
- set_current_state(TASK_INTERRUPTIBLE);
- }
- __set_current_state(TASK_RUNNING);
- return 0;
- }
- 在<kernel/Kthread.c>中
- int kthread_should_stop(void)
- {
- return (kthread_stop_info.k == current);
- }
- 在<kernel/Workqueue.c>中
- static void run_workqueue(struct cpu_workqueue_struct *cwq)
- {
- unsigned long flags;
-
- spin_lock_irqsave(&cwq->lock, flags);
- cwq->run_depth++;
- if (cwq->run_depth > 3) {
-
- printk("%s: recursion depth exceeded: %d/n",
- __FUNCTION__, cwq->run_depth);
- dump_stack();
- }
- while (!list_empty(&cwq->worklist)) {
- struct work_struct *work = list_entry(cwq->worklist.next,
- struct work_struct, entry);
- work_func_t f = work->func;
- list_del_init(cwq->worklist.next);
- spin_unlock_irqrestore(&cwq->lock, flags);
- BUG_ON(get_wq_data(work) != cwq);
- if (!test_bit(WORK_STRUCT_NOAUTOREL, work_data_bits(work)))
- work_release(work);
- f(work);
- if (unlikely(in_atomic() || lockdep_depth(current) > 0)) {
- printk(KERN_ERR "BUG: workqueue leaked lock or atomic: "
- "%s/0x%08x/%d/n",
- current->comm, preempt_count(),
- current->pid);
- printk(KERN_ERR " last function: ");
- print_symbol("%s/n", (unsigned long)f);
- debug_show_held_locks(current);
- dump_stack();
- }
- spin_lock_irqsave(&cwq->lock, flags);
- cwq->remove_sequence++;
- wake_up(&cwq->work_done);
- }
- cwq->run_depth--;
- spin_unlock_irqrestore(&cwq->lock, flags);
- }
- 在<List.h(include/linux)>中
- static inline void list_del_init(struct list_head *entry)
- {
- __list_del(entry->prev, entry->next);
- INIT_LIST_HEAD(entry);
- }
- static inline void __list_del(struct list_head * prev, struct list_head * next)
- {
- next->prev = prev;
- prev->next = next;
- }
- static inline void INIT_LIST_HEAD(struct list_head *list)
- {
- list->next = list;
- list->prev = list;
- }
工作用work_struct结构体表示:
- 在<include/linux/Workqueue.h>
- typedef void (*work_func_t)(struct work_struct *work);
- #define work_data_bits(work) ((unsigned long *)(&(work)->data))
- struct work_struct {
- atomic_long_t data;
- #define WORK_STRUCT_PENDING 0 /* T if work item pending execution */
- #define WORK_STRUCT_NOAUTOREL 1 /* F if work item automatically released on exec */
- #define WORK_STRUCT_FLAG_MASK (3UL)
- #define WORK_STRUCT_WQ_DATA_MASK (~WORK_STRUCT_FLAG_MASK)
- struct list_head entry;
- work_func_t func;
- };
这些结构体被连接成链表,在每个处理器上的每种类型的队列都对应这样一个链表。比如,每个处理器上用于执行被推后的工作的那个通用线程就有一个这样的链表。当一个工作者线程被唤醒时,它会执行它的链表上的所有工作。工作被执行完毕,它就将相应的work_struct对象从链表上移去。当链表上不再有对象的时候,它就会继续休眠。
3.数据结构间的关系
位于最高一层的是工作者线程。系统允许有多种类型的工作者线程存在。对于指定的一个类型,系统的每个CPU上都有一个该类的工作者线程。内核中有些不封可以根据需要来创建工作者线程。而在默认情况下内核只有events这一种类型的工作者线程。每个工作者线程都由一个cpu_workqueue_struct结构体表示。而workqueue_struct结构体则表示给定类型的所有工作者线程。
工作处于最低一层。你的驱动程序创建这些需要推后执行的工作(可以理解成用“工作”这种接口封装我们实际需要推后的工作,以便后续的工作者线程处理)。它们用work_struct结构来表示。这个结构体中最重要的部分是一个指针,它指向一个函数,而正是该函数负责处理需要推后执行的具体任务。工作会被提交给某个具体的工作者线程。然后这个工作者线程会被唤醒并执行这些安排好的给偶内置。
大部分驱动程序都使用的是现存的默认工作者线程。在有些要求更严格的情况下,驱动程序需要自己的工作者线程。
呵呵,终于又帖完了:P
下午弄了很久,虽然点了“发表文章”但是还是失败了,希望这次能成功!!