1.linux内核中断编程之顶半部和底半部机制
a)掌握linux系统相关的概念
1.任务:计算机中的任务细分两类:进程,硬件中断和软中断
进程:进程一开始在用户空间运行,一旦调用系统调用立刻陷入内核空间继续运行
运行完毕又回到用户空间继续运行
硬件中断:外设给CPU发送的中断信号,内核执行其硬件中断处理函数,此函数永远运行在内核空间
软中断:软件单独调用swi或svc指令立刻触发软中断异常,立刻执行其软中断处理函数,此函数
同样位于内核空间
注意:任何一种任务要想运行必须先获取到CPU资源!
2.任务优先级:衡量任务获取CPU资源的能力
优先级越高,获取CPU资源的能力越强,越能够及早运行
在linux内核中,三类任务的优先级划分:
硬件中断高于软中断
软中断高于进程
进程之间存在优先级之分(nice命令可以设置优先级)
软中断之间存在优先级
硬件中断无优先级之分(哪怕中断控制器支持优先级,内核不认)
3.进程调度:linux内核会给每个进程分配时间片,进程一旦轮到其运行,在此时间片之内可以一直运行
时间片到期,进程调度器(软件,有很牛叉的算法)会将当前进程的CPU资源撤下给其他进程使用
切记:中断不参与进程调度,中断来了,直接会抢夺CPU资源,因为优先级高
4.休眠sleep:休眠只能用于进程,不可用于中断(中断的世界里没有休眠一词)
进程休眠:当前获取CPU资源的进程一旦要进入休眠状态,此进程会立马释放占用的CPU资源
给其他进程
b)结论:由以上概念得到:在linux内核中,内核希望中断处理函数的执行速度要越快越好
如果中断处理函数长时间占用CPU资源,势必影响系统的并发能力(其他任务无法获取CPU资源投入运行)和
响应能力(其他的硬件中断再也无法得到响应,各种卡)
问:不可能所有的中断处理函数都能够满足内核的希望
例如:网卡利用中断获取网络数据包,其中断处理函数本身执行的速度就很慢
如果长时间占用CPU资源,就会造成丢包现象!
答:对于此种情况,必须要采用内核提供的顶半部和底半部机制来优化
c)顶半部和底半部实现机制
具体参见:bh.png(面试时边说边画)
1.顶半部特点
硬件中断触发,立刻执行
顶半部本质就是中断处理函数,只是现在里面做紧急,耗时较短的内容
CPU在执行顶半部中断处理函数期间不允许发生CPU资源的切换
顶半部同样不能休眠
2.底半部特点
底半部对应的处理函数做不紧急,并且耗时较长的内容
CPU在"适当"的时候执行底半部的处理函数
在执行期间允许高优先级的任务来抢夺CPU资源,等处理完毕再回到底半部继续运行
所以底半部的处理函数实现可以基于软中断或者进程实现
底半部实现方法三种:tasklet,工作队列,软中断
底半部的本质就是延后执行的一种手段,并不是非要和顶半部配合使用,也就是可以单独用底半部来
将你想要延后执行的事情进行延后,可以将宝贵的CPU资源给其他高优先级的任务使用
3.底半部实现方法之:tasklet
a)特点:
基于软中断实现,它对应的处理函数的优先级高于进程而低于硬件中断
tasklet对应的处理函数中不能进行休眠操作
tasklet对应的处理函数又称延后处理函数,里面做不紧急,耗时较长的内容
b)描述tasklet属性的结构体
struct tasklet_struct {
void (*func)(unsigned long data);
unsigned long data;
...
};
功能:描述tasklet的属性
func:指定tasklet的延后处理函数,将来内核会在适当时候调用其延后处理函数,不能进行休眠操作
形参data保存给这个延后处理函数传递的参数
data:给延后处理函数传递的参数
配套函数:
void tasklet_init(struct tasklet_struct *tasklet,void (*func)(unsigned long),unsigned long data)
功能:初始化tasklet对象,给tasklet对象指定一个延后处理函数并且给延后处理函数传递参数
tasklet:tasklet对象的地址
func:给tasklet指定一个延后处理函数
data:给延后处理函数传递的参数
void tasklet_schedule(struct tasklet_struct *tasklet)
功能:向内核注册tasklet对象和其延后处理函数,一旦注册成功,内核会在适当的时候执行其延后处理函数
案例:利用tasklet优化按键驱动
明确:之前的按键的中断处理函数本身执行速度很快了,理论上没必要再次优化
如果非要发掘按键的中断处理函数浪费CPU的地方,这个代码就是中断处理函数
打印按键信息的代码:printk(.....);此代码最浪费CPU资源,最消耗时间,因为此函数
操作的硬件设备是UART串口,UART串口本身数据处理的速度极其慢!
干脆用tasklet优化
参考代码:day06/1.0
1 #include<linux/init.h>
2 #include<linux/module.h>
3 #include<linux/irq.h>
4 #include<linux/interrupt.h>
5 #include<linux/gpio.h>
6 #include<mach/platform.h>
7
8 //声明按键的硬件信息
9 struct btn_resource{
10 char *name;
11 int gpio;
12 };
13
14 //定义按键的硬件信息对象
15 static struct btn_resource btn_info[] = {
16 {
17 .name = "KEY_UP",
18 .gpio = PAD_GPIO_A + 28
19 },
20 {
21 .name = "KEY_DOWN",
22 .gpio = PAD_GPIO_B + 9
23 }
24 };
25 //定义tasklet对象
26 static struct tasklet_struct btn_tasklet;
27
28 //两个函数用到,所以定义全局变量
29 static struct btn_resource *pdata;//暂存当前触发中断的按键硬件信息的首地址
30
31 //定义tasklet延后处理函数
32 //基于软中断实现
33 //内核在适当的时候执行此函数,同样不能进行休眠操作
34 //data = &g_data
35 static void btn_tasklet_function(unsigned long data){
36 //1.暂存当前触发中断的按键硬件信息
37 //KET_UP产生中断,pdata=&btn_info[0]
38 //KEY_DOWN产生中断,pdata=&btn_info[1]
39 //struct btn_resource *pdata = (struct btn_resource *)dev;
40
41 //2.分配内核缓冲区暂存按键的状态
42 int kstate;
43
44 //3.获取按键的状态保存到内核缓冲区
45 kstate = gpio_get_value(pdata->gpio);
46
47 //4.打印按键的状态
48 printk("底半部%s:按键[%s]的状态是:%s\n", __func__, pdata->name, kstate?"松开":"按下");
49 }
50
51 //定义按键的中断处理函数,又称顶半部
52 //只要来了按键中断,内核立刻执行,CPU执行期间不允许切换,更不能进行休眠操作
53 static irqreturn_t button_isr(int irq, void *dev){
54 //1.获取当前触发中断的按键硬件信息
55 pdata = (struct btn_resource *)dev;
56 //2.向内核登记tasklet延后处理函数,一旦登记完成,内核在适当的时候执行延后处理函数
57 tasklet_schedule(&btn_tasklet);
58 printk("顶半部:%s\n", __func__);
59 return IRQ_HANDLED;//中观处理函数执行成功返回,执行失败返回IRQ_NONE
60 }
61
62 //传参验证
63 static int g_data = 0x55;
64
65 static int btn_init(void){
66 int i;
67 //1.申请GPIO资源
68 //2.申请中断资源,注册中断处理函数
69 for(i = 0; i < ARRAY_SIZE(btn_info); i++){
70 int irq = gpio_to_irq(btn_info[i].gpio);//通过gpio编号换算对应的中断号
71 gpio_request(btn_info[i].gpio, btn_info[i].name);
72 request_irq(irq,//申请的硬件中断的中断号
73 button_isr,//注册的按键中断处理函数,目前是四个按键共用一个中断处理函数
74 IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,//指定按键中断触发的类型
75 btn_info[i].name,//指定按键中断的名称
76 &btn_info[i]//把每个按键的硬件信息的首地址传递给中断处理函数
77 );
78 }
79 //3.初始化tasklet对象,指定延后处理函数并且指定给延后处理函数传递的参数
80 tasklet_init(&btn_tasklet, btn_tasklet_function, (unsigned long)&g_data);
81 return 0;
82 }
83
84 static void btn_exit(void){
85 int i;
86 //1.释放GPIO资源
87 //2.释放中断资源和删除中断处理函数
88 for(i = 0; i < ARRAY_SIZE(btn_info); i++){
89 int irq = gpio_to_irq(btn_info[i].gpio);//通过gpio编号换算对应的中断号
90 gpio_free(btn_info[i].gpio);
91 free_irq(irq, &btn_info[i]);
92 }
93 }
94 module_init(btn_init);
95 module_exit(btn_exit);
96 MODULE_LICENSE("GPL");
实验步骤同上
4.底半部实现机制之工作队列
工作队列特点:
1.工作队列基于进程实现,所以其延后处理函数可以进行休眠操作
2.工作队列诞生的本质就是解决tasklet的延后处理函数不能休眠的问题
所以如果在延后执行的内容中有休眠操作,必须采用工作队列
3.工作队列的延后处理函数的优先级也是最低的
描述工作队列属性的数据结构:
struct work_struct {
void (*func)(struct work_struct *work);
};
func:工作队列的延后处理函数,基于进程实现,可以进行休眠操作
形参work:指向驱动定义初始化的work_struct对象,work指向自己
用于给延后处理函数传递参数
问:如何利用自己的地址给延后处理函数传递参数呢?
答:必须配合大名鼎鼎的container_of宏来实现传递参数!
作业:先自行研究大名鼎鼎的container_of宏
配套函数:
INIT_WORK(struct work_struct *work, void (*func)(struct work_struct *work));
功能:给work对象指定一个延后处理函数
参数:
work:work对象的地址
func:指定延后处理函数
schedule_work(struct work_struct *work);
功能:向内核登记注册一个延后处理函数,一旦注册成功,内核在适当的时候调用其延后处理函数
案例:利用工作队列优化按键驱动
明确:之前的按键的中断处理函数本身执行速度很快了,理论上没必要再次优化
如果非要发掘按键的中断处理函数浪费CPU的地方,这个代码就是中断处理函数
打印按键信息的代码:printk(.....);此代码最浪费CPU资源,最消耗时间,因为此函数
操作的硬件设备是UART串口,UART串口本身数据处理的速度极其慢!
这里采用工作队列优化
参考代码:day06/2.0
实验步骤同上
5.底半部实现机制之软中断(了解即可)
软中断特点:
1.软中断同样有对应的延后处理函数,此函数可以同时运行在多个CPU核上,效率极其高
这就要求其延后处理函数如果进行全局变量的访问,记得要做好互斥保护,当一个CPU核
在访问全局变量是,禁止其他CPU核访问,但是这种互斥保护势必降低代码的效率
2.tasklet基于软中断实现,但是它的延后处理函数同一时刻使能运行在一个CPU核上
也不用关心互斥访问的问题!
3.软中断的延后处理函数不能insmod和rmmod动态的安装和卸载,必须和uImage编写在一起
编译在一起,无形加大了开发的难度和加大了代码的维护难度
tasklet就可以动态的insmod和rmmod
6.终极总结:
1.tasklet基于软中断实现,工作队列基于进程实现
2.如果要进行延后执行,并且延后执行的内容中有休眠操作,只能用工作队列
3.如果要进行延后执行,延后执行的内容中无休眠操作,但又要考虑效率问题,建议使用tasklet
4.如果要进行延后执行,延后执行的内容中无休眠操作,但又不考虑效率问题,建议使用工作队列
5.如果要进行延后执行,延后执行的内容中无休眠操作,并且对效率要求极高,想多核同时运行处理,建议采用软中断
如果有对全局变量共享资源的同时访问还要进行互斥访问,所以建议还是tasklet吧!