
当IRP从驱动A传到底层驱动E后,在E里完成IRP[IoCompleteRequest]后,IRP会从底层E向A返回,然后再回到应用程序。 若是在一个IO堆栈中设置了完成例程[调用IoCallDriver之前设置],那么当IRP完成之后会一层一层向上返回,每到一层会检查到IOSTACKLOCATION里的CompletionRoutine这个子域,若是非空说明设置完成例程,那么IRP在这层时会调用这个函数。传进这个完成例程的子域是Context。 当设置完成例程后,调用IoCallDriver之前就不能用IoSkipCurrentIrpStackLocation把当前IO堆栈传到下一个驱动中,要用IoCopyCurrentIrpStackLocationToNext复制当前的IO栈到下一层驱动中。 当IRP在某个设备栈中给完成的之后会一层层向上弹出,若遇到有完成例程则调用,完成例程可以返回两种状态: 1、STATUS_SUCCESS 与 STATUS_CONTINUE_COMPLETION这两个是一样的,当返回这种时候表示驱动不能再得到IRP的控制。 2、STATUS_MORE_PROCESSING_REQUIRED若是返回这个状态,则本层的设备堆栈会重新得到IRP控制权,而且设备栈不会再向上弹出,这时可以重新将IRP发向底层或者调用CompleteRequest结束IRP。
设置完成例程的宏 VOID IoSetCompletionRoutine( IN PIRP Irp,//表示要设置的IRP IN PIO_COMPLETION_ROUTINE CompletionRoutine,//完成例程,传入NULL表示取消 IN PVOID Context,//传给完成例程的参数 IN BOOLEAN InvokeOnSuccess, IN BOOLEAN InvokeOnError, IN BOOLEAN InvokeOnCancel ); 完成例程格式 NTSTATUS MyCompletionRoutine(IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIrp, IN PVOID Context) { .. return ..; }
Return Value IoCallDriver returns the NTSTATUS value that a lower driver set in the I/O status block for the given request or STATUS_PENDING if the request was queued for additional processing.
例子:
NTSTATUS IrpReadRoutine(IN PDEVICE_OBJECT pDeviceObject,IN PIRP pIrp) { NTSTATUS status; PDEVICE_EXTEN_MY pDeviceMy; KdPrint(("Entry DriverBBBBB Irp Read/n")); pDeviceMy=(PDEVICE_EXTEN_MY)pDeviceObject->DeviceExtension; //将当前IRP堆栈拷贝底层堆栈 不要用IoSkipCurrentIrpStackLocation (pIrp); IoCopyCurrentIrpStackLocationToNext(pIrp); //设置完成例程 IoSetCompletionRoutine(pIrp,MyCompletionRoutine,NULL,TRUE,TRUE,TRUE); //调用底层驱动 status = IoCallDriver(pDeviceMy->pTargetDeviceObject, pIrp); KdPrint(("Leave DriverBBBBB Irp Read/n")); return status; }
NTSTATUS MyCompletionRoutine(IN PDEVICE_OBJECT pCRDeviceObject,IN PIRP pCRIrp,IN PVOID Context) { KdPrint(("Enter MyIoCompletionRoutine/n")); if (pCRIrp->PendingReturned) { IoMarkIrpPending( pCRIrp ); //如果驱动设置了一个完成例程然后向下层驱动传递IRP,它的完成例程应该检测IRP->PendingReturned标志 //域(Flag)。如果这个子域被设置成TRUE,完成例程就必须调用IoMarkIrpPending挂起该IRP。 //完成例程不返回STATUS_PENDING KdPrint(("Pending/n"));
} return STATUS_SUCCESS;//同STATUS_CONTINUE_COMPLETION } if (pCRIrp->PendingReturned) 这句要是底层驱动没有立刻调用IoCompleteRequst时,返回STATUS_PENDING时,那么 IoCallDriver的返回值也是STATUS_PENDING,若是再在设置完成例程的IRP里设置了返回值也是STATUS_PENDING,那么当底层[比如调用了定时器,过多少秒之后]调用了IoCompleteRequst结束了IRP,当结束IRP之后,会从底层向上弹出,弹到有设置完成例程的地方会进入完成例程,这时pCRIrp->PendingReturned这个就会置位,在完成例程中这个置位,要调用IoMarkIrpPending( pCRIrp );,但是完成例程的返回值不能是STATUS_PENDING,只能是上面说的两种状态[完成例程可以返回两种状态:]。[这种就是12章的test3例子的情况。]
这里给出的代码:是指底层的IRP分发例程是当上层把IRP转发一到就调用IoCompleteRequest( pIrp, IO_NO_INCREMENT );结束IRP请求,因为IoCallDriver是是同步的,即当底层IoCompleteRequest后,底层的返回值是STATUS_SUCCESS,这时IoCallDriver立刻返回,当IRP一完成,会从下往上弹出,这时就进入完成例程,在完成例程里返回STATUS_SUCCESS表示只是一个通知而已 如下面的log: 00000004 8:47:54 Entry DriverBBBBB Irp Read 进入顶层 调用iocalldriver 00000005 8:47:54 Entry DriverAAAAA Irp Read 进入底层分发 00000006 8:47:54 sum is value 49995000 ...执行这个后再调用iocompleterequest 00000007 8:47:54 Enter MyIoCompletionRoutine 结束之后会从底向上弹出,这时弹到顶层时检查到了设有完成例程,所以进入完成例程,这里因为底层返回的不是peding所以pCRIrp->PendingReturned没有置位,所以不会调用IoMarkIrpPending( pCRIrp );然后顶层分发例程返回status,当顶层结束时会先把底层的先结束。 00000008 8:47:54 Leave DriverAAAAA Irp Read 00000009 8:47:54 Leave DriverBBBBB Irp Read
底层的分发例程代码: NTSTATUS IrpReadRoutine(IN PDEVICE_OBJECT pDeviceObject,IN PIRP pIrp) { NTSTATUS status; int i,sum; KdPrint(("Entry DriverAAAAA Irp Read/n")); sum=0; for (i=0;i<10000;i++) { sum+=i; } KdPrint(("sum is value %d/n",sum)); status=STATUS_SUCCESS; pIrp->IoStatus.Status=status; pIrp->IoStatus.Information=0; IoCompleteRequest( pIrp, IO_NO_INCREMENT ); KdPrint(("Leave DriverAAAAA Irp Read/n")); return status; }
|
上面的例子完成例程返回的是STATUS_SUCCESS的,还有一种是返回STATUS_MORE_PROCESSING_REQUIRED,返回这个会使完成的IRP又变成未完成状态,这种情况可以使得设有完成例程的设备栈可以重新得到IRP的控制权。可以重新发送到下面或者自己结束IRP。[详见12章]
代码:
NTSTATUS IrpReadRoutine(IN PDEVICE_OBJECT pDeviceObject,IN PIRP pIrp)
{
NTSTATUS status;
PDEVICE_EXTEN_MY pDeviceMy;
KEVENT event;
KdPrint(("Entry DriverBBBBB Irp Read/n"));
pDeviceMy=(PDEVICE_EXTEN_MY)pDeviceObject->DeviceExtension;
KeInitializeEvent(&event,NotificationEvent,FALSE);
//将当前IRP堆栈拷贝底层堆栈
IoCopyCurrentIrpStackLocationToNext(pIrp);
//设置完成例程
IoSetCompletionRoutine(pIrp,MyCompletionRoutine,&event,TRUE,TRUE,TRUE);
//调用底层驱动
status = IoCallDriver(pDeviceMy->pTargetDeviceObject, pIrp);
KdPrint(("%08x/n",status));
KdPrint(("wait!/n"));
KeWaitForSingleObject(&event,Executive,KernelMode,FALSE,NULL);
//因为一般要挂载的话,那么底层的驱动处是IRP的例程,一定是返回成功的,不可能返回pending状态的,所以这里当返回成功时可以等待完成例程处理完了再执行下面的语句,若是完成例程要时间多点,那么这里会等,只有当事件给激活了才继续执行下面的语句。又因为完成例程返回的是STATUS_MORE_PROCESSING_REQUIRED所以IRP又变成未完成,所以要结束它,或者转发到下一层,这里选择了转发到下一层,看下面的log,转发后,它又进入到底层,再返回,但是再返回后是不会再进入完成例程的了。因为这个完成例程已设为NULL了。
// IoCompleteRequest( pIrp, IO_NO_INCREMENT );
status = IoCallDriver(pDeviceMy->pTargetDeviceObject, pIrp);
KdPrint(("Leave DriverBBBBB Irp Read/n"));
return status;
// return pIrp->IoStatus.Status;
}
NTSTATUS MyCompletionRoutine(IN PDEVICE_OBJECT pCRDeviceObject,IN PIRP pCRIrp,IN PVOID Context)
{
KdPrint(("Enter MyIoCompletionRoutine/n"));
if (pCRIrp->PendingReturned)
{
IoMarkIrpPending( pCRIrp );
KdPrint(("Pending/n"));
}
KdPrint(("Set Event/n"));
KeSetEvent((PKEVENT)Context,IO_NO_INCREMENT,FALSE);
return STATUS_MORE_PROCESSING_REQUIRED;//
}
00000004 9:42:04 Entry DriverBBBBB Irp Read
00000005 9:42:04 Entry DriverAAAAA Irp Read
00000006 9:42:04 sum is value 49995000
00000007 9:42:04 Enter MyIoCompletionRoutine
00000008 9:42:04 Set Event
00000009 9:42:04 Leave DriverAAAAA Irp Read
00000010 9:42:04 00000000
00000011 9:42:04 wait!
00000012 9:42:04 Entry DriverAAAAA Irp Read
00000013 9:42:04 sum is value 49995000
00000014 9:42:04 Leave DriverAAAAA Irp Read
00000015 9:42:04 Leave DriverBBBBB Irp Read
分层驱动总结:
1、在本层结束IRP请求,即调用IoCompleteRequest。那么IRP不会到达下层驱动中。
2、在本层转发IRP到下层。这里又分为三种
一、未设完成例程,调用IocallDriver转发到下层。
二、设置完成例程,调用IoCallDriver转发到下层,完成例程返回STATUS_SUCCESS
三、设置完成例程,调用IoCallDriver转发到下层,完成例程返回STATUS_MORE_PROCESSING_REQUIRED
http://hi.baidu.com/onepc/blog/item/fd2885dd0393fb3f5982dd8a.html
http://hi.baidu.com/onepc/blog/item/cd8095d66d8dc62406088b6d.html