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;
}
這裏沒有什麼大問題,但是效率不高。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章