Linux 驱动之中断下半部之工作队列
1、工作队列的使用
按惯例,在介绍工作队列如何实现之前,先说说如何使用工作队列实现下半部。
步骤一、定义并初始化工作队列:
创建工作队列函数: struct workqueue_struct *create_workqueue(const char *name) 函数传参是内核中工作队列的名称,返回值是workqueue_struct结构体的指针,该结构体用来维护一个等待队列。 我的代码如下: struct workqueue_struct * ZP1015_wq; //定义工作队列 ZP1015_wq = create_workqueue(" ZP1015");
步骤二、定义并初始化work结构体:
内核使用结构体来维护一个加入工作队列的任务:
/*linux/workqueue.h*/ struct work_struct { atomic_long_t data; #define WORK_STRUCT_PENDING 0 /* T if work item pending execution */ #define WORK_STRUCT_FLAG_MASK (3UL) #define WORK_STRUCT_WQ_DATA_MASK (~WORK_STRUCT_FLAG_MASK) struct list_head entry; work_func_t func; //这个是重点,下半部实现的处理函数指针就放在这 #ifdef CONFIG_LOCKDEP struct lockdep_map lockdep_map; #endif };
同样有静态和动态两种方法:
①静态定义并初始化work结构体:
/*linux/workqueue.h*/
#define DECLARE_WORK(n, f) \
struct work_struct n = __WORK_INITIALIZER(n, f)
定义并初始化一个叫n的work_struct数据结构,它对应的的处理函数是f。
②对应的动态初始化方法,该函数返回work_struct指针,所以需要先定义一个work_struct结构:
/*linux/workqueue.h*/
#define INIT_WORK(_work, _func) \
do { \
(_work)->data = (atomic_long_t) WORK_DATA_INIT(); \
INIT_LIST_HEAD(&(_work)->entry); \
PREPARE_WORK((_work), (_func)); \
} while (0)
#endif
跟tasklet一样,在初始化的同时,需要将处理函数实现,代码如下:
struct work_struct ZP1015_work; //2定义work结构体
void ZP1015_func(struct work_struct *work)
//2实现处理函数
{
printk("hello ZP1015!\n");
}
INIT_WORK(&ZP1015_work, ZP1015_func); //2初始化work结构体
步骤三、在中断处理函数中调度任务:
工作队列和worl结构体都已经实现了,接下来就可以调度了,使用一下函数: /*kernel/workqueue.c*/ int queue_work(struct workqueue_struct *wq, struct work_struct *work) 将指定的任务(work_struct),添加到指定的工作队列中。同样的,调度并不代表处理函数能够马上执行,这由内核进程调度决定。
步骤四、在卸载模块时,刷新并注销等待队列:
刷新等待队列函数: /*kernel/workqueue.c*/ void flush_workqueue(struct workqueue_struct *wq) 该函数会一直等待,知道指定的等待队列中所有的任务都执行完毕并从等待队列中移除。 注销等待队列: /*kernel/workqueue.c*/ void destroy_workqueue(struct workqueue_struct *wq) 该函数是是创建等待队列的反操作,注销掉指定的等待队列。
2、代码
#include <linux/module.h> #include <linux/init.h> #include <linux/interrupt.h> #include <linux/workqueue.h> #define DEBUG_SWITCH 1 #if DEBUG_SWITCH #define P_DEBUG(fmt, args...) printk("<1>" "<kernel>[%s]"fmt, __FUNCTI ON__, ##args) #else #define P_DEBUG(fmt, args...) printk("<7>" "<kernel>[%s]"fmt, __FUNCTI ON__, ##args) #endif struct workqueue_struct *ZP1015_wq; //1.定义工作队列 struct work_struct ZP1015_work; //2定义work结构体 void ZP1015_func(struct work_struct *work) //2实现处理函数 { printk("hello ZP1015!\n"); } irqreturn_t irq_handler(int irqno, void *dev_id) { printk("key down\n"); queue_work(ZP1015_wq ,&ZP1015_work); //3调度任务 return IRQ_HANDLED; } static int __init test_init(void) //模块初始化函数 { int ret; /*work*/ ZP1015_wq = create_workqueue("ZP1015"); //1初始化工作对列 INIT_WORK(&ZP1015_work, ZP1015_func); //2初始化work结构体 ret = request_irq(IRQ_EINT1, irq_handler, IRQF_TRIGGER_FALLING, "key INT_EINT1", NULL); if(ret){ P_DEBUG("request irq failed!\n"); return ret; } printk("hello irq\n"); return 0; } static void __exit test_exit(void) //模块卸载函数 { flush_workqueue(ZP1015_wq); //4刷新工作队列 destroy_workqueue(ZP1015_wq); //4注销工作队列 free_irq(IRQ_EINT1, NULL); printk("good bye irq\n"); } module_init(test_init); module_exit(test_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("ZP1015"); MODULE_VERSION("v0.1");
三、使用共享的工作队列改进
在内核中有一个默认的工作队列events,使用共享的工作队列可以省去创建和注销工作队列的步骤。当然,队列是共享的,用起来当然会不爽,如果有多个不同的任务都加入到这个工作对列中,每个任务调度的速度就会比较慢,肯定不如自己创建一个爽。不过,一般默认工作队列都能满足要求,不需要创建一个新的。
少了上面创建和注销等待队列两步,使用共享工作队列步骤相对少一点
步骤一、实现处理函数,定义并初始化work结构体:
这一步骤上一节介绍的步骤二完全一样,所以就不说了。
步骤二、调度任务:
默认工作队列的中任务的调度不需要指定工作对列,所以函数有所不同:
默认工作队列的中任务的调度不需要指定工作对列,所以函数有所不同:
/*kernel/workqueue.c*/
int schedule_work(struct work_struct *work)
该函数会把work_struct结构体加入到默认工作对列events中。
5、改进代码
#include <linux/module.h> #include <linux/init.h> #include <linux/interrupt.h> #include <linux/workqueue.h> #define DEBUG_SWITCH 1 #if DEBUG_SWITCH #define P_DEBUG(fmt, args...) printk("<1>" "<kernel>[%s]"fmt, __FUNCTI ON__, ##args) #else #define P_DEBUG(fmt, args...) printk("<7>" "<kernel>[%s]"fmt, __FUNCTI ON__, ##args) #endif struct work_struct ZP1015_work; //定义work结构体 void ZP1015_func(struct work_struct *work) { printk("hello Linux world!\n"); } irqreturn_t irq_handler(int irqno, void *dev_id) //中断处理函数 { printk("key down\n"); schedule_work(&ZP1015_work); //调度任务 return IRQ_HANDLED; } static int __init test_init(void) //模块初始化函数 { int ret; /*work*/ INIT_WORK(&ZP1015_work, ZP1015_func); //初始化work结构体 ret = request_irq(IRQ_EINT1, irq_handler, IRQF_TRIGGER_FALLING, "key INT_EINT1", NULL); if(ret){ P_DEBUG("request irq failed!\n"); return ret; } printk("hello irq\n"); return 0; } static void __exit test_exit(void) //模块卸载函数 { free_irq(IRQ_EINT1, NULL); printk("good bye irq\n"); } module_init(test_init); module_exit(test_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("ZP1015"); MODULE_VERSION("v0.1");
六、工作队列的实现
工作者线程,是指负责执行在内核队列中任务的内核线程。在工作队列中,有专门的工作者线程来处理加入到工作对列中的任务。工作对列对应的工作者线程可能不止一个,每个处理器有且仅有一个工作队列对应的工作者线程。当然,如果内核中两个工作对列,那每个处理器就分别有两个工作者线程。
在内核中有一个默认的工作队列events,对于单处理器的ARM9,有一个对应的工作者线程。
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。