Linux中断处理为什么需要分为上下部分?
linux中断处理不参与调度,所以中断处理事件过长会影响实时性;
中断处理函数(ISR)运行事件应尽可能短,但有些处理不可能再很短时间内处理完成,于是linux内核提供中断处理上下部。
Linux 中断处理上下部含义
中断处理的上半部(top half ,又叫顶半部);中断处理的下半部(bottom half , 又叫底半部);上半部指的是中断处理程序,下半部则是指一些虽然与中断有相关性但是可以延后执行的任务。例如:在网络传输中,网卡接收到数据包这个事件不一定需要马上被处理,这就适合放在下半部分去实现;而用户敲击键盘这样的事件就必须马上被响应,这个中断处理就必须放在中断的上半部去实现。具体的区分规则如下:
1、如果一个任务对时间非常敏感,将其放在中断处理程序中执行
2、如果一个任务和硬件相关,将其放在中断处理程序中执行
3、如果一个任务保证不被其他中断(特别是相同的中断)打断,将其放在中断处理程序中执行
4、其他所有任务,考虑放在下半部分执行
中断处理上下部的三种实现机制
软中断
tasklet 机制
workqueue 机制
软中断
软中断是下半部机制的代表,是随着SMP的出现应运而生的,它也是tasklet实现的基础 。它的出现就是因为要满足上面所提出的上半部和下半部的区别,使得对时间不敏感的任务延后执行,而且可以在多个cpu上并行执行,使得总的系统效率更高,它的特征包括:
1、产生后并不是马上可以执行,必须要等待内核的调度才能执行。软中断不能被自己打断(即单个cpu上软中断不能嵌套执行),只能被硬件中断打断(上半部)。
2、可以并发运行在多个cpu上(即使同一类型的也可以),所以软中断必须设计为可重入的函数(允许多个cpu同时操作),因此也需要使用自旋锁来保证其数据结构。
tasklet机制
tasklet机制是一种比较特殊的软中断,tasklet一词的原意为“小片任务”的意思,这里是指一小段可以执行的代码,且通常是以函数的形式出现,这个tasklet绑定的函数在一个时刻只能在一个cpu上运行,在smp系统上不会出现问题。
tasklet机制编程步骤:
1、编写tasklet结构中的绑定函数
void tasklet_func(unsigned long data){
...
}
2、定义tasklet结构
第一种使用静态定义:(定义出的tasklet能被调度)
DECLARE_TASKLET(mytasklet,tasklet_func,123);
第二种使用静态定义:(定义出的tasklet不能被调度)
DECLARE_TASKLET_DISABLE(mytasklet,tasklet_func,123);
第三种动态定义:一般在模块初始化时候
1)先定义一个变量
struct tasklet_struct mytasklet;
2) 对这个变量进行初始化,如果是静态定义结构体,略过这一步
tasklet_init(&mytasklet,tasklet_func,123);
3、初始化tasklet结构
根据想要实现的功能,需要在什么情况下调用到绑定的函数,就在哪里调用tasklet_schedule函数进行调度
tasklet_schedule(&mytasklet);
4、在适当的地方进行调度
如果中途不想使用tasklet,则可以删除它。
tasklet_kill(&mytasklet)一般情况在模块卸载函数中编写。
注意:tasklet 和 软中断都是运行在中断上下文中,因此tasklet 和 软中断实现得中断下部不能阻塞和睡眠。
工作队列
工作队列(work queue)是一种将工作推后执行的机制,它和之前的tasklet不同的是,工作队列可以把工作推后 ,交由一个内核线程去执行,也就是说这个中断下半部分可以在进程上下文中执行,这样,通过工作队列执行的代码就能占尽进程上下文的所有优点,最重要的就是工作队列允许被重新调度甚至睡眠。
工作队列使用步骤:
1、声明编写一个工作处理函数和一个工作队列
void my_func();
struct workqueue_struct *p_queue = create_singlethread_workqueue("p_queue");
2、创建一个工作结构体变量并初始化
struct work_struct my_work;
INIT_WORK(&my_work,my_func);
3、将工作添加到自己创建的工作队列等待执行
queue_work(p_queue,&my_work); //将my_work 添加到p_queue中等待执行
4、删除自己的工作队列
destroy_workqueue(p_queue);
工作队列和tasklet的选择
如果推后执行的任务需要睡眠,那么就选择工作队列,如果推后执行的任务不需要睡眠,那么就选择tasklet。
如果需要用一个可以重新调度的实体来执行你的下半部处理,也应该使用工作队列。它是唯一能在进程上下文运行的下半部实现的机制,也只有它才可以睡眠。
当需要获得大量的内存,需要获取信号量、需要执行阻塞式的I/O操作时。都需要选择使用工作队列。
总结:尽量少用tasklet,多用workqueue。