第 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指针向下移动(递减),指向当前设备的栈单元:
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的三个布尔参数(InvokeOnSuccess、InvokeOnError、InvokeOnCancel)决定了完成例程在何种情况下被调用。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锁定等)。
这个机制的典型应用场景包括:
-
IRP重用:过滤驱动可能需要在完成路径中修改IRP参数并重新下发。例如,一个加密过滤驱动在读取完成后,解密数据,然后重新发起一个写操作。
-
异步完成 :某些驱动需要在完成例程中执行异步操作(如排队到工作线程),此时返回
STATUS_MORE_PROCESSING_REQUIRED可以阻止IRP被立即释放,驱动在异步操作完成后手动调用IoCompleteRequest。 -
分片I/O :在处理大型I/O请求时,驱动可能将其拆分为多个子IRP。主IRP的完成例程返回
STATUS_MORE_PROCESSING_REQUIRED,等待所有子IRP完成后再统一处理。
ReactOS代码中对STATUS_MORE_PROCESSING_REQUIRED的处理非常严格:
c
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传递优先级提升:
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_IO和IRP_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对其进行了多项优化:
-
快速路径 :如果没有完成例程(大多数简单驱动不设置完成例程),
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 流程
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);
}
关键步骤
- 正序遍历栈单元 :
CurrentLocation从 1 到StackCount - 每个栈单元 :调用
CompletionRoutine(如果有) - STATUS_MORE_PROCESSING_REQUIRED:停止完成链,IRP 留待驱动自行处理
- 栈空后:清理资源、解锁、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,调用 IoCompleteRequest,IoCompleteRequest 沿栈 逆序 调用完成例程:
PDO 完成
↓ IoCompleteRequest
调用 FDO 的完成例程 (InvokeOnSuccess = TRUE 时)
↓ 返回 STATUS_SUCCESS
调用 Top 的完成例程
↓ 返回 STATUS_SUCCESS
全部完成 -> IoFreeIrp
返回 STATUS_MORE_PROCESSING_REQUIRED
如果完成例程返回 STATUS_MORE_PROCESSING_REQUIRED,IoCompleteRequest 停止 调用后续的完成例程,由当前例程自行处理 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 IoMarkIrpPending 与 STATUS_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 的条件:
- 调用了
IoMarkIrpPending - 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,然后:
- 如果
CurrentIrp == NULL,立即启动 - 否则插入队列
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 在栈空时自动释放:
- 同步事件:
KeSetEvent唤醒等待者 - 用户缓冲区解锁(
IRP_UNLOCK_USER_BUFFER) - MDL 释放
- 系统缓冲释放(
DO_BUFFERED_IO) - Auxiliary buffer 释放
- APC 入队(用户态完成通知)
- 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 总结
关键要点:
IoCompleteRequest沿栈逆序调用完成例程 :CurrentLocation从 1 到StackCount- 完成例程链 :每层驱动通过
IoSetCompletionRoutine注册 STATUS_MORE_PROCESSING_REQUIRED:停止完成链,驱动负责清理IoMarkIrpPending:标记 IRP 异步,调用方返回STATUS_PENDINGIoSetCompletionRoutine: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 定义 |