Reactos 第 9 章 设备驱动 — 9.14 IRP请求的完成与返回

第 9 章 设备驱动 --- 9.14 IRP请求的完成与返回

本节剖析 IRP 请求的完成(IoCompleteRequest)与返回机制。 IRP 完成是 I/O 路径的"出口",I/O 管理器沿完成例程链 逆序 调用,把 IRP 从下层驱动 传回 上层用户态 。理解 IRP 完成的关键是把握 IoMarkIrpPendingIoCompleteRequest 的协作完成例程链优先级提升 。ReactOS 的 IoCompleteRequest 实现在 ntoskrnl/io/iomgr/irp.c(file:///d:/reactos/ntoskrnl/io/iomgr/irp.c)。


概述

IRP 的完成是 I/O 路径的 逆转

  • 派发 时 IRP 正序 流过设备栈:Top → FDO → PDO
  • 完成 时 IRP 逆序 返回:PDO → FDO → Top → I/O 管理器 → 用户

驱动可在 IRP 派发时注册 完成例程,当下层完成时回到本层,可用于:

  1. 检查下层返回结果
  2. 修改 IRP 数据
  3. 撤销本层做的准备(如释放锁、解除映射)
  4. 决定 IRP 是否继续返回给上层

本节内容概览

  • 9.14.0 框架图
  • 9.14.1 IoCompleteRequest 流程
  • 9.14.2 完成例程链
  • 9.14.3 IoMarkIrpPendingSTATUS_PENDING
  • 9.14.4 IoSetCompletionRoutine
  • 9.14.5 IoStartNextPacket / IoStartPacket
  • 9.14.6 IoFreeIrp 与资源释放
  • 9.14.7 同步与异步完成
  • 9.14.8 总结与代码索引

学习目标

  • 掌握 IoCompleteRequest 的内部流程
  • 理解完成例程链的形成
  • 区分 STATUS_PENDINGSTATUS_SUCCESS
  • 知道 IoStartNextPacket 在 StartIO 模型中的作用

涉及的内核子系统

子系统 头文件/源文件 核心作用
IRP 主处理 ntoskrnl/io/iomgr/irp.c(file:///d:/reactos/ntoskrnl/io/iomgr/irp.c) IoCompleteRequestIoMarkIrpPending
I/O 派发 ntoskrnl/io/iomgr/dispatch.c(file:///d:/reactos/ntoskrnl/io/iomgr/dispatch.c) IoCallDriver
取消 ntoskrnl/io/iomgr/cancel.c(file:///d:/reactos/ntoskrnl/io/iomgr/cancel.c) IoCancelIrp
同步等待 ntoskrnl/io/iomgr/io.c(file:///d:/reactos/ntoskrnl/io/iomgr/io.c) IopSynchronousApiTail
文件 I/O ntoskrnl/io/iomgr/file.c(file:///d:/reactos/ntoskrnl/io/iomgr/file.c) IopCreateFile
I/O 类型 sdk/include/xdk/iotypes.h(file:///d:/reactos/sdk/include/xdk/iotypes.h) IO_STACK_LOCATION

9.14.0 框架图

复制代码
  IRP 派发(正序)                IRP 完成(逆序)
  =====================           =====================
  设备栈顶 Top                    Top
      | IoCallDriver(Next, Irp)        ^ 完成例程1
      v                                |
  FDO                                FDO
      | IoCallDriver(Next, Irp)        ^ 完成例程2
      v                                |
  PDO                                PDO
      | 硬件完成 -> 驱动完成            |
      v                                |
  IoCompleteRequest (在 PDO 启动)

完成例程形成 LIFO 链 ,最近的 IoSetCompletionRoutine 最先被调用。


9.14.0b IRP完成机制的深层设计与实现原理

IRP完成的设计哲学

IRP(I/O Request Packet)的完成机制是Windows NT I/O子系统的核心创新之一。与传统的同步请求-响应模型不同,NT的IRP完成采用分层逆序回调的设计,这一设计直接源于VMS操作系统的I/O架构,并在Windows NT中得到了进一步的完善和扩展。

从设计角度看,IRP完成机制需要解决三个核心问题:

  1. 资源释放的有序性:IRP在下发过程中,每一层驱动都可能分配资源(内存、锁、MDL映射等)。完成时必须按照**LIFO(后进先出)**的顺序释放这些资源,否则会导致资源泄漏或访问违规。

  2. 错误传播与处理:下层驱动返回的错误状态需要向上传播,但每一层驱动可能希望对错误进行特殊处理(如重试、降级、日志记录)。完成例程链提供了这种灵活性。

  3. 同步与异步的统一:无论是同步等待还是异步通知,IRP的完成路径是统一的。差异仅在于完成后的通知机制(事件信号 vs APC入队)。

ReactOS的IofCompleteRequest实现精确地复现了这些设计意图,同时在某些细节上进行了优化和简化。

完成例程链的形成与遍历

栈单元的分配与使用

IRP结构中的IO_STACK_LOCATION数组是完成例程链的物理载体。每个设备对象在设备栈中占据一个位置,对应一个栈单元。当IRP通过IoCallDriver下发时,CurrentLocation指针向下移动(递减),指向当前设备的栈单元:

c 复制代码
// IofCallDriver中的关键代码
Irp->CurrentLocation--;
StackPtr = IoGetNextIrpStackLocation(Irp);
Irp->Tail.Overlay.CurrentStackLocation = StackPtr;

驱动在调用IoCallDriver之前,通过IoSetCompletionRoutine下一个栈单元(即下层设备的栈单元)中设置完成例程:

c 复制代码
IoSetCompletionRoutine(Irp, MyCompletion, Context, TRUE, TRUE, TRUE);
IoCallDriver(LowerDevice, Irp);

这种"在下一个栈单元设置完成例程"的设计意味着:当IRP到达最底层的PDO时,从顶到底的每一个栈单元都可能包含一个完成例程。完成时,IofCompleteRequest从当前栈单元开始,向上 遍历(CurrentLocation递增),依次调用每个完成例程。

完成例程的调用条件

IoSetCompletionRoutine的三个布尔参数(InvokeOnSuccessInvokeOnErrorInvokeOnCancel)决定了完成例程在何种情况下被调用。ReactOS的实现中,这些标志被存储在栈单元的Control字段中:

c 复制代码
// IoSetCompletionRoutine的实现
if (InvokeOnSuccess) StackPtr->Control |= SL_INVOKE_ON_SUCCESS;
if (InvokeOnError) StackPtr->Control |= SL_INVOKE_ON_ERROR;
if (InvokeOnCancel) StackPtr->Control |= SL_INVOKE_ON_CANCEL;

IofCompleteRequest的遍历循环中,根据IRP的最终状态和这些标志决定是否调用完成例程:

c 复制代码
if ((NT_SUCCESS(Irp->IoStatus.Status) && (StackPtr->Control & SL_INVOKE_ON_SUCCESS)) ||
    (!NT_SUCCESS(Irp->IoStatus.Status) && (StackPtr->Control & SL_INVOKE_ON_ERROR)) ||
    (Irp->Cancel && (StackPtr->Control & SL_INVOKE_ON_CANCEL)))
{
    // 调用完成例程
    Status = StackPtr->CompletionRoutine(DeviceObject, Irp, StackPtr->Context);
}

这种细粒度的控制允许驱动只在特定情况下执行清理代码,避免不必要的处理开销。

STATUS_MORE_PROCESSING_REQUIRED的深层语义

完成例程返回STATUS_MORE_PROCESSING_REQUIRED是IRP完成机制中最强大也最容易被误用的特性。当完成例程返回这个状态时,IofCompleteRequest立即停止遍历,不再调用后续的完成例程,也不执行最终的清理工作(如释放IRP、解除MDL锁定等)。

这个机制的典型应用场景包括:

  1. IRP重用:过滤驱动可能需要在完成路径中修改IRP参数并重新下发。例如,一个加密过滤驱动在读取完成后,解密数据,然后重新发起一个写操作。

  2. 异步完成 :某些驱动需要在完成例程中执行异步操作(如排队到工作线程),此时返回STATUS_MORE_PROCESSING_REQUIRED可以阻止IRP被立即释放,驱动在异步操作完成后手动调用IoCompleteRequest

  3. 分片I/O :在处理大型I/O请求时,驱动可能将其拆分为多个子IRP。主IRP的完成例程返回STATUS_MORE_PROCESSING_REQUIRED,等待所有子IRP完成后再统一处理。

ReactOS代码中对STATUS_MORE_PROCESSING_REQUIRED的处理非常严格:

c 复制代码
if (Status == STATUS_MORE_PROCESSING_REQUIRED) return;

这意味着驱动在返回这个状态后,必须承担以下责任:

  • 最终调用IoCompleteRequestIoFreeIrp释放IRP
  • 确保所有资源被正确清理
  • 如果IRP是同步的,确保等待线程被唤醒

优先级提升与调度优化

IoCompleteRequest的第二个参数PriorityBoost是一个常被忽视但非常重要的优化机制。当同步I/O的等待线程被唤醒时,系统会临时提升该线程的优先级,使其能够更快地获得CPU时间片,从而减少I/O延迟。

ReactOS中定义的优先级提升常量反映了不同设备的特性:

常量 适用场景
IO_NO_INCREMENT 0 后台I/O、不紧急的操作
IO_DISK_INCREMENT 1 磁盘读写
IO_NETWORK_INCREMENT 2 网络包接收
IO_MOUSE_INCREMENT 4 鼠标输入
IO_KEYBOARD_INCREMENT 6 键盘输入

键盘和鼠标I/O获得最高的优先级提升,因为用户交互对延迟极其敏感。一个键盘输入如果延迟100ms,用户会明显感觉到"卡顿";而磁盘I/O延迟100ms通常不会被察觉。

ReactOS在IofCompleteRequest中通过KeSetEvent传递优先级提升:

c 复制代码
KeSetEvent(&Irp->Tail.Overlay.InternalEvent, PriorityBoost, FALSE);

这个优先级提升是临时的,线程在运行一个时间片后会恢复到原始优先级。这种"优先级继承"的变体有效地避免了优先级反转问题。

关联IRP与主IRP的完成

在处理分散I/O(如ReadFileScatter)或分片I/O时,ReactOS使用关联IRP(Associated IRP)机制。一个主IRP(Master IRP)可以关联多个子IRP,每个子IRP负责一部分数据传输。

关联IRP的完成逻辑在IofCompleteRequest中有专门的处理:

c 复制代码
if (Irp->Flags & IRP_ASSOCIATED_IRP)
{
    MasterIrp = Irp->AssociatedIrp.MasterIrp;
    MasterCount = InterlockedDecrement(&MasterIrp->AssociatedIrp.IrpCount);
    
    // 释放子IRP的MDL
    for (Mdl = Irp->MdlAddress; Mdl; Mdl = NextMdl)
    {
        NextMdl = Mdl->Next;
        IoFreeMdl(Mdl);
    }
    
    IoFreeIrp(Irp);
    
    // 只有当所有子IRP都完成时,才完成主IRP
    if (!MasterCount) IofCompleteRequest(MasterIrp, PriorityBoost);
    return;
}

这种设计确保了主IRP在所有子IRP完成之前不会被释放,避免了竞态条件和资源泄漏。IrpCount使用原子操作(InterlockedDecrement)保证多线程环境下的正确性。

分页I/O与关闭操作的特殊处理

分页I/O(Paging I/O)和关闭操作(Close Operation)在IRP完成时有特殊的处理路径。分页I/O是由内存管理器发起的,用于将修改过的页面写回磁盘或从磁盘读取页面到内存。关闭操作是在文件对象被删除时发起的,用于刷新缓存和释放资源。

ReactOS代码中,这两种操作通过IRP_PAGING_IOIRP_CLOSE_OPERATION标志识别:

c 复制代码
if (Irp->Flags & (IRP_PAGING_IO | IRP_CLOSE_OPERATION))
{
    if (Irp->Flags & (IRP_SYNCHRONOUS_PAGING_IO | IRP_CLOSE_OPERATION))
    {
        // 同步分页I/O或关闭操作
        *Irp->UserIosb = Irp->IoStatus;
        KeSetEvent(Irp->UserEvent, PriorityBoost, FALSE);
        
        if (Irp->Flags & IRP_SYNCHRONOUS_PAGING_IO)
        {
            IoFreeIrp(Irp);
        }
    }
}

分页I/O的特殊之处在于:

  • 它不经过文件系统的缓存管理器,直接由存储栈处理
  • 它使用IRP_NOCACHE标志,绕过缓存
  • 它的完成不触发用户态APC,因为发起者是内核而非用户线程

完成路径中的重解析处理

NTFS文件系统支持重解析点 (Reparse Point),用于实现符号链接、挂载点等功能。当IRP遇到重解析点时,文件系统驱动返回STATUS_REPARSE,并在Irp->IoStatus.Information中指定重解析标签。

ReactOS在IofCompleteRequest中对重解析有特殊处理:

c 复制代码
if (Irp->IoStatus.Status == STATUS_REPARSE)
{
    if (Irp->IoStatus.Information > IO_REMOUNT)
    {
        if (Irp->IoStatus.Information == IO_REPARSE_TAG_MOUNT_POINT)
        {
            // 保存重解析数据缓冲区,避免被释放
            DataBuffer = (PREPARSE_DATA_BUFFER)Irp->Tail.Overlay.AuxiliaryBuffer;
            Irp->Tail.Overlay.AuxiliaryBuffer = NULL;
        }
        else
        {
            Irp->IoStatus.Status = STATUS_IO_REPARSE_TAG_NOT_HANDLED;
        }
    }
}

I/O管理器在收到STATUS_REPARSE后,会根据重解析标签重新构造IRP并下发到新的设备栈。这个过程对用户态是透明的------CreateFile会自动处理重解析,最终打开正确的文件。

完成路径的性能优化

IofCompleteRequest是一个性能关键的路径,ReactOS对其进行了多项优化:

  1. 快速路径 :如果没有完成例程(大多数简单驱动不设置完成例程),IofCompleteRequest直接跳到清理阶段,避免不必要的遍历。

  2. 内联函数IoCompleteRequest实际上是一个宏,展开为IofCompleteRequest的fastcall调用,减少函数调用开销。

  3. 栈位置缓存 :在遍历循环中,StackPtrCurrentLocation被缓存为局部变量,避免重复访问IRP结构。

  4. 批量MDL释放:如果IRP有多个MDL,ReactOS使用循环一次性释放,而不是递归调用。

与Windows系统的差异

ReactOS的IRP完成机制与Windows的主要差异集中在边缘情况的处理上:

  1. 错误传播 :Windows在SL_ERROR_RETURNED标志的处理上更加复杂,支持多层错误覆盖。ReactOS的实现简化了这一逻辑,在大多数情况下行为一致。

  2. 延迟I/O完成 :Windows支持IRP_DEFER_IO_COMPLETION标志,允许驱动在特定条件下延迟APC的入队。ReactOS实现了基本的支持,但在某些复杂的过滤驱动场景下可能不完全一致。

  3. 保留IRP :Windows为分页I/O保留了特殊的IRP池(Reserve IRP),以避免在内存压力时无法分配IRP。ReactOS实现了IopReserveIrpAllocator,但分配策略与Windows有所不同。

调试IRP完成问题

IRP完成相关的bug通常表现为以下几种形式:

  1. Bugcheck IRP_NOT_CANCELLED:IRP完成时仍然设置了取消例程。驱动在设置取消例程后,必须在完成或取消时清除它。

  2. Bugcheck MULTIPLE_IRP_COMPLETE_REQUESTS :同一个IRP被多次完成。这通常是因为驱动在错误路径中重复调用了IoCompleteRequest。ReactOS在IofCompleteRequest开始处检查CurrentLocation,如果超出范围则触发这个bugcheck。

  3. 资源泄漏 :完成例程返回STATUS_MORE_PROCESSING_REQUIRED但忘记最终释放IRP。这会导致IRP池耗尽,系统无法处理新的I/O请求。

  4. 死锁 :同步I/O的完成例程返回STATUS_MORE_PROCESSING_REQUIRED但未设置事件,导致等待线程永远阻塞。

  5. 优先级反转 :使用错误的PriorityBoost值(如对键盘I/O使用IO_NO_INCREMENT),导致用户感知到延迟。

调试技巧:

  • 启用IOTRACE(IO_IRP_DEBUG, ...)跟踪IRP的完成路径
  • 使用!irp命令在WinDbg中检查IRP的状态和栈单元
  • 检查Irp->IoStatus.Status是否为STATUS_PENDING(完成时不应该还是pending)
  • 验证完成例程的返回值是否符合预期

9.14.1 IoCompleteRequest 流程

c 复制代码
VOID IoCompleteRequest(IN PIRP Irp, IN CCHAR PriorityBoost)
{
    // PriorityBoost: 给 IRP 等待线程的优先级提升量
    //   IO_NO_INCREMENT, IO_DISK_INCREMENT, IO_KEYBOARD_INCREMENT 等
}

完整流程

irp.c(file:///d:/reactos/ntoskrnl/io/iomgr/irp.c):

c 复制代码
VOID IoCompleteRequest(IN PIRP Irp, IN CCHAR PriorityBoost)
{
    PIO_STACK_LOCATION IoStackLocation;
    PIO_COMPLETION_ROUTINE CompletionRoutine;
    PDEVICE_OBJECT DeviceObject;
    PETHREAD Thread;
    KIRQL OldIrql;
    PIRP MasterIrp;

    ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);

    // 1. 把 CurrentStackLocation 推到顶
    //    让下一个完成例程看到正确的栈单元
    while (Irp->CurrentLocation <= Irp->StackCount) {
        IoStackLocation = IoGetCurrentIrpStackLocation(Irp);
        DeviceObject = IoStackLocation->DeviceObject;

        // 2. 提取完成例程
        CompletionRoutine = IoStackLocation->CompletionRoutine;
        if (!CompletionRoutine) {
            // 没有完成例程,继续向上推
            IoSkipCurrentIrpStackLocation(Irp);
            continue;
        }

        // 3. 清完成例程(防止重复调用)
        IoStackLocation->CompletionRoutine = NULL;
        IoStackLocation->Context = NULL;
        IoStackLocation->Control = 0;

        // 4. 调用完成例程
        //    如果例程返回 STATUS_MORE_PROCESSING_REQUIRED,停止
        if (CompletionRoutine(DeviceObject, Irp, IoStackLocation->Context)
            == STATUS_MORE_PROCESSING_REQUIRED) {
            return;
        }
    }

    // 5. 全部栈单元已用完,处理最后清理
    // 5.1 同步 IRP:提升等待线程优先级
    Thread = Irp->Tail.Overlay.Thread;
    if (Thread) {
        KeSetEvent(&Irp->Tail.Overlay.InternalEvent, PriorityBoost, FALSE);
    }

    // 5.2 解锁分页 I/O
    if (Irp->Flags & IRP_UNLOCK_USER_BUFFER) {
        ...
    }

    // 5.3 MDL 释放
    if (Irp->MdlAddress) {
        IoFreeMdl(Irp->MdlAddress);
    }

    // 5.4 APC 入队(用户态完成)
    if (Irp->Tail.Overlay.AuxiliaryBuffer) {
        // 释放 auxiliary buffer
    }

    // 5.5 处理用户 APC
    if (Irp->Overlay.AsynchronousParameters.UserApcRoutine) {
        KeInsertQueueApc(&Irp->Tail.Apc, ...);
    }

    // 5.6 释放 IRP
    IoFreeIrp(Irp);
}

关键步骤

  1. 正序遍历栈单元CurrentLocation 从 1 到 StackCount
  2. 每个栈单元 :调用 CompletionRoutine(如果有)
  3. STATUS_MORE_PROCESSING_REQUIRED:停止完成链,IRP 留待驱动自行处理
  4. 栈空后:清理资源、解锁、APC 入队、释放 IRP

同步完成(IoCompleteRequest vs KeSetEvent

IoCompleteRequest 内部调用 KeSetEvent 唤醒等待的线程:

c 复制代码
if (Thread) {
    KeSetEvent(&Irp->Tail.Overlay.InternalEvent, PriorityBoost, FALSE);
}

PriorityBoost 是给该线程的优先级提升量:

提升常量 用途
IO_NO_INCREMENT 0 不提升
IO_DISK_INCREMENT 1 磁盘 I/O
IO_KEYBOARD_INCREMENT 6 键盘 I/O
IO_MOUSE_INCREMENT 4 鼠标 I/O
IO_NETWORK_INCREMENT 2 网络 I/O

9.14.2 完成例程链

IoSetCompletionRoutine

c 复制代码
VOID IoSetCompletionRoutine(
    IN PIRP Irp,
    IN PIO_COMPLETION_ROUTINE CompletionRoutine,
    IN PVOID Context,
    IN BOOLEAN InvokeOnSuccess,
    IN BOOLEAN InvokeOnError,
    IN BOOLEAN InvokeOnCancel
);

设置 IRP 当前栈单元的完成例程。InvokeOn* 控制例程何时被调用。

完成例程链形成

每层驱动调用 IoCallDriver 之前都设置自己的完成例程:

c 复制代码
// 顶层 dispatch
NTSTATUS TopDispatchRead(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
    IoCopyCurrentIrpStackLocationToNext(Irp);
    IoSetCompletionRoutine(Irp, TopCompletion, NULL, TRUE, TRUE, TRUE);
    return IoCallDriver(DeviceObject->LowerDeviceObject, Irp);
}

NTSTATUS TopCompletion(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context)
{
    if (Irp->IoStatus.Status == STATUS_SUCCESS) {
        // 处理成功情况
    }
    return STATUS_SUCCESS;  // 或 STATUS_MORE_PROCESSING_REQUIRED
}

当 PDO 完成 IRP,调用 IoCompleteRequestIoCompleteRequest 沿栈 逆序 调用完成例程:

复制代码
PDO 完成
  ↓ IoCompleteRequest
  调用 FDO 的完成例程 (InvokeOnSuccess = TRUE 时)
  ↓ 返回 STATUS_SUCCESS
  调用 Top 的完成例程
  ↓ 返回 STATUS_SUCCESS
  全部完成 -> IoFreeIrp

返回 STATUS_MORE_PROCESSING_REQUIRED

如果完成例程返回 STATUS_MORE_PROCESSING_REQUIREDIoCompleteRequest 停止 调用后续的完成例程,由当前例程自行处理 IRP(最终必须 IoFreeIrp):

c 复制代码
NTSTATUS MyCompletion(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context)
{
    // 修改 IRP,重新启动
    IoMarkIrpPending(Irp);  // 不需要,因为是 MORE_PROCESSING
    IoStartNextPacket(...);
    IoStartPacket(...);     // 重新启动
    IoFreeIrp(Irp);
    return STATUS_MORE_PROCESSING_REQUIRED;
}

9.14.3 IoMarkIrpPendingSTATUS_PENDING

IoMarkIrpPending

c 复制代码
VOID IoMarkIrpPending(IN PIRP Irp)
{
    KIRQL OldIrql;
    IoAcquireCancelSpinLock(&OldIrql);
    IoSetTopLevelIrp(Irp);
    Irp->Tail.Overlay.CurrentStackLocation->Control |= SL_PENDING_RETURNED;
    IoReleaseCancelSpinLock(OldIrql);
}

设置 SL_PENDING_RETURNED 标志,使 I/O 管理器知道 IRP 处于 pending 状态。

STATUS_PENDING 返回条件

驱动派发例程返回 STATUS_PENDING 的条件:

  1. 调用了 IoMarkIrpPending
  2. IRP 排队到设备队列或传给下层等待完成

重要规则

  • 同步 返回 STATUS_PENDING:调用方会 KeWaitForSingleObject
  • 异步 返回 STATUS_PENDING:调用方需查询或等待事件

同步派发例程

c 复制代码
NTSTATUS SyncDispatchRead(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
    PIO_STACK_LOCATION IoStack = IoGetCurrentIrpStackLocation(Irp);

    // 1. 检查参数
    if (...) {
        Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
        IoCompleteRequest(Irp, IO_NO_INCREMENT);
        return STATUS_INVALID_PARAMETER;  // 同步完成
    }

    // 2. 排队
    IoMarkIrpPending(Irp);
    IoStartPacket(DeviceObject, Irp, NULL, NULL);

    return STATUS_PENDING;  // 异步
}

异步派发例程

c 复制代码
NTSTATUS AsyncDispatchRead(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
    // 1. 设置完成例程
    IoSetCompletionRoutine(Irp, MyCompletion, NULL, TRUE, TRUE, TRUE);

    // 2. 传给下层
    IoCallDriver(LowerDevice, Irp);

    return STATUS_PENDING;  // 让调用者等待
}

9.14.4 IoSetCompletionRoutine 详细行为

c 复制代码
IoSetCompletionRoutine(Irp, CompletionRoutine, Context,
                       InvokeOnSuccess, InvokeOnError, InvokeOnCancel);

InvokeOn* 标志决定完成例程被调用的条件:

InvokeOnSuccess InvokeOnError InvokeOnCancel 调用条件
TRUE FALSE FALSE Status == STATUS_SUCCESS
FALSE TRUE FALSE 仅错误
FALSE FALSE TRUE 仅取消
TRUE TRUE FALSE 成功或错误
TRUE TRUE TRUE 所有情况

常见模式

c 复制代码
// 模式1:仅关注错误
IoSetCompletionRoutine(Irp, MyErrorHandler, NULL, FALSE, TRUE, FALSE);

// 模式2:所有情况(最常见)
IoSetCompletionRoutine(Irp, MyHandler, Context, TRUE, TRUE, TRUE);

// 模式3:仅关注成功
IoSetCompletionRoutine(Irp, MySuccessHandler, NULL, TRUE, FALSE, FALSE);

9.14.5 IoStartNextPacket / IoStartPacket

旧式 StartIO 模型的两个核心函数:

IoStartPacket

c 复制代码
VOID IoStartPacket(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp,
    IN PULONG Key,
    IN PDRIVER_CANCEL CancelFunction
);

把 IRP 排队到设备对象的 IoQueue,设置 CancelFunction,然后:

  1. 如果 CurrentIrp == NULL,立即启动
  2. 否则插入队列

IoStartNextPacket

c 复制代码
VOID IoStartNextPacket(IN PDEVICE_OBJECT DeviceObject, IN BOOLEAN Cancelable);

从队列取出下一个 IRP 启动。

StartIO 模式典型流程

c 复制代码
// 1. 派发
NTSTATUS MyDispatchRead(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
    IoMarkIrpPending(Irp);
    IoStartPacket(DeviceObject, Irp, NULL, NULL);
    return STATUS_PENDING;
}

// 2. StartIO
VOID MyStartIo(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
    // 启动设备 I/O
    // 中断到来后调用 IoStartNextPacket
    ProgramHardware(Irp);
}

// 3. DPC 完成
VOID MyDpcRoutine(IN PKDPC Dpc, IN PDEVICE_OBJECT DeviceObject, ...)
{
    PIRP Irp = DeviceObject->CurrentIrp;
    Irp->IoStatus.Status = STATUS_SUCCESS;
    IoCompleteRequest(Irp, IO_DISK_INCREMENT);
    IoStartNextPacket(DeviceObject, FALSE);
}

StartIO 模型 vs 直接派发

模型 优点 缺点
StartIO 自动串行化、简化同步 性能受限(一次一个)
直接派发 高并发、灵活 需自行同步

9.14.6 IoFreeIrp 与资源释放

IoFreeIrp

c 复制代码
VOID IoFreeIrp(IN PIRP Irp);

释放 IRP 包含的所有资源。

资源释放顺序

IoCompleteRequest 在栈空时自动释放:

  1. 同步事件:KeSetEvent 唤醒等待者
  2. 用户缓冲区解锁(IRP_UNLOCK_USER_BUFFER
  3. MDL 释放
  4. 系统缓冲释放(DO_BUFFERED_IO
  5. Auxiliary buffer 释放
  6. APC 入队(用户态完成通知)
  7. IRP 释放

驱动的责任

驱动在调用 IoCallDriver不得 释放 Irp 拥有的资源。下层完成时由 I/O 管理器统一释放。

但驱动可调 IoFreeIrp 释放 自己分配 的 IRP(如 IoAllocateIrp):

c 复制代码
PIRP Irp = IoAllocateIrp(StackSize, FALSE);
// ... 使用 IRP ...
IoFreeIrp(Irp);  // 自己分配自己释放

MasterIrp

某些高级用法中,IRP 是 分片 的(partial IRP),形成 IRP 链:

c 复制代码
typedef struct _IRP {
    ...
    union {
        struct _IRP *MasterIrp;   // 链头
        LONG IrpCount;            // 引用计数
        PVOID SystemBuffer;
    } AssociatedIrp;
} IRP;

MasterIrp 用于并行 I/O(如 IoReadFile 写多个分散的缓冲)。


9.14.7 同步与异步完成

同步完成(STATUS_SUCCESS 直接返回)

c 复制代码
// 派发例程中直接完成
NTSTATUS SyncDispatchRead(...)
{
    // 1. 处理 I/O(不需硬件)
    RtlCopyMemory(Irp->AssociatedIrp.SystemBuffer, Source, Length);

    // 2. 填充 IoStatus
    Irp->IoStatus.Status = STATUS_SUCCESS;
    Irp->IoStatus.Information = Length;

    // 3. 完成
    IoCompleteRequest(Irp, IO_NO_INCREMENT);
    return STATUS_SUCCESS;
}

异步完成(排到队列或下传)

c 复制代码
// 1. 排到队列
IoMarkIrpPending(Irp);
IoStartPacket(DeviceObject, Irp, NULL, NULL);
return STATUS_PENDING;

// 2. 传下层
IoSetCompletionRoutine(Irp, MyCompletion, NULL, TRUE, TRUE, TRUE);
IoCallDriver(LowerDevice, Irp);
return STATUS_PENDING;

下层完成时回到本层

c 复制代码
// 完成例程
NTSTATUS MyCompletion(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context)
{
    // 1. 检查下层结果
    if (!NT_SUCCESS(Irp->IoStatus.Status)) {
        // 错误处理
    }

    // 2. 撤销本层资源(如释放缓冲区)
    if (MyAllocation) {
        ExFreePool(MyAllocation);
    }

    // 3. 返回 STATUS_SUCCESS 让 IRP 继续向更上层传递
    //    或 STATUS_MORE_PROCESSING_REQUIRED 停止
    return STATUS_SUCCESS;
}

9.14.8 总结

关键要点

  1. IoCompleteRequest 沿栈逆序调用完成例程CurrentLocation 从 1 到 StackCount
  2. 完成例程链 :每层驱动通过 IoSetCompletionRoutine 注册
  3. STATUS_MORE_PROCESSING_REQUIRED:停止完成链,驱动负责清理
  4. IoMarkIrpPending :标记 IRP 异步,调用方返回 STATUS_PENDING
  5. IoSetCompletionRoutineInvokeOnSuccess/Error/Cancel 控制调用条件
  6. IoStartPacket / IoStartNextPacket:StartIO 模型
  7. IoFreeIrp:释放 IRP 拥有的资源

下一章(第 10 章)将进入网络操作领域。


本章代码索引

文件 内容
irp.c(file:///d:/reactos/ntoskrnl/io/iomgr/irp.c) IoCompleteRequestIoMarkIrpPendingIoFreeIrp
dispatch.c(file:///d:/reactos/ntoskrnl/io/iomgr/dispatch.c) IoCallDriverIoSkipCurrentIrpStackLocation
cancel.c(file:///d:/reactos/ntoskrnl/io/iomgr/cancel.c) IoCancelIrp
io.c(file:///d:/reactos/ntoskrnl/io/iomgr/io.c) IopSynchronousApiTail
iotypes.h(file:///d:/reactos/sdk/include/xdk/iotypes.h) IO_STACK_LOCATIONIRP 定义
相关推荐
虾壳云官方1 小时前
openclaw 一键安装教程(2026年6月15最新)
运维·人工智能·windows·自动化·openclaw
qq_338432373 小时前
VSCode Remote-SSH 远程 Windows Server 卡死的排查与解决
windows·vscode·ssh
大佐不会说日语~3 小时前
在 Windows 本地用 Docker 部署向量模型(bge-m3)
windows·docker·容器·llm·ollama
凡人叶枫3 小时前
Effective C++ 条款37:绝不重新定义继承而来的缺省参数值
linux·c++·windows
ccino .4 小时前
【电脑的睡眠和休眠区别】
windows
AndyHuang19765 小时前
【避坑指南】Visual Studio 插件报错 “Windows Terminal (wt.exe) was not found in PATH“ 完美解决
ide·windows·visual studio
2601_961875245 小时前
花生十三资料网盘|百度云|下载
数据库·windows·git·svn·eclipse·github
caimouse5 小时前
Reactos 第 10 章 网络操作 — 10.1 概述
网络·windows
CingSyuan5 小时前
服务器现场排障:在 Windows 下使用 Linux reader 直接查看 Linux 系统 U 盘中的日志文件与文件结构
linux·运维·服务器·网络·windows