linux设备驱动归纳总结(六):3.中断的上半部和下半部——工作队列

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

这节介绍另外一种的下半部实现——工作队列。相对于软中断/tasklet,工作对列运行在进程上下文,允许睡眠,接下来慢慢介绍。

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

1、工作队列的使用

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

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

创建工作队列函数:

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)

定义并初始化一个叫n的work_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

2 #include

3

4 #include

5 #include

6

7 #define DEBUG_SWITCH 1

8 #if DEBUG_SWITCH

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

10 #else

11 #define P_DEBUG(fmt, args...) printk("<7>" "[%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

2 #include

3

4 #include

5 #include

6

7 #define DEBUG_SWITCH 1

8 #if DEBUG_SWITCH

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

10 #else

11 #define P_DEBUG(fmt, args...) printk("<7>" "[%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

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

三、工作队列的实现

在介绍工作队列的实现之前,必须要介绍什么是工作者线程和三个数据结构。

工作者线程,是指负责执行在内核队列中任务的内核线程。在工作队列中,有专门的工作者线程来处理加入到工作对列中的任务。工作对列对应的工作者线程可能不止一个,每个处理器有且仅有一个工作队列对应的工作者线程。当然,如果内核中两个工作对列,那每个处理器就分别有两个工作者线程。

在内核中有一个默认的工作队列events,对于单处理器的ARM9,有一个对应的工作者线程。

工作队列结构体workqueue_struct:

59 struct workqueue_struct {

60 struct cpu_workqueue_struct
*cpu_wq; //一个工作者线程对应一个该结构体

61 struct list_head list;

62 const char *name;

63 int singlethread;

64 int freezeable; /* Freeze threads during suspend */

65 int rt;

66 #ifdef CONFIG_LOCKDEP

67 struct lockdep_map lockdep_map;

68 #endif

69 };

工作对列workqueue_struct有一个成员cpu_workqueue_struct,每个工作者线程对应一个cpu_workqueue。所以,对于单处理器的ARM9,一个工作对列只有一个cpu_workqueue_struct。

结构体cpu_workqueue_struct:

41 struct cpu_workqueue_struct {

42

43 spinlock_t lock;

44 /*这是内核链表,所有分配在这个处理器的work_struct将通过链表连在一起,等待执行*/

45 struct list_head
worklist;

46 wait_queue_head_t more_work;

47 struct work_struct
*current_work; //指向当前执行的work_struct

48

49 struct workqueue_struct
*wq; //指向关联自己的工作队列

50 struct task_struct
*thread; //指向对应的内核线程,即工作者线程

51

52 int run_depth; /* Detect run_workqueue() recursion depth */

53 } ____cacheline_aligned;

由上面知道,当我们调用queue_work来调度任务时,并不是把work_struct添加到等待队列中,而是会被分配到工作对列的成员cpu_workqueue_struct中。

work结构体work_struct:

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; //cpu_workqueue_struct通过这个成员,将wrok_struct连在一起

31 work_func_t
func; //每个任务的处理函数

32 #ifdef CONFIG_LOCKDEP

33 struct lockdep_map lockdep_map;

34 #endif

35 };

可能上面讲得很乱,下面将来个图来讲解一下,双处理器的情况下:

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

四、选择哪个来实现下半部

在2.6内核,提供三种实现中断下半部的方法,软中断、tasklet和工作队列,其中tasklet是基于软中断实现的,两者很相近。而工作队列完全不同,它是靠内核线程实现的。

有这样的一张表:

简单来说,软中断和tasklet优先级较高,性能较好,调度快,但不能睡眠。而工作队列是内核的进程调度,相对来说较慢,但能睡眠。所以,如果你的下半部需要睡眠,那只能选择动作队列。否则最好用tasklet。

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

五、总结

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

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)

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

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

源代码: 6th_irq_3(2).rar

