IRP的简单完成例程!

此帖为转帖!

1.最高层驱动总是运行在发起该请求的程序所处的线程中。DriverEntry总是处在系统线程中,系统线程的空间不涉及到线性地址 0 - 2G。APC 是处在任意上下文中,它所在的线程取决于系统运行APC之前最后被挂起的那个线程。
2. 所有不返回 STATUS_MORE_PROCESSING_REQUIRED的完成回调例程,需要使用下面的代码:
NTSTATUS MyCompletionRoutine(PDEVICE_OBJECT DevObj, PIRP Irp, VOID Context)
{
 if (Irp->PendingRetured)
 {
  IoMarkIrpPending(Irp);
 }
 < your processing code ... >
 return STATUS_SUCCESS;
}
原因在于IRP的发起者通常希望同等的等待该IRP的完成,所以在构造IRP的时候有以下的逻辑:

KEVENT     event;
IO_STATUS_BLOCK  iosb;
KeInitializeEvent(&event, ....);
PIRP  Irp = IoBuildDeviceIoControlRequest(..., &event, &iosb);
NTSTATUS    status = IoCallDriver(someDeviceObject, Irp);
if (status == STATUS_PENDING)
{
 KeWaitForSingleObject(&event, ...);
 status = iosb.Status;
}

/
宏: IoMarkIrpPending(Irp)  (/
IoGetCurrentIrpStackLocation((Irp))->Control |= SL_PENDING_RETURND)
这儿会等待一个事件,该事件是在完成例程派遣的一个APC里面设置的。派遣该APC的一个测试条件是:最高层栈有一个SL_PENDING_RETURED的标志,其实就是 Irp->Control 这个域。在底层,某个驱动调用 IoMarkIrpPending(Irp),然后返回了 STATUS_PENDING, 那么就会出现上层在等待一个事件的情况。某一时刻,IoCompleteIrp会被调用,它会把Irp->PendingReturned 的值设置为 PIO_STACK_LOCATION->Control 中的值。所有依次被调用的完成回调例都要这么做,才能把该SL_PENDING_RETURED标志传到最上层的设备栈,然后IO管理才派遣一个APC来做这个事情。 使用APC的原因在于,可以使得IoCompleteRequest的调用可以发生在任意上下文中。而IoCompleteRequest必须能发生在任意上下文中,因为对于费时的操作,StartIo会启动设备,然后等待设备完成传输后的中断,在中断中,调用ISR,ISR调用DPC,这时候必然处在任意上下文中,而我们知道,通常会在DPC中完成IRP,所以要求IoCompleteRequest 发生在任意上下文中。
 要注意的是, 对于某些IRP,如果上层设备栈的 SL_PENDING_RETURNED 被置位,那么IoCompleterRequest会试图派遣一个APC去完成IRP。但是,如果IRP的发起者通过IoCallDriver得到的不是STATUS_PENDING 的状态,那么在IO管理器会在构造IRP的地方就把它清理掉。所以,返回STATUS_PENDING 和 IoMarkIrpPending 要同时调用,不要只调用它们中的一个。
 以下四种试图避开该调用的操作是不可取的。
