工作队列(work queue)是Linux kernel中将工作推后执行的一种机制。这种机制和BH或Tasklets不同之处在于工作队列是把推后的工作交由一个内核线程去执行,因此工作队列的优势就在于它允许重新调度甚至睡眠。
workqueue,中文称其为工作队列,是一个用于创建内核线程的接口,通过它创建的内核线程来执行内核其他模块排列到队列里的工作,创建的内核线程被称为工作者线程。要理解工作队列的实现,重点在于理解相关的三个数据结构的含义及关系
工作队列是2.6内核开始引入的机制,在2.6.20之后,工作队列的数据结构发生了一些变化,因此本文分成两个部分对2.6.20之前和之后的版本分别做介绍。
I、2.6.0~2.6.19
1
2
3
4
5
6
7
8
|
struct work_struct { unsigned long
pending; struct list_head entry; void (*func)( void *); void *data; void *wq_data; struct timer_list timer; }; |
pending是用来记录工作是否已经挂在队列上;
entry是循环链表结构;
func作为函数指针,由用户实现;
data用来存储用户的私人数据,此数据即是func的参数;
wq_data一般用来指向工作者线程(工作者线程参考下文);
timer是推后执行的定时器。
work_struct的这些变量里,func和data是用户使用的,其他是内部变量,我们可以不用太过关心。
1. 表示工作队列类型的数据结构:struct workqueue_struct
- struct workqueue_struct {
- struct cpu_workqueue_struct *cpu_wq; /*工作者线程数组*/
- struct list_head list; /*连接工作队列类型的链表*/
- const char *name; /*工作者线程的名称*/
- int singlethread; /*是否创建新的工作者线程,0表示采用默认的工作者线程event/n*/
- int freezeable;/* Freeze threads during suspend*/
- int rt;
- #ifdef CONFIG_LOCKDEP
- struct lockdep_map lockdep_map;
- #endif
- };
内核中默认的工作队列为:
- static struct workqueue_struct *keventd_wq __read_mostly;
其对应的工作者线程为:event/n 其中,n代表当前cpu中processor的个数。
2. 表示工作者线程的数据结构:struct cpu_workqueue_struct
- struct cpu_workqueue_struct {
- spinlock_t lock; /*因为工作者线程需要频繁的处理连接到其上的工作,所以需要枷锁保护*/
- struct list_head worklist;
- wait_queue_head_t more_work;
- struct work_struct *current_work; /*当前工作线程需要处理的工作*/
- struct workqueue_struct *wq; /*该工作者线程属于那种类型的工作者队列*/
- struct task_struct *thread; /*指向工作者线程的任务结构体*/
- } ____cacheline_aligned;
workqueue的执行非常简单,即在每次运行工作者线程的时候,去遍历工作者线程对应的工作链表上的工作,逐一进行处理即可,从这里我们也可以猜到,工作队列是没有优先级的,基本按照FIFO的方式进行处理。
API:
1
2
3
4
5
|
INIT_WORK(_work, _func, _data); int
schedule_work( struct
work_struct *work); int
schedule_delayed_work( struct
work_struct *work, unsigned long
delay); void
flush_scheduled_work( void ); int
cancel_delayed_work( struct
work_struct *work); |
1、初始化指定工作,目的是把用户指定的函数_func及_func需要的参数_data赋给work_struct的func及data变量。
2、对工作进行调度,即把给定工作的处理函数提交给缺省的工作队列和工作者线程。工作者线程本质上是一个普通的内核线程,在默认情况下,每个CPU均有一个类型为“events”的工作者线程,当调用schedule_work时,这个工作者线程会被唤醒去执行工作链表上的所有工作。
3、延迟执行工作,与schedule_work类似。
4、刷新缺省工作队列。此函数会一直等待,直到队列中的所有工作都被执行。
5、flush_scheduled_work并不取消任何延迟执行的工作,因此,如果要取消延迟工作,应该调用cancel_delayed_work。
以上均是采用缺省工作者线程来实现工作队列,其优点是简单易用,缺点是如果缺省工作队列负载太重,执行效率会很低,这就需要我们创建自己的工作者线程和工作队列。
API:
1
2
3
4
5
|
struct workqueue_struct *create_workqueue( const char
*name); int
queue_work( struct
workqueue_struct *wq, struct
work_struct *work); int
queue_delayed_work( struct
workqueue_struct *wq, struct
work_struct *work, unsigned long
delay); void
flush_workqueue( struct
workqueue_struct *wq); void
destroy_workqueue( struct
workqueue_struct *wq); |
1、创建新的工作队列和相应的工作者线程,name用于该内核线程的命名。
2、类似于schedule_work,区别在于queue_work把给定工作提交给创建的工作队列wq而不是缺省队列。
3、延迟执行工作。
4、刷新指定工作队列。
5、释放创建的工作队列。
下面一段代码可以看作一个简单的实作:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
void
my_func( void
*data) { char *name = ( char *)data; printk(KERN_INFO “Hello world, my name is %s!\n”, name); } struct workqueue_struct *my_wq = create_workqueue(“my wq”); struct work_struct my_work; INIT_WORK(&my_work, my_func, “Jack”); queue_work(my_wq, &my_work); destroy_workqueue(my_wq); |
II、2.6.20~2.6.??
数据结构:
1
2
3
4
5
6
7
|
typedef void
(*work_func_t)( struct work_struct *work); struct work_struct { atomic_long_t data; struct list_head entry; work_func_t func; }; |
与2.6.19之前的版本相比,work_struct瘦身不少。粗粗一看,entry和之前的版本相同,func和data发生了变化,另外并无其他的变量。
entry我们不去过问,这个和以前的版本完全相同。data的类型是atomic_long_t,这个类型从字面上看可以知道是一个原子类型。第一次看到这个变量时,很容易误认为和以前的data是同样的用法,只不过类型变了而已,其实不然,这里的data是之前版本的pending和wq_data的复合体,起到了以前的pending和wq_data的作用。
func的参数是一个work_struct指针,指向的数据就是定义func的work_struct。
看到这里,会有两个疑问,第一,如何把用户的数据作为参数传递给func呢?以前有void *data来作为参数,现在好像完全没有办法做到;第二,如何实现延迟工作?目前版本的work_struct并没有定义timer。
解决第一个问题,需要换一种思路。2.6.20版本之后使用工作队列需要把work_struct定义在用户的数据结构中,然后通过container_of来得到用户数据。具体用法可以参考稍后的实作。
对于第二个问题,新的工作队列把timer拿掉的用意是使得work_struct更加单纯。首先回忆一下之前版本,只有在需要延迟执行工作时才会用到timer,普通情况下timer是没有意义的,所以之前的做法在一定程度上有些浪费资源。所以新版本中,将timer从work_struct中拿掉,然后又定义了一个新的结构delayed_work用于处理延迟执行:
1
2
3
4
|
struct delayed_work { struct work_struct work; struct timer_list timer; }; |
下面把API罗列一下,每个函数的解释可参考之前版本的介绍或者之后的实作:
1
2
3
4
5
6
7
8
9
10
11
|
INIT_WORK( struct work_struct *work, work_func_t func); INIT_DELAYED_WORK( struct delayed_work *work, work_func_t func); int
schedule_work( struct
work_struct *work); int
schedule_delayed_work( struct
delayed_work *work, unsigned long
delay); struct workqueue_struct *create_workqueue( const char
*name); int
queue_work( struct
workqueue_struct *wq, struct
work_struct *work); int
queue_delayed_work( struct
workqueue_struct *wq, struct
delayed_work *work, unsigned long
delay); void
flush_scheduled_work( void ); void
flush_workqueue( struct
workqueue_struct *wq); int
cancel_delayed_work( struct
delayed_work *work); void destroy_workqueue( struct workqueue_struct *wq); |
其中,1、2、4、7和以前略有区别,其他用法完全一样。
实作:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
struct my_struct_t { char *name; struct work_struct my_work; }; void
my_func( struct
work_struct *work) { struct my_struct_t *my_name = container_of(work,
struct my_struct_t, my_work); printk(KERN_INFO “Hello world, my name is %s!\n”, my_name->name); } struct workqueue_struct *my_wq = create_workqueue(“my wq”); struct my_struct_t my_name; my_name.name = “Jack”; INIT_WORK(&(my_name.my_work), my_func); queue_work(my_wq, &(my_name.my_work)); destroy_workqueue(my_wq); |