linux中断的上半部和下半部

linux中断的上半部和下半部 

转载:http://blog.chinaunix.net/uid-20528014-id-3068412.html

一、最简单的中断机制

最简单的中断机制就是像芯片手册上讲的那样,在中断向量表中填入跳转到对应处理函数的指令,然后在处理函数中实现需要的功能。类似下图:

这种方式在原来的单片机课程中常常用到,一些简单的单片机系统也是这样用。

它的好处很明显,简单,直接。

 

二、下半部

中断处理函数所作的第一件事情是什么?答案是屏蔽中断(或者是什么都不做,因为常常是如果不清除IF位,就等于屏蔽中断了),当然只屏蔽同一种中断。之所以要屏蔽中断,是因为新的中断会再次调用中断处理函数,导致原来中断处理现场的破坏。即,破坏了 interrupt context。

随着系统的不断复杂,中断处理函数要做的事情也越来越多,多到都来不及接收新的中断了。于是发生了中断丢失,这显然不行,于是产生了新的机制:分离中断接收与中断处理过程。中断接收在屏蔽中断的情况下完成;中断处理在时能中断的情况下完成,这部分被称为中断下半部。

从上图中看,只看int0的处理。Func0为中断接收函数。中断只能简单的触发func0,而func0则能做更多的事情,它与funcA之间可以使用队列等缓存机制。当又有中断发生时,func0被触发,然后发送一个中断请求到缓存队列,然后让funcA去处理。

由于func0做的事情是很简单的,所以不会影响int0的再次接收。而且在func0返回时就会使能int0,因此funcA执行时间再长也不会影响int0的接收。

 

三、软中断

下面看看linux中断处理。作为一个操作系统显然不能任由每个中断都各自为政,统一管理是必须的。

我们不可中断部分的共同部分放在函数do_IRQ中,需要添加中断处理函数时,通过request_irq实现。下半部放在do_softirq中,也就是软中断,通过open_softirq添加对应的处理函数。

 

四、tasklet

旧事物跟不上历史的发展时,总会有新事物出现。

随着中断数的不停增加,软中断不够用了,于是下半部又做了进化。

软中断用轮询的方式处理。假如正好是最后一种中断,则必须循环完所有的中断类型,才能最终执行对应的处理函数。显然当年开发人员为了保证轮询的效率,于是限制中断个数为32个。

为了提高中断处理数量,顺道改进处理效率,于是产生了tasklet机制。

Tasklet采用无差别的队列机制,有中断时才执行,免去了循环查表之苦。

总结下tasklet的优点:

(1)无类型数量限制;

(2)效率高,无需循环查表;

(3)支持SMP机制;

 

五、工作队列

前面的机制不论如何折腾,有一点是不会变的。它们都在中断上下文中。什么意思?说明它们不可挂起。而且由于是串行执行,因此只要有一个处理时间较长,则会导致其他中断响应的延迟。为了完成这些不可能完成的任务,于是出现了工作队列。工作队列说白了就是一组内核线程,作为中断守护线程来使用。多个中断可以放在一个线程中,也可以每个中断分配一个线程。

工作队列对线程作了封装,使用起来更方便。

因为工作队列是线程,所以我们可以使用所有可以在线程中使用的方法。

Tasklet其实也不一定是在中断上下文中执行,它也有可能在线程中执行。

假如中断数量很多,而且这些中断都是自启动型的(中断处理函数会导致新的中断产生),则有可能cpu一直在这里执行中断处理函数,会导致用户进程永远得不到调度时间。

为了避免这种情况,linux发现中断数量过多时,会把多余的中断处理放到一个单独的线程中去做,就是ksoftirqd线程。这样又保证了中断不多时的响应速度,又保证了中断过多时不会把用户进程饿死。

问题是我们不能保证我们的tasklet或软中断处理函数一定会在线程中执行,所以还是不能使用进程才能用的一些方法,如放弃调度、长延时等。

 

