驱动开发(11)中断请求级

本博文由CSDN博主zuishikonghuan所作,版权归zuishikonghuan所有,转载请注明出处:http://blog.csdn.net/zuishikonghuan/article/details/51062287

“中断”是一个非常重要的概念,在 80x86 的设备上,Windows 引入了“中断请求级”(IRQL)这一个概念,(在 x86 平台)其中0-2为软件中断,3-31为硬件中断。其中,软件中断从低到高(0,1,2)分别为: IRQL PASSIVE_LEVEL、IRQL APC_LEVEL 、IRQL DISPATCH_LEVEL。

中断请求级高的中断可以打断当前处于较低中断请求级的线程的工作。请注意,中断请求级和线程优先级是两个不同的概念。线程优先级是让操作系统进行线程调度的,换句话说,是用来操作系统决定如何分配给各个线程分配的时间片段的长度等。而中断请求级是用来让操作系统决定判断是否需要在某个中断出现时打断当前的工作的。

举个例子,系统调度两个线程,系统会优先调度线程优先级高的线程,线程是用户模式的线程,CPU 在执行这个线程时,处于最低的中断请求级,这时候一个中断出现了,它的中断请求级为 0x0C(假设,下同),CPU 会去执行这个中断,假设这个时候出现了一个中断请求级为 0x09 的中断,那么 CPU 不会去理睬这个中断,等到0x0C的中断处理完毕后,CPU 才会执行 0x09 的中断,执行完毕后恢复到原来的线程,如果这时候还有同样优先级的线程,操作系统通过调度使这两个线程“并行执行”,当这个线程执行完毕后,系统去调度更低优先级的线程。

驱动程序可以提升/降低中断请求级,通过调用 KeRaiseIrql/KeLowerIrql 内核函数。驱动程序还可以通过 KeGetCurrentIrql 函数得到当前中断请求级。

另外,中断请求级和开发驱动程序是有很大关系的,具体体现在这几个方面:

1。中断服务例程(ISR)。在最早的计算机上,若需要访问硬件,是需要“轮询”的,即 CPU 给硬件发送一个指令后,就定时询问硬件是否完成请求,显而易见这样效率很差,后来有了“中断设备”,即 CPU 不需要对硬件进行轮询,而是对硬件发送指令后去做其他事情,当硬件完成操作后,会给 CPU 发送一个“中断”,属于“外部中断”,最早的 PC 可以接收16个中断信号,每个信号有他约定的用途,比如0号中断给系统时钟使用,3、4中断是串口设备,14号中断是主硬盘的 ATA 控制(主要 IDE 硬盘)等。然而,“高级可编程中断控制器”(APIC)将中断数量增加到24个,Windows 又对中断的概念进行了扩展,定义了“中断请求级”这一个概念,其中0-2为软件中断,3-31为硬件中断,优先级逐渐递增。当为硬件开发驱动程序时,我们有必要处理硬件的中断信号,当然和裸机编程不同,在 Windows 上我们只需要开发驱动程序并编写“中断服务例程”,即可处理中断。需要注意的是,中断服务例程运行在硬件中断级别!意味着高于任何软件中断,所以在中断服务例程中不要处理太耗时的操作,当然我们有解决方法,但这都是以后博客中需要说的了。

2。驱动程序的某些代码是工作在比较高的中断请求级上的,并非和应用程序一样都工作在最低的中断请求级上。这意味着在开发驱动程序时,我们需要考虑中断请求级给编写代码带来的影响。比如:DriverEntry工作在 IRQL PASSIVE_LEVEL ,一些派遣函数工作在 IRQL APC_LEVEL, StartIo 例程工作在 IRQL DISPATCH_LEVEL 。

3。中断请求级等于或高于 IRQL DISPATCH_LEVEL 的代码中不能使用分页内存,只能使用非分页内存。如果不低于 IRQL DISPATCH_LEVEL 中断请求级而使用分页内存,就会引发页故障而导致蓝屏死机。

MSDN:

Any routine that is running at greater than IRQL APC_LEVEL can neither allocate memory from paged pool nor access memory in paged pool safely. If a routine running at IRQL greater than APC_LEVEL causes a page fault, it is a fatal error.

这是为什么呢?我们访问的内存都是虚拟内存(线性地址空间),虚拟内存通过处理器的分页机制实现。虚拟内存分为非页内存和分页内存,非页内存不能交换到磁盘上,而分页内存可以暂时转储到磁盘上,一旦一个虚拟内存页面被转储到文件,那么此页面就被打上一个“脏的”标志,一旦程序访问这样的内存页面,就会触发一个“缺页中断”,从而引发异常处理程序,异常处理程序会将页面从磁盘移到物理内存中,并映射到程序试图访问的虚拟内存地址上。

因此有了两个说法:

1。缺页中断工作在 IRQL DISPATCH_LEVEL 中断请求级上,分页内存有可能会被转储到磁盘上的页面文件上,如果 IRQL DISPATCH_LEVEL 的代码访问这些虚拟内存,需要缺页中断来重新将数据放回到物理内存才行,而缺页中断显然是软件中断,工作在 IRQL DISPATCH_LEVEL ,此时缺页中断无法打断当前线程的执行!因此我们的程序访问脏页时就会被系统映射到错误的物理内存,可能会破坏系统,引发系统崩溃,因此高于 IRQL DISPATCH_LEVEL 中断请求级的代码不能使用分页内存。

2。还有一种说法, IRQL DISPATCH_LEVEL 上的线程不能被线程调度,因为线程调度程序也运行在 IRQL DISPATCH_LEVEL ,因此不能进行没有超时的线程等待操作(这很好理解,线程不能被调度,也就是说必须一直执行当前线程,如果是没有超时的等待,此时就不能转而执行其他线程了),分页内存有可能会被转储到磁盘上的页面文件上,如果 IRQL DISPATCH_LEVEL 的代码访问这些虚拟内存,需要重新将磁盘上的数据放回到物理内存才行,这个过程是调用了 MmReadFromSwapEntry 内核函数,这个函数通过磁盘 I/O 读取被转储磁盘的数据,它会创建一个 IRP 发送到文件系统驱动程序,进而转发到磁盘驱动程序, MmReadFromSwapEntry 会调用 KeWaitForSingleObject 等待 I/O 的完成,而 Timeout 参数设置为0!此时如果为 IRQL DISPATCH_LEVEL ,那么后果可想而知,在 IRQL DISPATCH_LEVEL 无限等待是非法操作,系统直接崩溃。

4。一些内核函数有中断请求级的要求,使用这些内核函数一定要注意,内核函数可以从它的 MSDN 文档上查找到他能工作的中断请求级范围。

你可能感兴趣的:(驱动开发)