使能和屏蔽中断:
CPU一般都具有屏蔽中断和打开中断的功能,这项功能可以保证正在执行的内核执行路径不被中断处理程序所抢占,防止某些竞态的发生。
屏蔽中断的使用方法:
local_irq_disable() /* 屏蔽中断 */
. . .
critical section /* 临界区 */
. . .
local_irq_enable() /* 开中断 */
其底层的实现原理就是让CPU本身不响应中断。
local_irq_disable() 和local_irq_enable() 都只能禁止和使能本CPU内的中断, 因此, 并不能解决SMP多CPU引发的竞态。
下列 3 个函数用于屏蔽一个中断源。
void disable_irq(int irq);
void disable_irq_nosync(int irq);
void enable_irq(int irq);
disable_irq_nosync()与 disable_irq()的区别在于前者立即返回,而后者等待目前的中断处理完成。
下列两个函数将屏蔽本 CPU 内的所有中断。
void local_irq_save(unsigned long flags);
void local_irq_disable(void);
前者会将目前的中断状态保留在 flags 中,注意 flags 被直接传递,而不是通过指针传递。后者直接禁止中断。
与上述两个禁止中断对应的恢复中断的方法如下:
void local_irq_restore(unsigned long flags);
void local_irq_enable(void);
以上各 local_开头的方法的作用范围是本 CPU 内。
底半部机制:
Linux中实现底半部的机制主要有:tasklet、工作队列和软中断。
1、tasklet
定义tasklet及其处理函数并将两者关联:
void my_tasklet_func(unsigned long); /*定义一个处理函数*/
DECLARE_TASKLET(my_tasklet, my_tasklet_func, data);
/*定义一个 tasklet 结构 my_tasklet,与 my_tasklet_func(data)函数相关联*/
在需要调度tasklet的时候引用一个tasklet_schedule()函数就能够在适当的时候调度运行,如下:
tasklet_schedule(&my_tasklet);
2、工作队列
定义一个工作队列和底半部执行函数:
struct work_struct my_wq; /*定义一个工作队列*/
void my_wq_func(unsigned long); /*定义一个处理函数*/
通过INIT_WORK()可以初始化工作队列,并实现工作队列和处理函数的绑定
INIT_WORK(&my_wq, (void (*)(void *)) my_wq_func, NULL);
/*初始化工作队列并将其与处理函数绑定*/
在需要调度工作队列的时候引用一个schedule_work()函数就能够在适当的时候调度运行,如下:
schedule_work(&my_wq);/*调度工作队列执行*/
3、软中断
软中断和 tasklet 仍然运行于中断上下文,而工作队列则运行于进程上下文。因此,软中断和 tasklet 处理函数中不能睡眠,而工作队列处理函数中允许睡眠。
local_bh_disable()和 local_bh_enable()是内核中用于禁止和使能软中断和 tasklet 底半部机制的函数。
软中断试一组静态定义的下半部的接口,有32个。
共享中断:
int request_irq(unsigned int irq,
irqreturn_t (*handler)(int, void *, struct pt_regs *),
unsigned long irqflags,const char * devname,
void * dev_id);
参数1:中断线号
参数2:中断处理程序函数指针
参数3:标志掩码(SA_INTERRUPT, SA_SAMPLE_RANDOM, SA_SHIRQ)
参数4:用于参数3为SA_SHIRQ(共享中断线)的时候,其他为NULL。
如果是共享的中断线号,则对应链表的所有中断处理程序都被调用,不过在这个中断处理函数的内部首先检查是不是这个中断处理程序对应的设备产生的中断,在进行下一部处理。