六、使用方式总结

Request_irq挂的中断函数要尽量简单,只做必须在屏蔽中断情况下要做的事情。

中断的其他部分都在下半部中完成。

软中断的使用原则很简单,永远不用。它甚至都不算是一种正是的中断处理机制,而只是tasklet的实现基础。

工作队列也要少用,如果不是必须要用到线程才能用的某些机制,就不要使用工作队列。其实对于中断来说,只是对中断进行简单的处理,大部分工作是在驱动程序中完成的。所以有什么必要非使用工作队列呢?

除了上述情况,就要使用tasklet。

即使是下半部,也只是作必须在中断中要做的事情,如保存数据等,其他都交给驱动程序去做。

----------------------------------------------------------------------------------

原理:转自:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=24690947&id=3491821

linux中断机制

通常,处理器一旦接收到中断,就会打断正在执行的代码,调用中断处理函数。如果在此中断处理函数中没有禁止中断,该中断处理函数执行过程中仍有可能被其他中断打断。出于这样的原因,大家都希望中断处理函数执行得越快越好另外,中断上下文中不能阻塞,这也限制了中断上下文中能干的事。基于上面的原因,内核将整个的中断处理流程分为了上半部和下半部。

上半部就是之前所说的中断处理函数,它能最快的响应中断,并且做一些必须在中断响应之后马上要做的事情。而一些需要在中断处理函数后继续执行的操作,内核建议把它放在下半部执行。


拿网卡来举例,在linux内核中,当网卡一旦接受到数据,网卡会通过中断告诉内核处理数据,内核会在网卡中断处理函数(上半部)执行一些网卡硬件的必要设置,因为这是在中断响应后急切要干的事情。接着,内核调用对应的下半部函数来处理网卡接收到的数据,因为数据处理没必要在中断处理函数里面马上执行,可以将中断让出来做更紧迫的事情。


可以有三种方法来实现下半部:软中断、tasklet和等待队列

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx


二、软中断


软中断一般很少用于实现下半部,但tasklet是通过软中断实现的,所以先介绍软中断。字面理解,软中断就是软件实现的异步中断,它的优先级比硬中断低,但比普通进程优先级高,同时,它和硬中断一样不能休眠


软中断是在编译时候静态分配的,要用软中断必须修改内核代码。


kernel/softirq.c中有这样的一个数组:

51 static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;

内核通过一个softirq_action数组来维护的软中断NR_SOFTIRQS是当前软中断的个数,待会再看他在哪里定义。


先看一下softirq_action结构体:

/*include/linux/interrupt.h*/

265 struct softirq_action

266 {

267 void (*action)(struct softirq_action *); //软中断处理函数

268 };

一看发现,结构体里面就一个软中断函数,他的参数就是本身结构体的指针。之所以这样设计,是为了以后的拓展,如果在结构体中添加了新成员,也不需要修改函数接口。在以前的内核,该结构体里面还有一个data的成员,用于传参,不过现在没有了。


接下来看一下如何使用软中断实现下半部

一、要使用软中断,首先就要静态声明软中断:

/*include/linux/interrupt.h*/

246 enum

247 {

248 HI_SOFTIRQ=0, //用于tasklet的软中断,优先级最高,为0

249 TIMER_SOFTIRQ, //定时器的下半部

250 NET_TX_SOFTIRQ, //发送网络数据的软中断

251 NET_RX_SOFTIRQ, //接受网络数据的软中断

252 BLOCK_SOFTIRQ,

253 TASKLET_SOFTIRQ, //也是用于实现tasklet

254 SCHED_SOFTIRQ,

255 HRTIMER_SOFTIRQ,

256 RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */

257 //add by xiaobai 2011.1.18

258 XIAOBAI_SOFTIRQ, //这是我添加的,优先级最低

259

260 NR_SOFTIRQS, //这个就是上面所说的软中断结构体数组成员个数

261 };

