此帖为转帖!
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”这一节。