3. 中断服务例程(ISR)基础:ISR注册、ISR执行上下文、ISR最小化原则

中断服务例程(Interrupt Service Routine, ISR)是WDM驱动响应硬件中断的核心代码路径。本章聚焦ISR的注册机制、执行上下文约束以及设计时必须遵循的最小化原则,帮助开发者构建高效、稳定的中断处理逻辑。

3.1 ISR注册:从INF文件到内核调用链

WDM驱动通过INF文件声明中断资源,并在DriverEntry或AddDevice中调用IoConnectInterrupt完成ISR注册。注册过程将ISR函数与中断向量、处理器亲和性、中断模式绑定。

注册步骤 关键函数/机制 说明
1. 资源声明 INF文件 [HardwareAdd.Reg] 声明中断资源类型(CM_PARTIAL_RESOURCE_DESCRIPTOR)
2. 资源解析 IRP_MN_START_DEVICE 从PCM_PARTIAL_RESOURCE_LIST提取中断向量和级别
3. 注册ISR IoConnectInterrupt 绑定ISR函数、中断对象、处理器掩码、共享模式
4. 中断对象存储 设备扩展(Device Extension) 保存PKINTERRUPT句柄,用于后续注销
注册要点:IoConnectInterrupt必须在PASSIVE_LEVEL调用,且中断资源必须来自IRP_MN_START_DEVICE成功返回的资源列表。共享中断(InterruptShare)需要ISR具备识别自身设备中断的能力。
// ISR注册核心代码片段
NTSTATUS RegisterInterrupt(PDEVICE_OBJECT DeviceObject, PDEVICE_EXTENSION DevExt) {
    CM_PARTIAL_RESOURCE_LIST *pResourceList = NULL;
    // 从IRP_MN_START_DEVICE中获取资源
    // ... 资源解析逻辑 ...
    
    // 注册ISR
    NTSTATUS status = IoConnectInterrupt(
        &DevExt->InterruptObject,           // 输出:中断对象句柄
        (PKSERVICE_ROUTINE)InterruptServiceRoutine, // ISR函数
        DevExt,                             // ISR上下文(ServiceContext)
        NULL,                               // 自旋锁(可选)
        DevExt->InterruptVector,            // 中断向量
        DevExt->InterruptLevel,             // 中断级别(IRQL)
        DevExt->InterruptAffinity,          // 处理器亲和性
        FALSE,                              // 是否共享中断
        EdgeTriggered,                      // 触发模式
        FALSE,                              // 是否支持DPC
        0,                                  // 旋转向量(x86专用)
        Latched                             // 中断模式
    );
    return status;
}
注册后检查:务必验证IoConnectInterrupt返回的PKINTERRUPT句柄非NULL。在DriverUnload或设备移除时,必须调用IoDisconnectInterrupt注销ISR,否则系统会残留中断引用导致蓝屏。

3.2 ISR执行上下文:DIRQL与中断优先级

ISR在DIRQL(Device IRQL)上执行,这是WDM中仅次于高IRQL(如PROFILE_LEVEL)的优先级。在DIRQL上,当前CPU屏蔽了所有低于或等于该IRQL的中断,确保ISR对设备寄存器的访问不被其他中断干扰。

上下文约束:ISR运行在DIRQL,不能访问分页内存、不能等待、不能调用大部分内核同步对象(如事件、互斥体)。任何阻塞操作都会导致系统死锁或IRQL违规(BSOD 0x0A)。
ISR执行上下文:IRQL层级与中断屏蔽 HIGH_IRQL (31) — 机器检查、总线错误 POWER_LEVEL (30) — 电源管理 PROFILE_LEVEL (27) — 性能剖析 DIRQL (设备特定,如 3~26) — ISR执行于此 DISPATCH_LEVEL (2) — DPC执行于此 APC_LEVEL (1) — 异步过程调用 PASSIVE_LEVEL (0) — 普通线程上下文 ISR运行于此

ISR执行上下文的三个关键特征:

  • 不可抢占:同一CPU上,DIRQL中断不能被同级或低级中断抢占,但可被更高级中断(如PROFILE_LEVEL)打断。
  • 无分页:DIRQL上无法处理页错误,所有ISR访问的内存必须是非分页内存(NonPagedPool)。
  • 无上下文切换:ISR不能等待、不能睡眠、不能获取需要等待的锁(如快速互斥体)。

3.3 ISR最小化原则:快速响应与延迟处理

ISR最小化原则是WDM中断设计的核心准则:ISR只做必须立即完成的工作,其余全部推迟到DPC(Deferred Procedure Call)中处理。这是因为ISR在DIRQL上执行,长时间占用DIRQL会阻塞其他设备的中断,降低系统整体响应性。

