Linux的中断处理框架大体为:入口点-->driver安装的中断处理函数-->退出点
通常说Linux下的中断处理分为top和bottom两部分,top部分中断是关闭的,因此一般在这里的代码都会以迅雷不及掩耳盗铃之势做完该做的事,然后打开中断以防止可能的中断丢失。因为要迅雷不及掩耳盗铃,所以大部分耗时的任务交给了bottom部分去处理。bottom阶段中断是打开的,所以有可能被其他中断到来时所打断。
bottom的延后处理,其中之一遍是由tasklet来完成,相对于workqueue这种任务延后机制,tasklet实际上是处在中断上下文中,因为处在空灵世界(不属于任一进程),所以在tasklet中休眠是绝对应该禁止的。
其他的一些bottom方法:
/* PLEASE, avoid to allocate new softirqs, if you need not _really_ high
frequency threaded job scheduling. For almost all the purposes
tasklets are more than enough. F.e. all serial device BHs et
al. should be converted to tasklets, not to softirqs.
*/
enum
{
HI_SOFTIRQ=0,
TIMER_SOFTIRQ,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
BLOCK_SOFTIRQ,
TASKLET_SOFTIRQ,
SCHED_SOFTIRQ,
HRTIMER_SOFTIRQ,
RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */
NR_SOFTIRQS
};
看了tasklet的大体实现,很容易想到内核中另一个延迟执行机制:workqueque
如果摆渡或者股沟一下,都会发现它们之间如下的区别:
1. tasklet在中断上下文中执行,所以tasklet中的代码不应该导致调度点的产生(如果调度,因为当前代码不属于任何一进程,或者current此时无效,tasklet在一个边缘世界里).而工作线程是在一个内核进程中,所以工作队列的函数当然可以睡眠。
2.tasklet始终运行在被当初提交它的那个CPU上,但对于工作队列而言,这只是它的缺省行为,当然可以改变它。
3.内核代码可以请求工作队列函数的执行延迟给定的时间间隔。
我们说,深入内核代码的最终目的,一是学习内核代码的设计思想,另一个重要目的是在写我们自己的代码时,因为能清楚了解各种内核设施的背后机制,因而可以能选择最佳的解决方案:你应该选择tasklet还是workqueue。
在海豚看来,在绝大多数的实际使用中,tasklet和workqueue之间的后两点区别并不重要,因为不管在哪个CPU上运行,你的代码总是会被执行。除非你的项目要非常精确地对性能进行调整,此外给请求队列函数的执行指定给定的时间间隔现实中也很少用到。
它们之间最关键的区别应该是第一点,这点决定了tasklet的代码应该是短小精悍,而且对系统的开销也小(无需象workqueue那样生成一个新的进程),并且它不能睡眠。workqueue则正好相反。
海豚曾经遇到过在tasklet中调用一个对外围spi设备寄存器读写函数,类似spi_reg_read/write云云,如果在tasklet中调用这个函数,系统必然crash掉。因为spi_reg_read/write的内部实现使用了中断驱动,阻塞I/O的实现机制,因此睡眠是必不可少的。此种情况,你别无选择,只能使用workqueue!