在非中断线程化的 OS 中,如果把响应中断的所有工作都在 ISR 中完成,系统

是无法忍受的,我们要做的是在 ISR 中尽量的减少代码,只做一些必要性的工作,

如 in / out 操作,把一些其他不必要在 ISR 中工作放到其他地方,比如数据的

处理,这也就是软中断目的所在。其实即使在中断线程化的 OS 当中(如:SOLARIS)

也一样有软中断,但这里的软中断含义和那种推迟调度不是一个概念了。以下仅针

对此帖题目举例说明硬件中断如何触发软中断,关于如何注册软中断和注册步骤就不

说了。这里所说的软中断是与题目相关的。


在 NT KERNEL 中把软件运行环境又分为三个软件中断 IRQL 等级,DISPATCH_LEVEL

(DPC_LEVEL),APC_LEVEL,PASSVIE_LEVEL。系统提供的 DPC 是为了处理硬件中断

后续工作提供的一种机制,DPC 与 Linux Kernel 提供的基于 tasklet 实现的 Bottom

Half 作用上有些相似,但又不一样。在使用 DPC 处理硬件中断后续的工作前,需显示

调用相关的 kernel api 注册 DPC。关于硬件中断如何触发 DPC 调用,可以看

HalEndSystemInterrupt() 这个函数,在这个函数中,首先确定没有其他嵌套硬件

中断后(没有高于 DPC_LEVEL 的 IRQL),把 IRQL 降低,并判断是否有 DPC 调用

(之前使用 IoRequestDpc() 向系统注册的 DPC 回调函数,当把 DPC 回调函数插入

DPC LIST 后,系统调用 HalRequestSoftwareInterrupt() 设置当前 IRQL 为

DPC_LEVEL,如果当前有硬件级的 IRQL 高于 DPC_LEVEL 则 pending DPC),

如果有则设置 IRQL 为 DPC_LEVEL 并调用 KiDispatchInterrupt() 处理之前注册

的 DPC 回调函数。KiDispatchInterrupt() 函数本身是在 IRQL == DPC_LEVEL 上

运行的,它同样是处理线程调度的主函数。也就是说 DPC 调用点不光是在所有硬件

ISR 处理完成后调用 DPC。还有是处理任务调度时也会判断是否需要执行 DPC。这

里注意,DPC 与 DPC_LEVEL 不是一回事,DPC 回调函数运行级别在 DPC_LEVEL,

但并不是说所有 DPC_LEVEL 级别都是要运行 DPC 回调函数的。在非 SMP 环境下

spinlock 的实现就是简单的提升当前 IRQL 为 DPC_LEVEL,因为在这个级别的

IRQL 下所有其他软件级别运行的代码都将被阻塞,所以说线程调度主函数运行在这个

级别是不会被任何线程抢占的,另 DPC_LEVEL 虽然不是中断上下文,但在这个级别

下,任何引起睡眠的机制都将导致系统崩溃。扯的有点远了。再简单说下 APC(

Asynchronous Procedure Call),故名思意,既然是“异步过程调用”那么当前执

行环境很可能是任意线程上下文。其实到了 APC 这里就跟硬件中断扯不上边了,

虽然是这样系统还是把 APC 实现也定位为一种软中断,APC 运行在 APC_LEVEL 级

别(同样系统调用 HalRequestSoftwareInterrupt() 函数设置当前IRQL 为

APC_LEVEL)。关于如何触发 APC 可以去看一些读/写函数,当一个读/写函数设置了

异步标志时,有几种情况(因为是异步的),首先说一下触发条件,条件是:“系

统处理的当前线程是调用者线程且 IRQL 小于 APC_LEVEL 时从当前线程 APC 队列里

取出 APC 回调函数并把 IRQL 提升到 APC_LEVEL 进行调用”。继续看这几种情况,

如果系统当前处理线程不是调用者者线程,则清除 APC_LEVEL 标志,并将此 APC(

回调函数)插入调用者线程的 APC 列然后返回。如果系统当前处理线程正是调用者

线程,那么判断当前 IRQL 是否高于 APC_LEVEL,如果高于则插入调用者线程 APC

队列,此次 APC 中断被记录并返回。如果系统当前处理线程是调用者线程且 IRQL