处理阶段 IRQL 允许的操作 禁止的操作
ISR(中断服务例程) DIRQL 读取设备状态寄存器、清除中断标志、记录最小状态、请求DPC 内存分配、文件I/O、等待、同步对象、分页内存访问
DPC(延迟过程调用) DISPATCH_LEVEL 批量数据处理、链表操作、事件设置、启动I/O 用户模式访问、文件系统操作、等待用户输入
工作者线程 PASSIVE_LEVEL 文件I/O、内存分配、同步对象等待、用户模式交互 无(几乎全部允许)
ISR最小化原则:中断处理三阶段流水线 阶段1:ISR IRQL: DIRQL • 读取状态寄存器 • 清除中断标志 • 记录最小状态 • 请求DPC IoRequestDpc 阶段2:DPC IRQL: DISPATCH_LEVEL • 批量数据处理 • 链表操作 • 设置事件 • 启动后续I/O ExQueueWorkItem 阶段3:工作者线程 IRQL: PASSIVE_LEVEL • 文件I/O • 内存分配 • 同步等待 • 用户交互 核心原则:ISR只做“必须立即做”的事,其余全部推迟到DPC或工作者线程 ISR执行时间应控制在微秒级,DPC在毫秒级,工作者线程无严格时间约束 中断触发 ISR返回 DPC执行 工作者线程
最小化检查清单:编写ISR时,逐项确认——是否访问了分页内存?是否调用了KeWaitForSingleObject?是否执行了超过100条指令?是否在ISR内完成了本可推迟的数据处理?任何一项为“是”,都需要重构。

3.4 ISR返回值与中断识别

ISR返回BOOLEAN值,告知系统是否真正处理了中断。对于共享中断,此返回值至关重要。

返回值 宏定义 含义 系统行为
TRUE TRUE ISR识别并处理了中断 系统不再调用其他共享中断的ISR,并调度DPC
FALSE FALSE ISR未识别到设备中断 系统继续调用链中下一个共享中断的ISR
// ISR标准实现模板
BOOLEAN InterruptServiceRoutine(
    _In_ struct _KINTERRUPT *Interrupt,
    _In_ PVOID ServiceContext          // 设备扩展指针
) {
    PDEVICE_EXTENSION devExt = (PDEVICE_EXTENSION)ServiceContext;
    BOOLEAN recognized = FALSE;
    
    // 1. 读取设备中断状态寄存器
    ULONG status = READ_REGISTER_ULONG(devExt->InterruptStatusReg);
    
    // 2. 判断是否为本设备中断
    if (status & devExt->InterruptMask) {
        recognized = TRUE;
        
        // 3. 清除中断标志(必须立即执行)
        WRITE_REGISTER_ULONG(devExt->InterruptClearReg, devExt->InterruptMask);
        
        // 4. 记录最小状态(非分页内存)
        devExt->LastInterruptStatus = status;
        
        // 5. 请求DPC处理剩余工作
        IoRequestDpc(devExt->DeviceObject, NULL, NULL);
    }
    
    // 6. 返回是否识别
    return recognized;
}
共享中断陷阱:如果ISR返回TRUE但实际并未清除中断标志,会导致该中断被“吞掉”,其他共享设备无法收到中断。如果ISR返回FALSE但实际设备已产生中断,会导致中断丢失。务必通过硬件状态寄存器准确判断。

3.5 ISR设计常见错误与避坑

  • 错误1:在ISR中调用KeSetEvent — 如果事件是自动重置事件且等待线程在DISPATCH_LEVEL以下,会导致IRQL违规。应改为在DPC中设置事件。
  • 错误2:ISR中访问分页内存 — 分页内存可能在DIRQL上引发页错误,导致系统崩溃。所有ISR访问的数据必须分配自NonPagedPool。
  • 错误3:ISR执行时间过长 — 超过100微秒的ISR会显著增加系统中断延迟。使用性能分析工具(如Xperf)测量ISR耗时。
  • 错误4:未处理共享中断识别 — 在共享中断配置下,ISR必须通过硬件寄存器确认中断归属,不能假设每次调用都是本设备中断。
  • 错误5:ISR中调用KeAcquireSpinLock — 自旋锁可以在DIRQL上获取,但必须确保锁的IRQL匹配,且释放顺序正确。推荐使用中断自旋锁(InterruptSpinLock)。
最佳实践:ISR代码应保持精简,通常不超过50~100条指令。将ISR视为“中断通知”而非“中断处理”,真正的处理逻辑交给DPC。在开发阶段,可以在ISR入口和出口添加性能计数器(QueryPerformanceCounter),监控ISR执行时间。