13.1 内核工作队列
在linux中断编程中,需要中断程序分成中断顶部和中断底部两部分,顶部负责做中断标志,然后耗时的事情在中断底部执行。
那么底部分代码实现可以通过内核工作队列实现。我们就必须先知道什么是内核工作对列。
工作队列(work queue)是另外一种将工作推后执行的形式,它和内核定时器推后的情况有所不同。工作队列可以把工作推后,交由一个内核线程去执行,也就是说,这个下半部分可以在进程上下文中执行。最重要的就是工作队列允许被重新调度甚至是睡眠。
工作、工作队列和工作者线程
如前所述,我们把推后执行的任务叫做工作(work),描述它的数据结构为work_struct,这些工作以队列结构组织成工作队列(workqueue),其数据结构为workqueue_struct,而工作线程就是负责执行工作队列中的工作。系统有默认的工作者线程,自己也可以创建自己的工作者线程。
13.1.1 工作结构
具体描述如下表:
名称 | 具体描述 |
定义文件 | Workqueue.h (linux-3.5\include\Linux) |
原型 | struct work_struct { atomic_long_t data; struct list_head entry; work_func_t func;/* 工作函数指针 */ #ifdef CONFIG_LOCKDEP struct lockdep_map lockdep_map; #endif }; |
成员介绍 | 我们只需要关心一个成员函数:work_func_t func; 工作函数是一个函数指针,这个成员是指向工作函数的指针; |
内核使用这个结构来描述一个工作,一个工作简单理解就是对应于一个函数,可以通过内核调度函数来调用work_struct中func指针所指向的函数。 |
在内核中,一个工作(就是一段代码)就使用一个struct work_struct 结构表示。
13.1.2 工作服务函数
工作函数指针原型如下:
名称 | 具体描述 |
定义文件 | Workqueue.h (linux-3.5\include\linux) |
函数原型 | typedef void (*work_func_t)(struct work_struct *work); |
功能 | 是一个函数指针,编写一个工作的函数。 |
参数 | struct work_struct *work: 这个参数,指向struct work_struct结构变量本身。 |
示例:
struct work_struct work; INIT_WORK(&work, work_func); |
这样初始化了一个work结构,work_func工作函数的参数就是指向work结构。
13.1.3初始化work结构的宏
1)初始化一个work结构:
INIT_WORK(_work, _func) |
_work: struct work_struct结构指针。
_func:用来填充work_struct结构的fun成员,就是工作函数指针。
2)共享工作队列调度宏:
schedule_work(_work) |
它也是一个宏,作用是调度一个工作_work。
_work:要调度工作的结构指针;
示例:
schedule_work(&work) |
13.1.4 使用工作队列步骤
1)定义一个工作结构变量
struct work_struct work; |
2)初始化工作结构(重点func成员)。
先编写一个工作函数:
void work_func(structwork_struct * dat) { printk(“%p:”,dat); …… } |
初始化work:
INIT_WORK(&work, work_func); |
- 在适当的地方调度工作
如果工作用于中断底部代码,则在中断顶部调度。
schedule_work(&work); |
不是马上执行,而是等待CPU空闲才执行work_func。
13.1.5 自定义工作队列
内核有初始化好的工作队列,因为工作队列在内核驱动中使用比较频繁,为了方便使用不用每人都去注册一个工作队列,所以内核提供了一个公共的工作队列。
那么什么时候我们要自己去创建工作队列呢?
共享工作队列是每人都可以使用的,那就意味着你不能一直霸占他去运行你的程序。
不过一般情况不可能运行那么多的代码,而且需要延迟时可以用睡眠。这里只简要介绍。
序号 | 接口函数 | 说明 |
1 | create_workqueue | 用于创建一个workqueue队列,为系统中的每个CPU都创建一个内核线程。输入参数: @name:workqueue的名称 |
2 | create_singlethread_workqueue | 用于创建workqueue,只创建一个内核线程。输入参数: @name:workqueue名称 |
3 | destroy_workqueue | 释放workqueue队列。输入参数: @ workqueue_struct:需要释放的workqueue队列指针 |
4 | queue_work | 调度执行一个指定workqueue中的任务。输入参数: @ workqueue_struct:指定的workqueue指针 @work_struct:具体任务对象指针 |
5 | queue_delayed_work | 延迟调度执行一个指定workqueue中的任务,功能与queue_work类似,输入参数多了一个delay。 |
注册函数中
struct workqueue_struct *wq=NULL;
创建自己的工作队列,加入工作到工作队列(加入内核就对其调度执行)*/ wq=create_workqueue("test"); INIT_WORK(&my_work,work_func); queue_work(wq,&my_work); |
注销函数中
/*工作队列销毁*/ destroy_workqueue(wq); |
schedule_work函数也是调用queue_work再把工作队列参数默认添加成系统提供的工作队列。
13.1.6共享工作队列示例代码
使用内核共享工作队列,演示如何使用work参数。
#include <linux/module.h> #include <linux/init.h> #include <linux/workqueue.h> typedef struct __mydat{ struct work_struct mywork; int x; int y; int z; } mydat_t; //工作服务函数 static void work_handler(struct work_struct *data) { mydat_t *p; //计算结构体首地址container_of(结构体某成员的地址, 结构体, 结构体某成员) //p = container_of(data, mydat_t, mywork); //或使用下面一条代码 p = (mydat_t *)data; printk(KERN_EMERG "data:%p,\n", data); printk(KERN_EMERG "x:%d,\n", ((mydat_t *)data)->x); printk(KERN_EMERG "y:%d,\n", ((mydat_t *)data)->y); printk(KERN_EMERG "x:%d,\n", p->x); printk(KERN_EMERG "y:%d,\n", p->y); printk(KERN_EMERG "work handler function \n"); } /* 初始化函数 */ static int __init test_init(void) { //struct work_struct work; static mydat_t work; work.x = 123; work.y = 456; printk(KERN_EMERG "&work:%p\n", &work); //把work放置内核共享工作队列中 INIT_WORK(&work.mywork, work_handler); //调用工作 schedule_work(&work.mywork); printk("test_init \n"); return 0; } /* 卸载函数 */ static void __exit test_exit(void) { printk("%s is call\r\n", __FUNCTION__); } MODULE_LICENSE("GPL"); module_init(test_init); module_exit(test_exit); |
1.3.1.7 工作队列的其他函数
一些帮助函数用于清理或取消工作队列中的任务。想清理特定的任务项目并阻塞任务,直到任务完成为止,可以调用 flush_work 来实现。 指定工作队列中的所有任务能够通过调用 flush_workqueue 来完成。 这两种情形下,调用者阻塞直到操作完成为止。 为了清理内核全局工作队列,可调用 flush_scheduled_work。
int flush_work( struct work_struct *work ); int flush_workqueue( struct workqueue_struct *wq ); void flush_scheduled_work( void ); |
还没有在处理程序当中执行的任务可以被取消。调用 cancel_work_sync 将会终止队列中的任务或者阻塞任务直到回调结束(如果处理程序已经在处理该任务)。 如果任务被延迟,可以调用 cancel_delayed_work_sync。
int cancel_work_sync( struct work_struct *work ); int cancel_delayed_work_sync( struct delayed_work *dwork ); |
可以通过调用 work_pending 或者 delayed_work_pending 来确定任务项目是否在进行中。
#define work_pending(work) \ test_bit(WORK_STRUCT_PENDING_BIT, work_data_bits(work)) #define delayed_work_pending(w) \ work_pending(&(w)->work) 说明:返回值0表示成功执行 1表示未执行 |
13.1.8创建自定义工作队列示例代码
代码功能:创建自定义的工作队列(creator_workqueue)
#include <linux/kernel.h> //内核头文件 #include <linux/module.h> //模块 #include <linux/workqueue.h> /*工作队列相关*/ /*创建自己的工作队列相关*/ struct workqueue_struct *my_work_queue; /*内核工作队列相关结构体*/ static struct work_struct my_work; /*工作队列的处理函数*/ static void my_work_func(struct work_struct *work) { printk("\n\n工作队列调度成功!\n\n"); } static int __init tiny4412_module_init(void) { /*创建自己的工作队列*/ my_work_queue =create_workqueue("my_workqueue"); /*初始化工作队列*/ INIT_WORK(&my_work,my_work_func); /*调度工作队列*/ queue_work(my_work_queue,&my_work); return 0; } static void __exit tiny4412_module_cleanup(void) { } module_init(tiny4412_module_init); //驱动的入口,驱动安装的时候调用 module_exit(tiny4412_module_cleanup); //驱动的的出口,驱动卸载的时候调用 MODULE_LICENSE("GPL"); //驱动的许可证声 |
13.1.9 注销工作队列
void destroy_workqueue(struct workqueue_struct *wq) |
工作队列不使用之后就直接注销。
13.1.10 等待工作队列执行完成
bool cancel_work_sync(struct work_struct *work) |
取消工作队列执行,并等待工作队列执行完成。
示例:
cancel_work_sync(&ts->work); destroy_workqueue(ts->queue); |
13.2 内核延时工作队列
相同点:和上面的内核工作队列一样,可以实现中断的底部代码功能。
不同点:和内核工作队列惟一区别就是延后的时间可控制。
13.2.1 关键数据结构:
延时工作队列结构:
struct delayed_work { struct work_struct work; //工作结构 struct timer_list内核定时器结构 }; |
实际就是工作队列和内核定时器组成。
13.2.2 初始化宏
INIT_DELAYED_WORK(_work, _func) |
它是内核定义的一个宏,定义在
功能:初始化一个delayed_work结构。
参数:
_work: struct delayed_work *work结构指针,即传递的是指针。
_func:用来填充struct delayed_work结构的fun指针。
13.2.3 调度函数
内核提供了一个函数来调度延时工作队列。
头文件 | |
函数原型 | int schedule_delayed_work(struct delayed_work *dwork, unsigned long delay) |
功能 | |
参数 | dwork:要调度的延时工作结构指针。 delay:延后调度的时间,单位是时钟节拍。和内核定时器定时时间单位相同,但是不是到期时间,而是定时时间。 |
返回值 |
13.2.4 应用延时工作队列步骤
1)定义一个struct delayed_work结构变量。
2)编写一个延时工作函数。
3)初始化struct delayed_work结构。
3)在适当地方调用工作。
13.2.5 延时工作队列示例代码
#include <linux/kernel.h> //内核头文件 #include <linux/module.h> //模块 #include <linux/workqueue.h> /*工作队列相关*/ /*延时工作队列相关结构体*/ static struct delayed_work my_delay_work; /*工作队列的处理函数*/ static void my_work_func(struct work_struct *work) { printk("\n\n延时系统共享工作队列调度成功\n"); } static int __init tiny4412_module_init(void) { /*初始化延时工作队列*/ INIT_DELAYED_WORK(&my_delay_work,my_work_func); /*添加延时工作到系统工作队列中等待执行*/ schedule_delayed_work(&my_delay_work,HZ*5); return 0; } static void __exit tiny4412_module_cleanup(void) { } module_init(tiny4412_module_init); //驱动的入口,驱动安装的时候调用 module_exit(tiny4412_module_cleanup); //驱动的的出口,驱动卸载的时候调用 MODULE_LICENSE("GPL"); //驱动的许可证声 |