【Linux开发】linux设备驱动归纳总结(六):3.中断的上半部和下半部——工作队列的更多相关文章

  1. 【Linux开发】linux设备驱动归纳总结(六):1.中断的实现

    linux设备驱动归纳总结(六):1.中断的实现 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...

  2. 【Linux开发】linux设备驱动归纳总结(六):2.分享中断号

    linux设备驱动归纳总结(六):2.分享中断号 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...

  3. linux设备驱动归纳总结(六):2.分享中断号【转】

    本文转载自:http://blog.chinaunix.net/uid-25014876-id-90837.html xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...

  4. linux设备驱动归纳总结(六):1.中断的实现【转】

    本文转载自:http://blog.chinaunix.net/uid-25014876-id-90740.html linux设备驱动归纳总结(六):1.中断的实现 xxxxxxxxxxxxxxxx ...

  5. linux设备驱动归纳总结(六):1.中断的实现

    linux设备驱动归纳总结(六):1.中断的实现 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...

  6. 【Linux开发】linux设备驱动归纳总结(五):1.在内核空间分配内存

    linux设备驱动归纳总结(五):1.在内核空间分配内存 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...

  7. 【Linux开发】linux设备驱动归纳总结(四):5.多处理器下的竞态和并发

    linux设备驱动归纳总结(四):5.多处理器下的竞态和并发 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...

  8. 【Linux开发】linux设备驱动归纳总结(四):4.单处理器下的竞态和并发

    linux设备驱动归纳总结(四):4.单处理器下的竞态和并发 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...

  9. 【Linux开发】linux设备驱动归纳总结(四):1.进程管理的相关概念

    linux设备驱动归纳总结(四):1.进程管理的相关概念 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...

随机推荐

  1. BZOJ 3531: [Sdoi2014]旅行 (树剖+动态开点线段树)

    对于每种信仰维护一棵动态开点线段树就行了- #include <cstdio> #include <cctype> #include <cstring> #incl ...

  2. 浮动float和清除clear

    一.浮动(float) float简介 取值:left,right,none,inherit,默认none(不浮动) 可应用与所有元素 没有继承性 不在正常流中,但会影响布局.因为一个元素浮动时,其他 ...

  3. Confluence 6 移动一个文件到其他页面

    你需要同时具有 添加页面(Add Page),添加附件(Add Attachment)和删除附件(Remove Attachment)空间权限来移动一个附件文件到其他页面. 希望修改附件附加的页面到其 ...

  4. CCPC-Wannafly & Comet OJ 夏季欢乐赛(2019)F

    题面 F比较友善(相较于E),我们发现如果i和j是满足条件的两个下标,那么: a[i]-2*b[i] + a[j]-2*b[j] >=0 或者 b[i]-2*a[i] + b[j]-2*a[j] ...

  5. Trie树(c++实现)——转载自jihite的博客

    Trie树(c++实现)   原理 先看个例子,存储字符串abc.ab.abm.abcde.pm可以利用以下方式存储 上边就是Trie树的基本原理:利用字串的公共前缀来节省存储空间,最大限度的减少无谓 ...

  6. hadoop yarn日志分离

    根据hdfs的auditlog以及fsimage分析,yarn的日志文件占用了10%-20%的rpc请求以及文件量,这对namenode的性能有比较大的影响,特别是当集群规模越来越大,会影响生产业务. ...

  7. python3中的heapq模块使用

    heapq-堆排序算法 heapq实现了一个适合与Python的列表一起使用的最小堆排序算法. 二叉树 树中每个节点至多有两个子节点 满二叉树 树中除了叶子节点,每个节点都有两个子节点 什么是完全二叉 ...

  8. thinkphp session 跨域问题解决方案

    session 跨域,困扰我好几天,今天终于弄明白了! 不管是thinkphp ,还是本身的php 其实都要设置session.cookie_domain 设置好,就OK了 在thinkphp 里,在 ...

  9. leetcode-hard-array-454 4sum II-NO

    mycode   过不了...我也不知道为什么... class Solution(object): def fourSumCount(self, A, B, C, D): ""& ...

  10. anroid学习笔记(1)

    大概是2个月前,报名了慕课的android就业班课程. 算是补全了当初博客分类的最初设计. 安卓和前端比较: 1,java在安卓开发中的作用,现在我的认识是和JavaScript在前端web开发中有很 ...