小于 APC_LEVEL 则满足调用条件,APC 回调函数立刻被调用。处理 APC 的调用点

是在 syscall 返回时检查。


在 Linux Kernel 里引入 softirq 机制后,使用 tasklet 来实现 Bottom Half。

tasklet 也是 softirq 的一种应用,在使用 Bootom Half(tasklet)处理硬件

中断后续工作前,需显示调用相关的 kernel api 注册 Bootom Half(tasklet)。

关于硬件中断如何触发 softirq,可以看 do_IRQ() 这个函数,这个函数在处理

完设备驱动程序调用 request_irq() 注册的 ISR 后,会接着调用 irq_exit(),在

irq_exit() 中会检测是否注册了 softirq 回调函数,且在确定所有硬件中断都返

回后调用 do_softirq(),在这个函数当中会遍历软中断向量表,并调用相应的回

调函数,被调用的函数就是 tasklet 注册的回调函数,也就是你注册的 Bottom

Half。上面只是针对硬件中断而言,其实处理软中断的调用点,不光是在硬件中断

后触发,还有一些地方也调用了 do_softirq()。如在任务调度中 schedule() 函

数里也会判断是否需要执行软中断。还有在 syscall 调用时也有判断是否需要执

行软中断的地方。软中断的运行环境同样是中断上下文,在中断上下文中任何引起

睡眠的机制都将导致系统崩溃。


在 SOLARIS Kernel 中,有很大一部分硬件中断被线程化,如磁盘和网络中断,包

括时钟中断。实现中断上下文可阻塞。被线程化的中断,与普通线程共用一个

dispatcher。中断线程使用区别于 TS/IA/RT 调度等级的优先级,即全局优先级最

高的等级。我个人认为在这种响应硬件中断时可阻塞的中断线程化的处理机制中无

需提供像 DPC,Bottom Half 这种机制,这也是跟以上两种非中断线程化 OS 主要

区别。以上两种 OS 的中断处理机制,是为了不让在 ISR 中处理过多耗时操作,

而提供了一个系统回调函数接口,把那些相对耗时的处理操作放在回调函数中延迟

调用。而现在中断线程化并可阻塞了,我完全可以创建一个中断线程,所有处理工

作由系统负责,这样就无需再提供这种回调函数的接口了。既然不需要 DPC /

tasklet 的Bootom Half 的这种推迟机制那么在 SOLAIRS 内核中软中断的实现自

然也就不一样,它本身也是作为一种中断线程形式存在。关于软中断(softint)

的调用可以查看 interrupt.s 的代码。在中断处理的入口中首先判断了是否为软

中断调用 T_SOFTINT。如果是,则跳转到 dosoftint() 去执行,首先会判断如果

有比自身更高级的 pending 软中断,则直接返回。如果没有则判断当前处理线程

优先级比自身高的话马上返回。如果可以处理当前软中断则先 lock BUS 防止其他

CPU 重入这个软中断,设置当前 IPL 为软中断级。然后比较当前要中断的线程是

否是一个中断线程,如果不是则再继续判断是否要设置时间片。如果需要则进行设

置。如果当前是中断线程,则没有以上的步骤。然后就是中断当前线程,并使用被

中断线程的 LWP。接下来保存被中断的线程,切换到新软中断线程运行,继续使用

av_dispatch_softvect() 函数来执行被注册到 struct autovec 结构里的软中断

回调函数。等到调用完成后,退出软中断线程返回。以上步骤是在没有更高级的硬

件中断线程在运行。如果当前有更高级别的硬件中断正在执行的话,那么会在处理

完硬件中断后无条件的跳转到软中断处理中执行。注意,这里没有判断。从以上流

程可以看出软中断是有优先级的,即可以进行抢占。软中断的调用是无条件调用的,

即我可以直接显示的调用。这样我们可以看出系统在处理软中断时,从处理机制来

讲是与硬中断平级的,当然有优先级之分。这也意味着与 DPC 和 Bootom Half 这

种被动调用机制有这明显的不同。之所以设计成可以直接显示调用,应该是为了某

种性能调优而考虑的。在 SOLAIRS 的解释中,软中断是为一些伪设备所提供的。