上面通过枚举定义了NR_SOFTIRQS(10)个软中断的索引号,优先级最高是0HI_SOFTIRQ),最低是我刚添加上去的XIAOBAI_SOFTIRQ,优先级为9


二、定义了索引号后,还要注册处理程序。

通过函数open_sofuirq来注册软中断处理函数,使软中断索引号与中断处理函数对应。该函数在kernel/softirq.c中定义:

/*kernel/softirq.c */

321 void open_softirq(int nr, void (*action)(struct softirq_action *))

322 {

323 softirq_vec[nr].action = action;

324 }

其实该函数就是把软中断处理函数的函数指针存放到对应的结构体中,一般的,我们自己写的模块是不能调用这个函数的,为了使用这个函数,我修改了内核:

322 void open_softirq(int nr, void (*action)(struct softirq_action *))

323 {

324 softirq_vec[nr].action = action;

325 }

326 EXPORT_SYMBOL(open_softirq); //这是我添加的,导出符号,这样我编写的程序就能调用

在我的程序中如下调用:

/*6th_irq_3/1st/test.c*/

13 void xiaobai_action(struct softirq_action *t) //软中断处理函数

14 {

15 printk("hello xiaobai!\n");

16 }

。。。。。。。。

48 open_softirq(XIAOBAI_SOFTIRQ, xiaobai_action);


三、在中断处理函数返回前,触发对应的软中断。

在中断处理函数完成了必要的操作后,就应该调用函数raise_sotfirq触发软中断,让软中断执行中断下半部的操作。

/*kernel/softirq.c*/

312 void raise_softirq(unsigned int nr)

313 {

314 unsigned long flags;

315

316 local_irq_save(flags);

317 raise_softirq_irqoff(nr);

318 local_irq_restore(flags);

319 }

所谓的触发软中断,并不是指马上执行该软中断,不然和在中断上执行没什么区别。它的作用只是告诉内核:下次执行软中断的时候,记得执行我这个软中断处理函数。

当然,这个函数也得导出符号后才能调用:

/*kernel/softirq.c*/

312 void raise_softirq(unsigned int nr)

313 {

314 unsigned long flags;

315

316 local_irq_save(flags);

317 raise_softirq_irqoff(nr);

318 local_irq_restore(flags);

319 }

320 EXPORT_SYMBOL(raise_softirq);

在我的程序中如下调用:

/*6th_irq_3/1st/test.c*/

18 irqreturn_t irq_handler(int irqno, void *dev_id) //中断处理函数

19 {

20 printk("key down\n");

21 raise_softirq(XIAOBAI_SOFTIRQ);

22 return IRQ_HANDLED;

23 }


经过三步,使用软中断实现下半部就成功了,看一下完整的函数:

/*6th_irq_3/1st/test.c*/

1 #include <linux/module.h>

2 #include <linux/init.h>

3

4 #include <linux/interrupt.h>

5

6 #define DEBUG_SWITCH 1

7 #if DEBUG_SWITCH

8 #define P_DEBUG(fmt, args...) printk("<1>" "<kernel>[%s]"fmt, __FUNCTI ON__, ##args)

9 #else

10 #define P_DEBUG(fmt, args...) printk("<7>" "<kernel>[%s]"fmt, __FUNCTI ON__, ##args)

11 #endif

12

13 void xiaobai_action(struct softirq_action *t) //软中断处理函数

14 {

15 printk("hello xiaobai!\n");

16 }

17

18 irqreturn_t irq_handler(int irqno, void *dev_id) //中断处理函数

19 {

20 printk("key down\n");

21 raise_softirq(XIAOBAI_SOFTIRQ); //触发软中断

22 return IRQ_HANDLED;

23 }

24

25 static int __init test_init(void) //模块初始化函数