Ø 在Dispatch 例程中有条件的调用IoMarkIrpPending
NTSTATUS TopDriverDispatchSomething(PDEVICE_OBJECT fidp, PIRP irp)
{
 PDEVICE_EXTENSION pdx =
 (PDEVICE_EXTENSION)fido->DevcieExtension;
 IoCopyCurrentIrpStackLocationToNext(irp);
 IoSetCompletionRoutine(irp, TopDriverCompletionRoutine, ...);
 NTSTATUS status = IoCallDriver(pdx->LowerDevice, irp);
 if (STATUS_PENDING == status)
 {
  IoMarkIrpPending(Irp); // 此时, IRP,有可能已经完成了,这里会破坏内存;
 }      // 在驱动中,调用了IoCallDriver后,就不要碰 IRP 了
 return status;
}
Ø 总在 Dispatch 例程中调用 IoMarkIrpPending(...)
NTSTATUS TopDrvierDispatchSomething(PDEVICE_OBJECT fido, PIRP irp)
{
 PDEVICE_EXTENSION pdx = fido->DeviceExtension;
 IoMarkIrpPending(irp);
 IoCopyCurrrentIrpStackLocationToNext(irp);
 IoSetCompletionRoutine(irp, TopDriverCompleteRoutine, ...);
 return IoCallDriver(pdx->LowrDevice, irp);
}
这里的问题在于,如果底层驱动返回的不是 STATUS_PENDING, 因为调用了IoMarkIrpPending ,所以IoCompleteRequest 会派遣APC完成该IRP,但是由于返回的不是STATUS_PENDING, I/O 管理器在构造IRP的地方也可能完成它,所以会出现两次完成的情况。
Ø 在完成回调例程中总是调用 IoMarkIrpPending
NTSTATUS TopDevierDeispatchSomething(PDEVCIE_OBJECT fido, PIRP irp)
{
 PDEVICE_EXTENSION pdx = fido->DeviceExtenson;
 KEVENT     event;
 KeInitializeEvent(&event, NotificaionEvent, FALSE);
 IoCopyCurrentIrpStackLocationToNext(irp);
 IoSetCompletionRoutine(irp, TopDriverCompletionRoutine, &event, TRUE,TRUE,TRUE);
 IoCallDriver(pdx->LowerDeviceObject, irp);
 KeWaitForSingleObject(&event, ...);
 Irp->IoStatus.Status = status;
 IoCompleteRequest(irp, IO_NO_INCREMENT);
 return status;
}
NTSTATUS TopDriverCompletionRoutine(PDEVICE_OBJECT fido, PIRP irp, PVOID pev)
{
 if (Irp->PendingReturned)
{
 IoMarkIrpPending(irp);
}
KeSetEvent((PKEVENT)pev, IO_NO_CREMENT, FALSE);
return STATUS_MORE_PROCESSING_REQUIRED;
}
这里意图将IRP同步的向下层驱动传递,等到下层驱动处理完毕后,我们再处理它。但是这里的问题在于,如果下层驱动返回了STATUS_PENDING , 那么会造成在IRP栈中SL_PENDING_RETUREND 置位,并且dispatch 例程不返回 STATUS_PENDING 的状态,会发生同上面一样的错误。
下面的code也会有问题, 一个驱动构造了一个异步的IRP以帮助自己完成某些工作,然后提供了一个完成例程以释放这个IRP.
SOMETYPE SomeFunction()
{
 PIRP irp = IoBuildAsynchronousFsdRequest(...);
 IoSetCompleteRoutine(irp, MyCompleteRoutine, ...);
 IoCallDriver(...);
}
NTSTATUS MyCompletionRoutine(PDEVCIE_OBJECT junk, PIRP irp, PVOID context)
{
 if (Irp->PendingReturned)
 {
  IoMarkIrpPending(irp);
 }
 IoFreeIrp(irp);
 return STATUS_MORE_PROCESISING_REQUIRED;
}
这里的问题在于,由于这里已经没有与之对应的IRP栈,所以IoMarkIrpPending设置的标志会破坏别的内存。永远记住,在返回STATUS_MORE_PROCESSING_REQUIRED的完成回调例程中,不要调用 IoMarkIrpPending 就好了。
Ø 总是返回 STATUS_PENDING.
NTSTATUS TopDriverDispatchSomething(PDEVCIE_OBJECT fido, PIRP irp)
{
 PDEVICE_EXTENSION pdx = fido->DeviceExtension;
 IoMarkIrpPending(irp);
 IoCopyCurrentIrpStackLocationToNext(irp);
 IoSetCompleteionRoutine(irp, TopDriverCompletionRoutine, ...);
 return STATUS_PENDING
}
NTSTATUS TopDriverCompletionRoutine(PDEVICE_OBJECT fido, PIRP irp)
{
 ....
 return STATUS_SUCCESS;
}
这里没有什么大问题,但是效率不高。

引自: http://liftstring.blog.sohu.com/82618353.html


 关于完成例程(Completion Routine)

通常使用完成例程有这么三步:
    (1) 调用IoCopyCurrentIrpStackLocationToNext(...)函数,把当前的IRP栈数据复制一份到下一层。
    (2) 调用IoSetCompletionRoutine(...)函数,为IRP设置完成例程。
    (3) 调用IoCallDriver(...)函数,把IRP传递到下一层驱动对象。


    DDK对使用完成例程还有如下要求:
    (1) 与'IRQL'相关的要求(因为完成例程有肯能运行在DISPATCH_LEVEL级,所有对IRQL有要求):
        ① 对于要求运行在低IRQL上的部分内核函数(如IoDeleteDevice、ObQueryNameString), 完成例程无法很安全的调用它们。
        ② 完成例程使用的数据结构必须从非分页内存中分配。DDK上关于前面提到的“数据结构”讲述很模糊,像是完成例程的context参数。不过我觉得,除了在栈上分配的空间外,完成例程中所有的内存分配都应该在非分页堆中分配。
        ③ 完成例程不能放在分页内存中。
        ④ 完成例程不能'申请资源(acquire resources)'、'mutexts'、'fast mutexes'。但是可以申请自旋锁。(我没有理解'申请资源'具体指的是什么)


    (2) 检查PendingReturned标志(这一方面,DDK上说的很清楚,就不画蛇添足翻译了。个人感觉MS在MSDN上真是费了不少心血,绝大部分的句子都是用的最基本的语法,而且描述的很清楚)
        ① Unless the completion routine signals an event, it must check the Irp->PendingReturned flag. If this flag is set, the completion routine must call IoMarkIrpPending to mark the IRP as pending.
        ② If a completion routine signals an event, it should not call IoMarkIrpPending.


    (3) 返回值:
        过滤驱动的完成例程只能有两种返回值'STATUS_SUCCESS',或者'STATUS_MORE_PROCESSING_REQUIRED'。其他任何的返回值都会被IO manager重置为'STATUS_SUCCESS'。


    (4) 把IRP包放到work queue中应注意的:
        先调用IoMarkIrpPending(...),再把IRP包放到queue中。如果顺序反了,则在两者调用的之间的那段时间,该IRP包可能已经被其他驱动处理函数从queue中拿出,处理完毕,并且释放掉了该IRP包。这种情况下,可能导致系统崩溃。
    还有一些关于完成例程在哪些情况下,应该返回'STATUS_MORE_PROCESSING_REQUIRED'。这里就不罗嗦了,具体看DDK中“Constraints on Completion Routines”这一节。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值