07-底半部机制
10.3.3 底半部机制
Linux实现底半部的机制主要有tasklet、工作队列和软中断。
1.tasklet
tasklet的使用较简单,我们只需要定义tasklet及其处理函数并将两者关联,例如:
void my_tasklet_func(unsigned long); /定义一个处理函数/
DECLARE_TASKLET(my_tasklet, my_tasklet_func, data);
/定义一个tasklet结构my_tasklet,与my_tasklet_func(data)函数相关联/
代码DECLARE_TASKLET(my_tasklet,my_tasklet_func,data)实现了定义名称为my_tasklet的tasklet并将其与my_tasklet_func()这个函数绑定,而传入这个函数的参数为data。
在需要调度tasklet的时候引用一个tasklet_schedule()函数就能使系统在适当的时候进行调度运行:
tasklet_schedule(&my_tasklet);
使用tasklet作为底半部处理中断的设备驱动程序模板如代码清单10.2所示(仅包含与中断相关的部分)。
代码清单10.2 tasklet使用模板
1 /定义tasklet和底半部函数并关联/
2 void xxx_do_tasklet(unsigned long);
3 DECLARE_TASKLET(xxx_tasklet, xxx_do_tasklet, 0);
4
5 /中断处理底半部/
6 void xxx_do_tasklet(unsigned long)
7 {
8 ...
9 }
10
11 /中断处理顶半部/
12 irqreturn_t xxx_interrupt(int irq, void *dev_id)
13 {
14 ...
15 tasklet_schedule(&xxx_tasklet);
16 ...
17 }
18
19 /设备驱动模块加载函数/
20 int _ _init xxx_init(void)
21 {
22 ...
23 /申请中断/
24 result = request_irq(xxx_irq, xxx_interrupt,
25 IRQF_DISABLED, "xxx", NULL);
26 ...
27 return IRQ_HANDLED;
28 }
29
30 /设备驱动模块卸载函数/
31 void _ _exit xxx_exit(void)
32 {
33 ...
34 /释放中断/
35 free_irq(xxx_irq, xxx_interrupt);
36 ...
37 }
上述程序在模块加载函数中申请中断(第24~25行),并在模块卸载函数中释放它(第35行)。对应于xxx_irq的中断处理程序被设置为xxx_interrupt()函数,在这个函数中,第15行的tasklet_schedule(&xxx_tasklet)调度被定义的tasklet函数xxx_do_tasklet()在适当的时候得到执行。
2.工作队列
工作队列的使用方法和tasklet非常相似,下面的代码用于定义一个工作队列和一个底半部执行函数:
struct work_struct my_wq; /定义一个工作队列/
void my_wq_func(unsigned long); /定义一个处理函数/
通过INIT_WORK()可以初始化这个工作队列并将工作队列与处理函数绑定:
INIT_WORK(&my_wq, (void ()(void )) my_wq_func, NULL);
/初始化工作队列并将其与处理函数绑定/
与tasklet_schedule()对应的用于调度工作队列执行的函数为schedule_work(),如:
schedule_work(&my_wq);/调度工作队列执行/
与代码清单10.2对应的使用工作队列处理中断底半部的设备驱动程序模板如代码清单10.3所示(仅包含与中断相关的部分)。
代码清单10.3 工作队列使用模板
1 /定义工作队列和关联函数/
2 struct work_struct xxx_wq;
3 void xxx_do_work(unsigned long);
4
5 /中断处理底半部/
6 void xxx_do_work(unsigned long)
7 {
8 ...
9 }
10
11 /中断处理顶半部/
12 irqreturn_t xxx_interrupt(int irq, void dev_id, struct pt_regs regs)
13 {
14 ...
15 schedule_work(&xxx_wq);
16 ...
17 return IRQ_HANDLED;
18 }
19
20 /设备驱动模块加载函数/
21 int xxx_init(void)
22 {
23 ...
24 /申请中断/
25 result = request_irq(xxx_irq, xxx_interrupt,
26 IRQF_DISABLED, "xxx", NULL);
27 ...
28 /初始化工作队列/
29 INIT_WORK(&xxx_wq, (void ()(void )) xxx_do_work, NULL);
30 ...
31 }
32
33 /设备驱动模块卸载函数/
34 void xxx_exit(void)
35 {
36 ...
37 /释放中断/
38 free_irq(xxx_irq, xxx_interrupt);
39 ...
40 }
与代码清单10.2不同的是,上述程序在设计驱动模块加载函数中增加了初始化工作队列的代码(第29行)。
尽管Linux社区多建议在设备第一次打开时才申请设备的中断并在最后一次关闭时释放中断以尽量减少中断被这个设备占用的时间,但是,许多情况下,驱动工程师还是将中断申请和释放的工作放在了设备驱动的模块加载和卸载函数中。
3.软中断
软中断(softirq)也是一种传统的底半部处理机制,它的执行时机通常是顶半部返回的时候,tasklet是基于软中断实现的,因此也运行于软中断上下文。
在Linux内核中,用softirq_action结构体表征一个软中断,这个结构体中包含软中断处理函数指针和传递给该函数的参数。使用open_softirq()函数可以注册软中断对应的处理函数,而raise_softirq()函数可以触发一个软中断。
软中断和tasklet运行于软中断上下文,仍然属于原子上下文的一种,而工作队列则运行于进程上下文。因此,软中断和tasklet处理函数中不能睡眠,而工作队列处理函数中允许睡眠。
local_bh_disable()和local_bh_enable()是内核中用于禁止和使能软中断和tasklet底半部机制的函数。
内核中采用softirq的地方包括HI_SOFTIRQ、TIMER_SOFTIRQ、NET_TXSOFTIRQ、NET RX_SOFTIRQ、SCSI_SOFTIRQ、TASKLET_SOFTIRQ等,一般来说,驱动的编写者不会也不宜直接使用softirq。
第9章异步通知所基于的信号也类似于中断,现在,总结一下硬中断、软中断和信号的区别:硬中断是外部设备对CPU的中断,软中断是中断底半部的一种处理机制,而信号则是由内核(或其他进程)对某个进程的中断。在论及系统调用的场合,人们也常说通过软中断(例如ARM为swi)陷入内核,此时软中断的概念是指由软件指令引发的中断,和我们这个地方说的softirq是两个完全不同的概念。