Linux内核 irq/soft irq/tasklet/同步


1.中断处理程序结束之前,不允许产生相同的中断事件;(禁用PIC上该中断,但可以产生其他中断)
2.中断处理程序、软中断、tasklet既不可被抢占也不能被阻塞,最多发生中断嵌套;
3.执行中断处理的内核路径不能被执行延迟函数或系统调用服务例程的内核控制路径中断。

于是有如下结论:
a.中断处理程序和tasklet不必是可重入的;
b.仅被软中断和tasklet访问的每CPU变量不需要同步;
c.仅被一种tasklet访问的数据不需要同步。

软中断(即便是同一种类型的软中断)可以并发地运行在多个CPU上。
同类型的tasklet总是被串行化执行,不会在两个CPU上同时运行相同类型的tasklet。
一个tasklet可以被调度多次,但实际只会运行一次。
static inline void tasklet_schedule(struct tasklet_struct *t)
{
if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
__tasklet_schedule(t);
}
另外参见do_softirq > _do_softirq > tasklet_action


一个中断处理程序既可以抢占其他的中断处理程序,也可以抢占异常处理程序;
异常处理程序从不抢占中断处理程序;
中断处理程序从不执行可导致缺页的操作(意味着进程切换);
中断处理程序必须永不阻塞,即不发生进程切换!



如果一个数据结构仅被中断程序“上半部分”访问,访问数据结构就无需任何同步,因为每个中断处理程序都相对于自己串行的执行。
如果多个中断处理程序访问一个数据结构,在单处理器上,必须在中断处理程序的所有临界区上禁止中断来避免竞争;在多处理器上,必须禁止本地中断,并获取保护数据结构的自旋锁或读写锁。
软中断是可重入函数,必须明确使用自旋锁保护结构数据(因为可能在多个CPU上同时执行);
如果某个中断程序“上半部分”也会访问该数据结构,则必须在拥有自旋锁的时候禁止中断,以免死锁!
仅由一种tasklet访问的数据结构不需要保护,因为同种tasklet不能并发运行。


关于tasklet的一点说明:
1.前述的一种或一个tasklet,都是指一个,以tasklet结构体的内存地址区分,不同地址的tasklet即为不同的tasklet,即使两个tasklet结构体的func指针指向同一个中断设备的同一个延迟处理函数,甚至内容完全相等,都算作不同(种类)的tasklet,就需要处理同时执行时的同步操作问题;(包括《Understanding Linux Kernel》书中所提到的一种,都是指的一个!!!)

刚开始我理解为同一个中断程序指定的tasklet为一种或一类,至少func应该指向同一个处理函数吧,后来还理解为同一个软中断对应的tasklet(如TASKLET_SOFTIRQ)是同一类,这一类tasklet可以被串行化执行。事实上看上去也确实像,因为tasklet_action函数中会首先把本地CPU的tasklet_vec链表中的所有tasklet全部摘取下来,然后一个一个地串行执行,这样虽然保证了本地CPU上同一个TASKLET_SOFTIRQ上的所有tasklet串行化,甚至softirq也按从0到31顺序执行,但是仍然可能多个CPU同时执行一个TASKLET_SOFTIRQ上的tasklet,从而不能确保global串行化。

2.同一个tasklet可能被挂进两个CPU(甚至同一个CPU)的本地待执行tasklet_vec链表中,当在 tasklet_action函数中执行该tasklet时,TASKLET_STATE_SCHED被清除,并设置上了TASKLET_STATE_RUN标志,此时该tasklet可以重新被schedule成功,但TASKLET_STATE_RUN标志可以防止该tasklet被同时执行。

3.tasklet结构体中的func以及data实际上没有想象中的那么动态,它们应当在初始化时候就确定,而不应该在硬中断程序(“上半部分”)中schedule tasklet之前重新指定!设想一个tasklet的func刚准备执行,这时一个硬中断重新设置了这个func,并成功schedule这个tasklet,那么返回继续执行时将是一个灾难!

4.如果硬中断需要动态schedule tasklet,那么可以每次分配一个tasklet,设置func指向同一个处理函数,但是每次data可以设置为从设备上读取出来的实时数据;func则需要考虑数据结构同步问题。
Linux不支持这一系列tasklet的串行化运行也是有道理的,这样可以尽可能提高IO设备的吞吐量,毕竟目前IO才是瓶颈。