手把手教你写Linux设备驱动---中断(三)--workqueue实现(基于友善之臂4412开发板)
2024-11-08 来源:cnblogs
上节,我们讲到如何来实现tasklet小任务机制
http://blog.csdn.NET/morixinguan/article/details/69666935
这节,我们来实现一下中断下半部的工作队列:
在写这个demo之前,我们要了解一下工作队列的相关数据结构还有API。
需要包含的头文件:
#include 基本的数据结构: //工作队列结构 struct work_struct { atomic_long_t data; //链表处理 struct list_head entry; //工作处理函数 work_func_t func; #ifdef CONFIG_LOCKDEP struct lockdep_map lockdep_map; #endif }; 当然,如果需要等待一定时间后再执行工作队列,可以用下面这个结构体申请一个内核定时器: //指定时间让工作队列执行 struct delayed_work { //初始化 struct work_struct work; //内核定时器 struct timer_list timer; }; 一般,不要轻易的去使用工作队列,因为每当创建一条工作队列,内核就会为这条工作队列创建一条内核线程。 工作队列位于进程上下文,与软中断,tasklet有所区别,工作队列里允许延时,睡眠操作,而软中断,tasklet位于中断上下文,不允许睡眠,延时操作。 参考我转发的这位博主写的工作队列和tasklet的区别: http://blog.csdn.net/morixinguan/article/details/69666642 工作队列(work queue)是另外一种将工作推后执行的形式,它和前面讨论的tasklet有所不同。工作队列可以把工作推后,交由一个内核线程去执行,也就是说,这个下半部分可以在进程上下文中执行。这样,通过工作队列执行的代码能占尽进程上下文的所有优势。最重要的就是工作队列允许被重新调度甚至是睡眠。 那么,什么情况下使用工作队列,什么情况下使用tasklet。如果推后执行的任务需要睡眠,那么就选择工作队列;如果推后执行的任务不需要睡眠,那么就选择tasklet。另外,如果需要用一个可以重新调度的实体来执行你的下半部处理,也应该使用工作队列。它是唯一能在进程上下文运行的下半部实现的机制,也只有它才可以睡眠。这意味着在需要获得大量的内存时、在需要获取信号量时,在需要执行阻塞式的I/O操作时,它都会非常有用。如果不需要用一个内核线程来推后执行工作,那么就考虑使用tasklet。 接下来我们看看需要使用到哪些API: 创建一个队列就会有一个内核线程,一般不要轻易创建队列 位于进程上下文--->可以睡眠 定义: struct work_struct work; 初始化: INIT_WORK(struct work_struct *work, void (*func)(struct work_struct *work)); 定义并初始化: DECLARE_WORK(name, void (*func)(struct work_struct *work)); =========================================================== 调度: int schedule_work(struct work_struct *work); 返回1成功, 0已经添加在队列上 延迟调度: int schedule_delayed_work(struct work_struct *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 work_struct *work, unsigned long delay); 销毁队列: void destroy_workqueue(struct workqueue_struct *wq); 接下来,我们来实现这个demo: #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct tasklet_struct task_t ; struct workqueue_struct *mywork ; //定义一个工作队列结构体 struct work_struct work; static void task_fuc(unsigned long data) { if(in_interrupt()){ printk('%s in interrupt handle!n',__FUNCTION__); } } //工作队列处理函数 static void mywork_fuc(struct work_struct *work) { if(in_interrupt()){ printk('%s in interrupt handle!n',__FUNCTION__); } msleep(2); printk('%s in process handle!n',__FUNCTION__); } static irqreturn_t irq_fuction(int irq, void *dev_id) { tasklet_schedule(&task_t); //调度工作 schedule_work(&work); if(in_interrupt()){ printk('%s in interrupt handle!n',__FUNCTION__); } printk('key_irq:%dn',irq); return IRQ_HANDLED ; } static int __init tiny4412_Key_irq_test_init(void) { int err = 0 ; int irq_num1 ; int data_t = 100 ; //创建新队列和新工作者线程 mywork = create_workqueue('my work'); //初始化 INIT_WORK(&work,mywork_fuc); //调度指定队列 queue_work(mywork,&work); tasklet_init(&task_t,task_fuc,data_t); printk('irq_key initn'); irq_num1 = gpio_to_irq(EXYNOS4_GPX3(2)); err = request_irq(irq_num1,irq_fuction,IRQF_TRIGGER_FALLING,'tiny4412_key1',(void *)'key1'); if(err != 0){ free_irq(irq_num1,(void *)'key1'); return -1 ; } return 0 ; } static void __exit tiny4412_Key_irq_test_exit(void) { int irq_num1 ; printk('irq_key exitn'); irq_num1 = gpio_to_irq(EXYNOS4_GPX3(2)); //销毁一条工作队列 destroy_workqueue(mywork); free_irq(irq_num1,(void *)'key1'); } module_init(tiny4412_Key_irq_test_init); module_exit(tiny4412_Key_irq_test_exit); MODULE_LICENSE('GPL'); MODULE_AUTHOR('YYX'); MODULE_DESCRIPTION('Exynos4 KEY Driver'); 将程序编译完,将zImage下到板子上: 我们可以看到,当我们按下按键的时候,进入外部中断服务函数,此时task_fuc先被调用,然后调用到mywork_fuc,并打印了mywork_fuc里面的信息,从这里我们用程序验证了,工作队列是位于进程上下文,而不是中断上下文,和tasklet是有所区别的。