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)

定义并初始化一个叫nwork_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,有一个对应的工作者线程。


技术分享技术分享
技术分享

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。