本文转载自:http://blog.csdn.net/tommy_wxie/article/details/7204306
工作项、工作队列和工作者线程
把推后执行的任务叫做工作(work),描述它的数据结构为work_struct ,这些工作以队列结构组织成工作队列(workqueue),其数据结构为workqueue_struct ,而工作线程就是负责执行工作队列中的工作。系统默认的工作者线程为events。
工作队列(work queue)是另外一种将工作推后执行的形式。工作队列可以把工作推后,交由一个内核线程去执行—这个下半部分总是会在进程上下文执行,但由于是内核线程,其不能访问用户空间。最重要特点的就是工作队列允许重新调度甚至是睡眠。
通常,在工作队列和软中断/tasklet中作出选择非常容易。可使用以下规则:
如果推后执行的任务需要睡眠,那么只能选择工作队列;
如果推后执行的任务需要延时指定的时间再触发,那么使用工作队列,因为其可以利用timer延时;
如果推后执行的任务需要在一个tick之内处理,则使用软中断或tasklet,因为其可以抢占普通进程和内核线程;
如果推后执行的任务对延迟的时间没有任何要求,则使用工作队列,此时通常为无关紧要的任务。
实际上,工作队列的本质就是将工作交给内核线程处理,因此其可以用内核线程替换。但是内核线程的创建和销毁对编程者的要求较高,而工作队列实现了内核线程的封装,不易出错,所以我们也推荐使用工作队列。
工作队列使用
相关文件:
kernel/include/linux/workqueue.h
Kernel/kernel/workqueue.c
工作队列的创建,有两种方式:
1)静态创建:
DECLARE_WORK(name,function); 定义正常执行的工作项
DECLARE_DELAYED_WORK(name,function); 定义延后执行的工作项
2)动态创建,运行时创建:
通常在probe()函数中执行下面的操作来初始化工作项:
INIT_WORK(&work, new_ts_work);
INIT_DELAYED_WORK(&led_work,s0340_ledtime_scanf);
工作队列待执行的函数原型是:
typedef void(*work_func_t)(structwork_struct *work);
这个函数会由一个工作者线程执行,因此,函数会运行在进程上下文中。默认情况下,允许响应中断,并且不持有任何锁。如果需要,函数可以睡眠。需要注意的是,尽管该函数运行在进程上下文中,但它不能访问用户空间,因为内核线程在用户空间没有相关的内存映射。通常在系统调用发生时,内核会代表用户空间的进程运行,此时它才能访问用户空间,也只有在此时它才会映射用户空间的内存。
创建了工作项之后,在适当的时候可以通过下面的两种方式来提交工作项给工作者线程,通常我们使用的工作队列和工作者线程都是系统初始化时候默认创建的。
工作队列的调度运行
schedule_work(&work);
&work马上就会被调度,一旦其所在的处理器上的工作者线程被唤醒,它就会被执行。
schedule_delayed_work(&delay_work,delay);
&delay_work指向的delay_work直到delay指定的时钟节拍用完以后才会执行。
eg:
schedule_delayed_work(&kpd_backlight_work,msecs_to_jiffies(300));
默认工作队列和工作者线程创建过程
系统默认的工作队列名称是:keventd_wq,默认的工作者线程叫:events/n,这里的n是处理器的编号,每个处理器对应一个线程。比如,单处理器的系统只有events/0这样一个线程。而双处理器的系统就会多一个events/1线程。
默认的工作者线程会从多个地方得到被推后的工作。许多内核驱动程序都把它们的下半部交给默认的工作者线程去做。除非一个驱动程序或者子系统必须建立一个属于它自己的内核线程,否则最好使用默认线程。不过并不存在什么东西能够阻止代码创建属于自己的工作者线程。如果你需要在工作者线程中执行大量的处理操作,这样做或许会带来好处。处理器密集型和性能要求严格的任务会因为拥有自己的工作者线程而获得好处。
默认的工作队列keventd_wq只有一个,但是其工作者线程在每一个cpu上都有。而标记为singlethread的工作者线程最存在于一个cpu上。
关于默认工作队列keventd_wq和工作者线程events/n的建立在文件Kernel/kernel/workqueue.c中实现。
Start_kernel()-->rest_init(),该函数中创建了两个内核线程kernel_init和kthreadd,这两个线程都和本文描述的部分有关系,先说说kernel_init。
kernel_init()-->do_basic_setup()-->init_workqueues(),该函数中创建了上面提到的默认工作队列和工作者线程。
init_workqueues()-->
-->hotcpu_notifier(workqueue_cpu_callback,0);
-->keventd_wq=create_workqueue("events");
注册的cpu通知链cpu_chain上的回调函数是workqueue_cpu_callback(),raw_notifier_call_chain()函数用来调用cpu_chain上的所有回调函数。
这里主要关注的是函数:create_workqueue("events");
@kernel/include/linux/workqueue.h
#define__create_workqueue(name,singlethread,freezeable,rt)/
__create_workqueue_key((name),(singlethread),(freezeable),(rt),/NULL,NULL)
#definecreate_workqueue(name)__create_workqueue((name),0,0,0)
#definecreate_rt_workqueue(name)__create_workqueue((name),0,0,1)
#definecreate_freezeable_workqueue(name)__create_workqueue((name),1,1,0)
#definecreate_singlethread_workqueue(name)__create_workqueue((name),1,0,0)
从宏__create_workqueue的参数可以看出,可以通过传递不同的参数:是否单cpu线程,是否可冻结,是否实时来创建不同类型的工作队列和工作者线程。
work_struct工作项结构体定义:@kernel/include/linux/workqueue.h
工作队列workqueue_struct结构体:@kernel/kernel/workqueue.c