26 {

27 int ret;

28

29 /*注册中断处理函数:

30 * IRQ_EINT1:中断号,定义在"include/mach/irqs.h"

31 * irq_handler:中断处理函数

32 * IRQ_TIRGGER_FALLING:中断类型标记,下降沿触发中断

33 * ker_INT_EINT1:中断的名字,显示在/proc/interrupts等文件中

34 * NULL;现在我不使用dev_id,所以这里不传参数

35 */

36 ret = request_irq(IRQ_EINT1, irq_handler,

37 IRQF_TRIGGER_FALLING, "key INT_EINT1", NULL);

38 if(ret){

39 P_DEBUG("request irq failed!\n");

40 return ret;

41 }

42

43 /*fostirq*/

44 open_softirq(XIAOBAI_SOFTIRQ, xiaobai_action); //注册软中断处理程序

45

46 printk("hello irq\n");

47 return 0;

48 }

49

50 static void __exit test_exit(void) //模块卸载函数

51 {

52 free_irq(IRQ_EINT1, NULL);

53 printk("good bye irq\n");

54 }

55

56 module_init(test_init);

57 module_exit(test_exit);

58

59 MODULE_LICENSE("GPL");

60 MODULE_AUTHOR("xoao bai");

61 MODULE_VERSION("v0.1");

注意。在上面的程序,只是为了说明如何实现上下半步,而我的中断上下半步里面的操作是毫无意义的(只是打印)。上下半步的作用我在一开始就有介绍。

接下来验证一下:

[root: 1st]# insmod test.ko

hello irq

[root: 1st]# key down //上半部操作

hello xiaobai! //下半部操作

key down

hello xiaobai!

key down

hello xiaobai!

[root: 1st]# rmmod test

good bye irq


上面介绍,触发软中断函数raise_softirq并不会让软中断处理函数马上执行,它只是打了个标记,等到适合的时候再被实行。如在中断处理函数返回后,内核就会检查软中断是否被触发并执行触发的软中断。

软中断会在do_softirq中被执行,其中核心部分在do_softirq中调用的__do_softirq中:

/*kernel/softirq.c*/

172 asmlinkage void __do_softirq(void)

173 {

。。。。。。

194 do {

195 if (pending & 1) { //如果被触发,调用软中断处理函数

196 int prev_count = preempt_count();

197

198 h->action(h); //调用软中断处理函数

199

200 if (unlikely(prev_count != preempt_count())) {

201 printk(KERN_ERR "huh, entered softirq %td %p"

202 "with preempt_count %08x,"

203 " exited with %08x?\n", h - softirq_vec,

204 h->action, prev_count, preempt_count());

205 preempt_count() = prev_count;

206 }

207

208 rcu_bh_qsctr_inc(cpu);

209 }

210 h++; //下移,获取另一个软中断

211 pending >>= 1;

212 } while (pending); //大循环内执行,知道所有被触发的软中断都执行完

。。。。。。


xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx


三、tasklet


上面的介绍看到,软中断实现下半部的方法很麻烦,一般是不会使用的。一般,我们使用tasklet——利用软中断实现的下半部机制


在介绍软中断索引号的时候,有两个用于实现tasklet的软中断索引号:HI_SOFTIRQTASKLET_SOFTIRQ。两个tasklet唯一的区别就是优先级的大小,一般使用TAKSLET_SOFTIRQ


先看一下如何使用tasklet,用完之后再看内核中是如何实现的:

步骤一、编写tasklet处理函数,定义并初始化结构体tasklet_struct

内核中是通过tasklet_struct来维护一个tasklet,介绍一下tasklet_struct里面的两个成员:

/*linux/interrupt.h*/

319 struct tasklet_struct

320 {

321 struct tasklet_struct *next;

322 unsigned long state;

323 atomic_t count;

324 void (*func)(unsigned long); //tasklet处理函数

325 unsigned long data; //给处理函数的传参

326 };


所以,在初始化tasklet_struct之前,需要先写好tasklet处理函数,如果需要传参,也需要指定传参,你可以直接传数据,也可以传地址。我定义的处理函数如下:

/*6th_irq_3/2nd/test.c*/

15 void xiaobai_func(unsigned long data)

16 {

17 printk("hello xiaobai!, data[%d]\n", (int)data); //也没干什么事情,仅仅打印。

18 }


同样,可以通过两种办法定义和初始化tasklet_struct

1、静态定义并初始化

/*linux/interrupt.h*/

328 #define DECLARE_TASKLET(name, func, data) \

329 struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }

