今天在公司fixed了一个BSOD D1的bug。
这个bug的引出,是开发者没有注意到,在内核下的中断级别造成---在一个DPC中调用了可分页的CODE。
现在总结一下内核下中断请求。
1.中断请求分为外部中断,也就是硬件产生的中断;另一种是由软件指令int n产生的中断。
在C语言代码中,有时候为了Debug的需求,加上软件中断int 3.
如:
switch( ulDeviceNum)
{
case 1:
HandleFunctionA( );
break;
case 2:
HandleFunctionB( );
....
default
ASSERT(0);
_asm{
int 3;
}
}
这里介绍硬件中断。
X86架构的计算机,现在一般采用APIC。在APIC中,IRQ为24个,每个IRQ都有自己的优先级别。
Windows对IRQ进行扩展,提出中断请求级的概念。一共32个中断优先级。其中0-2为软件中断。其余为硬件中断。
2.中断优先级
熟悉一个常见的优先级。
PASSIVE_LEVEL(0级): 用户模式代码一般运行在此优先级。从WDK中可知,调用PASSIVE_LEVEL的驱动程序例程包括— DriverEntry, AddDevice, Reinitialize, Unload routines, most dispatch routines, driver-created threads, worker-thread callbacks。
APC_LEVEL(1级): 从WDK文档中可知:
在系统分页路径和独立线程上下文下,最底层的设备驱动程序和位于这些驱动程序之上的中间层驱动程序能够被IRQL = APC_LEVEL 调用。这些例程必须常驻内存。驱动程序例程既不可分页,也不可为驱动程序可分页镜像的一部分,它们不可以访问任何可分页内存。
PASSIVE_LEVEL(2级): Driver Routines Called at DISPATCH_LEVEL — StartIo, AdapterControl, AdapterListControl, ControllerControl, IoTimer, Cancel (while holding the cancel spin lock), DpcForIsr, CustomTimerDpc, CustomDpc routines.
系统负责线程调度的组件运行在DISPATCH_LEVEL。驱动程序的StartIo,AdapterControl,AdapterListControl,ControllerControl,IoTimer,Cancel,DpcForIsr,CustomTimerDpc,CustomDpc例程等运行在DISPATCH_LEVEL.
3.中断与分页
使用分页内存可能导致分页故障。页故障允许出现在PASSIVE_LEVEL。但是如果在DISPATCH_LEVEL或更高,就有可能引起系统崩溃。所以在StartIo例程,DPC例程,中断服务例程中,不可以使用分页内存。
4.中断升降的注意事项
有时候需要提升IRQL。
下面的实例:
VOID KeRaiseIRQL()
{
KIRQL oldIRQL;
oldIRQL = KeGetCurrentIrql();
ASSERT(oldIRQL <= DISPATCH_LEVEL);
KeRaiseIrql(DISPATCH_LEVEL, &oldIRQL);
......
KeLowerIrql(oldIRQL);
}
从WDK中知道,调用KeRaiseIrql and KeLowerIrql 需要注意的事项:
1)调用KeRaiseIrql(),传入的NewIrql若低于当前的IRQL,则会引起致命的错误;调用KeLowerIrql()除了是restore原来的IRQL也会引起错误。
2)当运行在IRQL >= DISPATCH_LEVEL,为获取内核定义的分发对象,调用KeWaitForSingleObject 或KeWaitForMultipleObjects 去等待一个非零的间隔会引起错误。
3)能安全等待事件,信号量,互斥量或定时器而被设置为有信号状态的驱动程序例程为运行在IRQL为PASSIVE_LEVEL的非独断式线程上下文的例程。
4)运行在PASSIVE_LEVEL,可分页代码中,不可调用KeSetEvent, KeReleaseSemaphore, 或者 KeReleaseMutex (传入参数为TRUE)。
5)运行在APC_LEVEL以上的例程不可以从可分页池中分配内存或访问可分页池。
6)调用 KeAcquireSpinLockAtDpcLevel and KeReleaseSpinLockFromDpcLevel时,必须在DISPATCH_LEVEL