欢迎光临
我们一直在努力

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

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

本节剖析 IRP 请求的完成(IoCompleteRequest)与返回机制。 IRP 完成是 I/O 路径的"出口",I/O 管理器沿完成例程链 逆序 调用,把 IRP 从下层驱动 传回 上层 或 用户态。理解 IRP 完成的关键是把握 IoMarkIrpPending 与 IoCompleteRequest 的协作、完成例程链、优先级提升。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 派发时注册 完成例程,当下层完成时回到本层,可用于:

  • 检查下层返回结果
  • 修改 IRP 数据
  • 撤销本层做的准备(如释放锁、解除映射)
  • 决定 IRP 是否继续返回给上层
  • 本节内容概览

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

    学习目标

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

    涉及的内核子系统

    子系统头文件/源文件核心作用
    IRP 主处理 [ntoskrnl/io/iomgr/irp.c](file:///d:/reactos/ntoskrnl/io/iomgr/irp.c) IoCompleteRequest、IoMarkIrpPending
    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完成机制需要解决三个核心问题:

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

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

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

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

    完成例程链的形成与遍历

    栈单元的分配与使用

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

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

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

    IoSetCompletionRoutine(Irp, MyCompletion, Context, TRUE, TRUE, TRUE);
    IoCallDriver(LowerDevice, Irp);

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

    完成例程的调用条件

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

    // 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的最终状态和这些标志决定是否调用完成例程:

    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锁定等)。

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

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

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

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

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

    if (Status == STATUS_MORE_PROCESSING_REQUIRED) return;

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

    • 最终调用IoCompleteRequest或IoFreeIrp释放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传递优先级提升:

    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中有专门的处理:

    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_IO和IRP_CLOSE_OPERATION标志识别:

    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中对重解析有特殊处理:

    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对其进行了多项优化:

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

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

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

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

  • 与Windows系统的差异

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

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

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

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

  • 调试IRP完成问题

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

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

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

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

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

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

  • 调试技巧:

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

    9.14.1 IoCompleteRequest 流程

    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):

    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);
    }

    关键步骤

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

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

    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

    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 之前都设置自己的完成例程:

    // 顶层 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,调用 IoCompleteRequest,IoCompleteRequest 沿栈 逆序 调用完成例程:

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

    返回 STATUS_MORE_PROCESSING_REQUIRED

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

    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 IoMarkIrpPending 与 STATUS_PENDING

    IoMarkIrpPending

    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 的条件:

  • 调用了 IoMarkIrpPending
  • IRP 排队到设备队列或传给下层等待完成
  • 重要规则:

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

    同步派发例程

    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; // 异步
    }

    异步派发例程

    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 详细行为

    IoSetCompletionRoutine(Irp, CompletionRoutine, Context,
    InvokeOnSuccess, InvokeOnError, InvokeOnCancel);

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

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

    常见模式

    // 模式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

    VOID IoStartPacket(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp,
    IN PULONG Key,
    IN PDRIVER_CANCEL CancelFunction
    );

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

  • 如果 CurrentIrp == NULL,立即启动
  • 否则插入队列
  • IoStartNextPacket

    VOID IoStartNextPacket(IN PDEVICE_OBJECT DeviceObject, IN BOOLEAN Cancelable);

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

    StartIO 模式典型流程

    // 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

    VOID IoFreeIrp(IN PIRP Irp);

    释放 IRP 包含的所有资源。

    资源释放顺序

    IoCompleteRequest 在栈空时自动释放:

  • 同步事件:KeSetEvent 唤醒等待者
  • 用户缓冲区解锁(IRP_UNLOCK_USER_BUFFER)
  • MDL 释放
  • 系统缓冲释放(DO_BUFFERED_IO)
  • Auxiliary buffer 释放
  • APC 入队(用户态完成通知)
  • IRP 释放
  • 驱动的责任

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

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

    PIRP Irp = IoAllocateIrp(StackSize, FALSE);
    // … 使用 IRP …
    IoFreeIrp(Irp); // 自己分配自己释放

    MasterIrp

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

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

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


    9.14.7 同步与异步完成

    同步完成(STATUS_SUCCESS 直接返回)

    // 派发例程中直接完成
    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;
    }

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

    // 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;

    下层完成时回到本层

    // 完成例程
    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 总结

    关键要点:

  • IoCompleteRequest 沿栈逆序调用完成例程:CurrentLocation 从 1 到 StackCount
  • 完成例程链:每层驱动通过 IoSetCompletionRoutine 注册
  • STATUS_MORE_PROCESSING_REQUIRED:停止完成链,驱动负责清理
  • IoMarkIrpPending:标记 IRP 异步,调用方返回 STATUS_PENDING
  • IoSetCompletionRoutine:InvokeOnSuccess/Error/Cancel 控制调用条件
  • IoStartPacket / IoStartNextPacket:StartIO 模型
  • IoFreeIrp:释放 IRP 拥有的资源
  • 下一章(第 10 章)将进入网络操作领域。


    本章代码索引

    文件内容
    [irp.c](file:///d:/reactos/ntoskrnl/io/iomgr/irp.c) IoCompleteRequest、IoMarkIrpPending、IoFreeIrp
    [dispatch.c](file:///d:/reactos/ntoskrnl/io/iomgr/dispatch.c) IoCallDriver、IoSkipCurrentIrpStackLocation
    [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_LOCATION、IRP 定义
    赞(0)
    未经允许不得转载:171主机测评 » Reactos 第 9 章 设备驱动 — 9.14 IRP请求的完成与返回
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址