330

331 #define DECLARE_TASKLET_DISABLED(name, func, data) \

332 struct tasklet_struct name = { NULL, 0, ATOMIC_INIT

上面两个函数都是定义一个叫nametasklet_struct,并指定他的处理函数和传参分别是funcdata。唯一的区别是,DCLARE_TASKLET_DISABLED初始化后的处于禁止状态,暂时不能被使用。

2、动态定义并初始化

跟以往的一样,需要先定义结构体,然后把结构体指针传给tasklet_init来动态初始化:

/*kernel/softirq.c*/

435 void tasklet_init(struct tasklet_struct *t,

436 void (*func)(unsigned long), unsigned long data)


在我的程序中,使用动态定义并初始化:

/*6th_irq_3/2nd/test.c*/

13 struct tasklet_struct xiaobai_tasklet; //定义tasklet结构体

32 tasklet_init(&xiaobai_tasklet, xiaobai_func, (unsigned long)123);

我这里的传参直接传一个数值123。这操作也相当于:

DECLEAR_TASKLET(xiaobai_tasklet, xiaobai_func, (unsigned long)123);


步骤二、在中断返回前调度tasklet


跟软中断一样(其实tasklet就是基于软中断实现),这里说的调度并不是马上执行,只是打个标记,至于什么时候执行就要看内核的调度。

调度使用函数tasklet_schedule或者tasklet_hi_schedule,两个的区别是一个使用TASKLET_SOFTIRQ,另一个使用HI_SOFTIRQ。这两个函数都是一tasklet_struct指针为参数:

/*linux/interrupt.h*/

365 static inline void tasklet_schedule(struct tasklet_struct *t)

373 static inline void tasklet_hi_schedule(struct tasklet_struct *t)


在我的函数中,使用tasklet_schedule

/*6th_irq_3/2nd/test.c*/

23 tasklet_schedule(&xiaobai_tasklet);


步骤三、当模块卸载时,将tasklet_struct结构体移除:

/*kernel/softirq.c*/

447 void tasklet_kill(struct tasklet_struct *t)

确保了 tasklet 不会被再次调度来运行,通常当一个设备正被关闭或者模块卸载时被调用。如果 tasklet 正在运行程序会休眠,等待直到它执行完毕


另外,还有禁止与激活tasklet的函数。被禁止的tasklet不能被调用,直到被激活:

/*linux/interrupt.h*/

386 static inline void tasklet_disable(struct tasklet_struct *t) //禁止

393 static inline void tasklet_enable(struct tasklet_struct *t) //激活


最后附上程序:

/*6th_irq_3/2nd/test.c*/

1 #include <linux/module.h>

2 #include <linux/init.h>

3

4 #include <linux/interrupt.h>

5

。。。。省略。。。。

13 struct tasklet_struct xiaobai_tasklet; //定义tasklet结构体

14

15 void xiaobai_func(unsigned long data)

16 {

17 printk("hello xiaobai!, data[%d]\n", (int)data);

18 }

19

20 irqreturn_t irq_handler(int irqno, void *dev_id) //中断处理函数

21 {

22 printk("key down\n");

23 tasklet_schedule(&xiaobai_tasklet);

24 return IRQ_HANDLED;

25 }

26

27 static int __init test_init(void) //模块初始化函数

28 {

29 int ret;

30

31 /*tasklet*/

32 tasklet_init(&xiaobai_tasklet, xiaobai_func, (unsigned long)123);

33

41 ret = request_irq(IRQ_EINT1, irq_handler,

42 IRQF_TRIGGER_FALLING, "key INT_EINT1", NULL);

43 if(ret){

44 P_DEBUG("request irq failed!\n");

45 return ret;

46 }

47

48 printk("hello irq\n");

49 return 0;

50 }

51

52 static void __exit test_exit(void) //模块卸载函数

53 {

54 tasklet_kill(&xiaobai_tasklet);

55 free_irq(IRQ_EINT1, NULL);

56 printk("good bye irq\n");

57 }

58

59 module_init(test_init);

60 module_exit(test_exit);


最后验证一下,还是老样子,上下半步只是打印一句话,没有实质操作:

[root: 2nd]# insmod test.ko

hello irq

[root: 2nd]# key down //上半部操作

hello xiaobai!, data[123] //下半部操作

key down

hello xiaobai!, data[123]

[root: 2nd]# rmmod test

good bye irq


既然知道怎么使用tasklet,接下来就要看看它是怎么基于软中断实现的


上面说明的是单处理器的情况下,如果是多处理器,每个处理器都会有一个tasklet_vectasklet_hi_vec链表,这个情况我就不介绍了。


xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx


四、总结


这节介绍了如何通过软中断(tasklet也是软中断的一种实现形式)机制来实现中断下半部。使用软中断实现的优缺点很明显:

优点:运行在软中断上下文,优先级比普通进程高,调度速度快。

缺点:由于处于中断上下文,所以不能睡眠。


也许有人会问,那软中断和tasklet有什么区别?

个人理解,tasklet是基于软中断实现的,基本上和软中断相同。但有一点不一样,如果在多处理器的情况下,内核不能保证软中断在哪个处理器上运行(听起来像废话),所以,软中断之间需要考虑共享资源的保护。而tasklet,内核可以保证,两个同类型(TASKLET_SOFTIRQHI_SOFTIRQ)的tasklet不能同时执行,那就说明,同类型tasklet之间,可以不考虑同类型tasklet之间的并发情况。


一般的,优先考虑使用tasklet

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx


工作队列的使用


按惯例,在介绍工作队列如何实现之前,先说说如何使用工作队列实现下半部。


步骤一、定义并初始化工作队列:


创建工作队列函数:

struct workqueue_struct *create_workqueue(const char *name)

函数传参是内核中工作队列的名称,返回值是workqueue_struct结构体的指针,该结构体用来维护一个等待队列。

我的代码如下:

/*6th_irq_3/4th/test.c*/

14 struct workqueue_struct *xiaobai_wq; //定义工作队列

33 xiaobai_wq = create_workqueue("xiaobai");


步骤二、定义并初始化work结构体:


内核使用结构体来维护一个加入工作队列的任务:

/*linux/workqueue.h*/

25 struct work_struct {

26 atomic_long_t data;

27 #define WORK_STRUCT_PENDING 0 /* T if work item pending execution */

28 #define WORK_STRUCT_FLAG_MASK (3UL)

29 #define WORK_STRUCT_WQ_DATA_MASK (~WORK_STRUCT_FLAG_MASK)

30 struct list_head entry;

31 work_func_t func; //这个是重点,下半部实现的处理函数指针就放在这

32 #ifdef CONFIG_LOCKDEP

33 struct lockdep_map lockdep_map;

34 #endif

35 };


同样有静态和动态两种方法:

静态定义并初始化work结构体:

/*linux/workqueue.h*/

72 #define DECLARE_WORK(n, f) \

73 struct work_struct n = __WORK_INITIALIZER(n, f)

定义并初始化一个叫nwork_struct数据结构,它对应的的处理函数是f

对应的动态初始化方法,该函数返回work_struct指针,所以需要先定义一个work_struct结构:

/*linux/workqueue.h*/

107 #define INIT_WORK(_work, _func) \

108 do { \

109 (_work)->data = (atomic_long_t) WORK_DATA_INIT(); \

110 INIT_LIST_HEAD(&(_work)->entry); \

111 PREPARE_WORK((_work), (_func)); \

112 } while (0)

113 #endif


tasklet一样,在初始化的同时,需要将处理函数实现,代码如下:

/*6th_irq_3/4th/test.c*/

15 struct work_struct xiaobai_work; //定义work结构体

16

17 void xiaobai_func(struct work_struct *work) //处理函数

18 {

19 printk("hello xiaobai!\n"); //同样什么都没干,只是打印

20 }

34 INIT_WORK(&xiaobai_work, xiaobai_func); //初始化work结构体


步骤三、在中断处理函数中调度任务:


工作队列和worl结构体都已经实现了,接下来就可以调度了,使用一下函数:

/*kernel/workqueue.c*/

161 int queue_work(struct workqueue_struct *wq, struct work_struct *work)

将指定的任务(work_struct),添加到指定的工作队列中。同样的,调度并不代表处理函数能够马上执行,这由内核进程调度决定。


步骤四、在卸载模块时,刷新并注销等待队列:


刷新等待队列函数:

/*kernel/workqueue.c*/

411 void flush_workqueue(struct workqueue_struct *wq)

该函数会一直等待,知道指定的等待队列中所有的任务都执行完毕并从等待队列中移除。

注销等待队列:

/*kernel/workqueue.c*/

904 void destroy_workqueue(struct workqueue_struct *wq)

该函数是是创建等待队列的反操作,注销掉指定的等待队列。


四个步骤讲完,贴个代码:

/*6th_irq_3/4th/test.c*/

1 #include <linux/module.h>

2 #include <linux/init.h>

3

4 #include <linux/interrupt.h>

5 #include <linux/workqueue.h>

6

7 #define DEBUG_SWITCH 1

8 #if DEBUG_SWITCH

9 #define P_DEBUG(fmt, args...) printk("<1>" "<kernel>[%s]"fmt, __FUNCTI ON__, ##args)

10 #else

11 #define P_DEBUG(fmt, args...) printk("<7>" "<kernel>[%s]"fmt, __FUNCTI ON__, ##args)

12 #endif

13

14 struct workqueue_struct *xiaobai_wq; //1.定义工作队列

15 struct work_struct xiaobai_work; //2定义work结构体

16

17 void xiaobai_func(struct work_struct *work) //2实现处理函数

18 {

19 printk("hello xiaobai!\n");

20 }

21

22 irqreturn_t irq_handler(int irqno, void *dev_id)

23 {

24 printk("key down\n");

25 queue_work(xiaobai_wq ,&xiaobai_work); //3调度任务

26 return IRQ_HANDLED;

27 }

28 static int __init test_init(void) //模块初始化函数

29 {

30 int ret;

31

32 /*work*/

33 xiaobai_wq = create_workqueue("xiaobai"); //1初始化工作对列

34 INIT_WORK(&xiaobai_work, xiaobai_func); //2初始化work结构体

35

36 ret = request_irq(IRQ_EINT1, irq_handler,

37 IRQF_TRIGGER_FALLING, "key INT_EINT1", NULL);

38 if(ret){

39 P_DEBUG("request irq failed!\n");

40 return ret;

41 }

42

43 printk("hello irq\n");

44 return 0;

45 }

46

47 static void __exit test_exit(void) //模块卸载函数

48 {

49 flush_workqueue(xiaobai_wq); //4刷新工作队列

50 destroy_workqueue(xiaobai_wq); //4注销工作队列

51 free_irq(IRQ_EINT1, NULL);

52 printk("good bye irq\n");

53 }

54

55 module_init(test_init);

56 module_exit(test_exit);

57

58 MODULE_LICENSE("GPL");

59 MODULE_AUTHOR("xoao bai");

60 MODULE_VERSION("v0.1");

和以往的一样,下半部仅仅是打印,没实质操作,看效果:

[root: 4th]# insmod test.ko

hello irq

[root: 4th]# key down

hello xiaobai!

key down

hello xiaobai!

[root: 4th]# rmmod test

good bye irq


xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx


二、使用共享的工作队列


在内核中有一个默认的工作队列events,使用共享的工作队列可以省去创建和注销工作队列的步骤。当然,队列是共享的,用起来当然会不爽,如果有多个不同的任务都加入到这个工作对列中,每个任务调度的速度就会比较慢,肯定不如自己创建一个爽。不过,一般默认工作队列都能满足要求,不需要创建一个新的。


少了上面创建和注销等待队列两步,使用共享工作队列步骤相对少一点


步骤一、实现处理函数,定义并初始化work结构体:


这一步骤上一节介绍的步骤二完全一样,所以就不说了。


步骤二、调度任务:


默认工作队列的中任务的调度不需要指定工作对列,所以函数有所不同:

/*kernel/workqueue.c*/

620 int schedule_work(struct work_struct *work)

该函数会把work_struct结构体加入到默认工作对列events中。


上函数:

/*6th_irq_3/3rd/test.c*/

1 #include <linux/module.h>

2 #include <linux/init.h>

3

4 #include <linux/interrupt.h>

5 #include <linux/workqueue.h>

6

7 #define DEBUG_SWITCH 1

8 #if DEBUG_SWITCH

9 #define P_DEBUG(fmt, args...) printk("<1>" "<kernel>[%s]"fmt, __FUNCTI ON__, ##args)

10 #else

11 #define P_DEBUG(fmt, args...) printk("<7>" "<kernel>[%s]"fmt, __FUNCTI ON__, ##args)

12 #endif

13

14 struct work_struct xiaobai_work; //定义work结构体

15

16 void xiaobai_func(struct work_struct *work)

17 {

18 printk("hello xiaobai!\n");

19 }

20

21 irqreturn_t irq_handler(int irqno, void *dev_id) //中断处理函数

22 {

23 printk("key down\n");

24 schedule_work(&xiaobai_work); //调度任务

25 return IRQ_HANDLED;

26 }

27 static int __init test_init(void) //模块初始化函数

28 {

29 int ret;

30

31 /*work*/

32 INIT_WORK(&xiaobai_work, xiaobai_func); //初始化worl结构体

33

34 ret = request_irq(IRQ_EINT1, irq_handler,

35 IRQF_TRIGGER_FALLING, "key INT_EINT1", NULL);

36 if(ret){

37 P_DEBUG("request irq failed!\n");

38 return ret;

39 }

40

41 printk("hello irq\n");

42 return 0;

43 }

44

45 static void __exit test_exit(void) //模块卸载函数

46 {

47 free_irq(IRQ_EINT1, NULL);

48 printk("good bye irq\n");

49 }

50

51 module_init(test_init);

52 module_exit(test_exit);

53

54 MODULE_LICENSE("GPL");

55 MODULE_AUTHOR("xoao bai");

56 MODULE_VERSION("v0.1");

再看一下实现效果,效果和之前的一样:

[root: 3rd]# insmod test.ko

hello irq

[root: 3rd]# key down

hello xiaobai!

key down

hello xiaobai!

[root: 3rd]# rmmod test

good bye irq


总结


这节简单介绍了工作队列的使用和实现。其中,还有函数能够使指定工作队列的任务延时执行,相关的结构体和函数有:

struct delayed_work

DECLARE_DELAYED_WORK(n,f)

INIT_DELAYED_WORK(struct delayed_work *work, void (*function)(void *));

int queue_delayed_work(struct workqueue_struct *queue, struct delayed_work *work, unsigned long delay);

schedule_delayed_work(struct delayed_work *work, unsigned long delay)

有兴趣自己看看,很简单。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值