【Linux开发】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.中断的上半部和下半部——工作队列的更多相关文章
- 【Linux开发】linux设备驱动归纳总结(六):1.中断的实现
linux设备驱动归纳总结(六):1.中断的实现 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...
- 【Linux开发】linux设备驱动归纳总结(六):2.分享中断号
linux设备驱动归纳总结(六):2.分享中断号 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...
- linux设备驱动归纳总结(六):2.分享中断号【转】
本文转载自:http://blog.chinaunix.net/uid-25014876-id-90837.html xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...
- linux设备驱动归纳总结(六):1.中断的实现【转】
本文转载自:http://blog.chinaunix.net/uid-25014876-id-90740.html linux设备驱动归纳总结(六):1.中断的实现 xxxxxxxxxxxxxxxx ...
- linux设备驱动归纳总结(六):1.中断的实现
linux设备驱动归纳总结(六):1.中断的实现 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...
- 【Linux开发】linux设备驱动归纳总结(五):1.在内核空间分配内存
linux设备驱动归纳总结(五):1.在内核空间分配内存 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...
- 【Linux开发】linux设备驱动归纳总结(四):5.多处理器下的竞态和并发
linux设备驱动归纳总结(四):5.多处理器下的竞态和并发 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...
- 【Linux开发】linux设备驱动归纳总结(四):4.单处理器下的竞态和并发
linux设备驱动归纳总结(四):4.单处理器下的竞态和并发 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...
- 【Linux开发】linux设备驱动归纳总结(四):1.进程管理的相关概念
linux设备驱动归纳总结(四):1.进程管理的相关概念 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...
随机推荐
- join on 和group
左边的表是article文章表,右边的是comment文章回复表. 今天mysql查询的时候,遇到了有趣的事,任务是查询数据库需要得到以下格式的文章标题列表,并按 ...
- 前端Web浏览器基于Flash如何实时播放监控视频画面(四)之使用videoJs‘拉流’
本片文章只是起到抛砖引玉的作用,能从头到尾走通就行,并不做深入研究.为了让文章通俗易懂,尽量使用白话描述. 0x001: 下载videoJs 对于Video.js 5.x及更低版本,Flash技术(v ...
- cogs服务点设置(不凶,超乖) x
cogs3. 服务点设置 ★ 输入文件:djsa.in 输出文件:djsa.out 简单对比时间限制:1 s 内存限制:128 MB 问题描述为了进一步普及九年义务教育,政府要在某乡镇 ...
- 技巧:在 C/C++中如何构造通用的对象链表[转]
原文:技巧:在 C/C++中如何构造通用的对象链表 虚拟链表和类链表可以很好地实现这一点 您是否做过这样一个项目,它要求您在内存中保存数目不定的若干不同对象?对于某些情况,二叉树是最佳选择,但在通常情 ...
- Linux 打印可变参数日志
实现了传输进去的字符串所在的文档,函数和行数显示功能. 实现了将传入的可变参数打印到日志功能. #include<stdio.h> #include<stdarg.h> #in ...
- AcWing:173. 矩阵距离(bfs)
给定一个N行M列的01矩阵A,A[i][j] 与 A[k][l] 之间的曼哈顿距离定义为: dist(A[i][j],A[k][l])=|i−k|+|j−l|dist(A[i][j],A[k][l]) ...
- MySQL_(Java)使用JDBC向数据库发起查询请求
MySQL_(Java)使用JDBC向数据库发起查询请求 传送门 MySQL_(Java)使用JDBC创建用户名和密码校验查询方法 传送门 MySQL_(Java)使用preparestatement ...
- JDK7 JDK8 的安装 且不同版本之间的切换
myeclipse 论坛下载 https://www.myeclipsecn.com/download/ 用户名:xcj26 邮箱:xcj26@126.com 密码: 26**_X** 版本: Jav ...
- orale数据库的SQL查询
创建学生表,成绩表,教师表,课程表,分别添加数据信息 create table student( sno ) primary key, sname ), sage ), ssex ) ); cre ...
- nginx环境准备
一.环境调试确认 1.四项确认 确认公网已连通. 确认yum源可用. 确认iptables已经关闭. 确认selinux已经关闭. a.确认是否连通公网. ping www.baidu.com b.确 ...