Windows 内核的各个运行级别(IRQL,Interrupt Request Level)是非常关键的基础知识,尤其对于驱动开发和内核调试来说。
Windows 内核 IRQL(中断请求级别)概述
IRQL 是 Windows 内核用来管理硬件中断和同步的优先级机制。它是一种优先级标记,内核根据当前 IRQL 决定哪些代码可以运行,哪些会被挂起或被抢占。
1. IRQL 的基本概念
-
IRQL 是一个数值,表示当前处理器的中断优先级等级。
-
数值越高,优先级越高,能中断或阻塞数值较低的代码执行。
-
内核和驱动程序代码运行时都在某个 IRQL 上。
2. 主要的 IRQL 等级(x86/x64 平台)
IRQL 级别 | 数值 (示例) | 描述 | 代码可以执行的操作 |
---|---|---|---|
PASSIVE_LEVEL | 0 | 被动级别,普通线程执行环境 | 可以做任何事情,比如内存分配、阻塞等待 |
APC_LEVEL | 1 | 异步过程调用(APC)级别 | 执行异步过程调用,不能做高延迟操作 |
DISPATCH_LEVEL | 2 | 调度级别 | 调度器运行级别,不能进行阻塞操作,不能分配分页内存 |
DEVICE_IRQL | 3-27 | 设备中断级别,不同硬件中断对应不同值 | 硬件中断处理,禁止线程调度,不能做阻塞或分页内存分配 |
HIGH_LEVEL | 28 | 高优先级中断 | 极高优先级中断处理 |
实际上,不同处理器架构下,IRQL 数值范围和定义会有所不同,上面是 Windows x86/x64 的典型表示。
3. 各 IRQL 级别允许的操作限制
操作内容 | PASSIVE_LEVEL | APC_LEVEL | DISPATCH_LEVEL | DEVICE_IRQL/HIGH_LEVEL |
---|---|---|---|---|
分配分页池内存 | 允许 | 允许 | 不允许 | 不允许 |
分配非分页池内存 | 允许 | 允许 | 允许 | 允许 |
阻塞等待(睡眠) | 允许 | 不允许 | 不允许 | 不允许 |
调度线程 | 允许 | 不允许 | 不允许 | 不允许 |
调用同步机制(如互斥锁) | 允许 | 有限制 | 有限制 | 严格限制 |
响应硬件中断 | 不允许 | 不允许 | 允许 | 允许 |
4. 典型 IRQL 场景举例
-
PASSIVE_LEVEL (0)
这是普通应用程序和大部分内核线程的运行级别,允许做任何内核操作,比如申请分页内存、文件操作、等待同步对象。 -
DISPATCH_LEVEL (2)
当内核进入调度级别时,不允许任何可能阻塞的操作,典型例子是自旋锁保护代码、DPC(延迟过程调用)处理。 -
DEVICE_IRQL
处理硬件中断时使用,通常驱动程序的中断服务例程(ISR)会在该级别执行。不能进行任何可能引起阻塞的操作。
5. IRQL 的调度和切换
-
当高优先级的中断发生时,CPU 会提高 IRQL 来响应中断,低优先级代码会被挂起。
-
IRQL 只能单向提高或恢复到之前的级别,不能随意跳转。
-
许多内核API会根据调用时的IRQL不同,行为也不同,甚至会失败。
6. 你写驱动时IRQL的影响
-
在 DISPATCH_LEVEL 及以上,不能调用可能阻塞或需要分页内存的函数。
-
ExAllocatePoolWithTag 如果需要分页内存,只能在 PASSIVE_LEVEL 调用,否则会死锁或失败。
-
设计回调函数(如你的
MiniFilterMessageNotify
)时要特别注意它运行的 IRQL,避免不允许的操作。
7. 总结
级别 | 代码可运行位置 | 允许操作 | 典型使用场景 |
---|---|---|---|
PASSIVE_LEVEL | 用户线程和大多数内核线程 | 允许所有内存和阻塞操作 | 普通驱动初始化,文件I/O,等待事件等 |
APC_LEVEL | 异步过程调用 | 限制阻塞和分页内存分配 | APC回调处理 |
DISPATCH_LEVEL | DPC、调度器中断上下文 | 不允许阻塞,非分页内存分配 | 自旋锁保护代码,DPC处理 |
DEVICE_IRQL | 硬件中断服务例程 | 严格限制,不能阻塞 | 硬件中断处理 |
HIGH_LEVEL | 最高级别中断 | 非常有限的操作 | 非常高优先级中断 |