任何內核模式程序在創建一個IRP時,都同時創建了一個與之關聯的IO_STACK_LOCATION結構數組:數組中的每個堆棧單元都對應一個將處理該IRP的驅動程序,另外還有一個堆棧單元供IRP的創建者使用。堆棧單元中包含該IRP的類型代碼和參數信息以及完成函數的地址。IRP的CurrentLocation爲當前IO堆棧單元的索引,IRP的Tail.Overlay.CurrentStackLocation就是指向它的指針。CurrentLocation的最小值是1(注意:不是0)並且從上到下依次變小.每一層驅動程序都必須負責設置下一層IO堆棧的內容,這樣下一層驅動調用IoGetCurrentIrpStackLocation()時總能得到相應的數據。 Irp->StackCount的值等於IoAllocateIrp()的第一個參數,這個值不包括IRP的建立者擁有的那個堆棧。
二、跟IO堆棧操作相關的幾個宏的說明:
1、#define IoGetNextIrpStackLocation( Irp ) (\
(Irp)->Tail.Overlay.CurrentStackLocation - 1 )
上面已經說明,CurrentLocation的值從上到下依次變小,而CurrentStackLocation與它相對應。現在返回比CurrentStackLocation小1的堆棧單元,實際上就是下一個單元,也就是上面的Filter Driver對應的那個堆棧單元。
注意:這個宏不會導致原來堆棧指針的變化。
2、#define IoSetNextIrpStackLocation( Irp ) { \
(Irp)->CurrentLocation--; \
(Irp)->Tail.Overlay.CurrentStackLocation--; }
這個宏把堆棧指針指向下一個堆棧單元。一般由IoCallDriver()在內部調用這個宏,因爲調用IoCallDriver()時,下層驅動程序的派遣例程被調用,在那些派遣例程裏邊調用IoGetCurrentStackLocation()時得到的堆棧指針必須與下層驅動程序相對應,所以必須提前推進堆棧指針指向那個堆棧單元。
下面是IoCallDriver()的一個僞碼:
NTSTATUS IoCallDriver(PDEVICE_OBJECT device, PIRP Irp) { IoSetNextIrpStackLocation(Irp); PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp); stack->DeviceObject = device; ULONG fcn = stack->MajorFunction; PDRIVER_OBJECT driver = device->DriverObject; return (*driver->MajorFunction[fcn])(device, Irp); } |
3、#define IoGetCurrentIrpStackLocation( Irp ) ( (Irp)->Tail.Overlay.CurrentStackLocation )
得到當前堆棧指針。
4、#define IoCopyCurrentIrpStackLocationToNext( Irp ) { \
PIO_STACK_LOCATION irpSp; \
PIO_STACK_LOCATION nextIrpSp; \
irpSp = IoGetCurrentIrpStackLocation( (Irp) ); \
nextIrpSp = IoGetNextIrpStackLocation( (Irp) ); \
RtlCopyMemory(nextIrpSp,irpSp,FIELD_OFFSET(IO_STACK_LOCATION, CompletionRoutine)); \
nextIrpSp->Control = 0; }
這個例程把“除了完成例程以外”的其它所有IO堆棧單元數據拷貝到下一個堆棧單元,以備下層驅動程序調用。
5、#define IoSkipCurrentIrpStackLocation( Irp ) \
(Irp)->CurrentLocation++; \
(Irp)->Tail.Overlay.CurrentStackLocation++;
有些時候並不需要拷貝整個堆棧單元數據到下層堆棧,而是直接引用當前堆棧。利用這個宏就可以達到這個目的。這個宏把堆棧指針推回到上一層堆棧,回想一下上面給出的IoCallDriver()的僞碼,在裏邊調用了IoSetNextIrpStackLocation(),而那個宏把堆棧指針推向下一層堆棧,兩者中和的結果就是堆棧指針不變,這樣當下層驅動調用IoGetCurrentStackLocation()時,它得到的堆棧指針和我們得到的指針完全一樣。
三、推遲IRP地完成
如果一個驅動不能立刻完成一個IRP,則它可以返回STATUS_PENDING狀態標明它還不能完成這個IRP,並且以後在完成該IRP會調用IoCompleteRequest()來完成這個IRP。
Code1:
IoMarkIrpPending(Irp)
Return STATUS_SUCCESS;
Code2:
Irp->IoStatus.Status = <Final-Status>
Irp->IoStatus.Information = <Size>
IoCompleteRequest(Irp);
Return <Final->Status>
四、IRP完成例程
本驅動的完成例程是設置在下一層IO堆棧中的
調用IoSetCompletionRoutine(
IN PIRP Irp,
IN PIO_COMPLETION_ROUTINE CompletionRoutine,
IN PVOID Context,
IN BOOLEAN InvokeOnSuccess,
IN BOOLEAN InvokeOnError,
IN BOOLEAN InvokeOnCancel
);
#define IoSetCompletionRoutine( Irp, Routine, CompletionContext, Success, Error, Cancel ) { \
PIO_STACK_LOCATION irpSp; \
ASSERT( (Success) | (Error) | (Cancel) ? (Routine) != NULL : TRUE ); \
irpSp = IoGetNextIrpStackLocation( (Irp) ); \
irpSp->CompletionRoutine = (Routine); \
irpSp->Context = (CompletionContext); \
irpSp->Control = 0; \
if ((Success)) { irpSp->Control = SL_INVOKE_ON_SUCCESS;
} \
if ((Error)) { irpSp->Control |= SL_INVOKE_ON_ERROR; } \
if ((Cancel)) { irpSp->Control |= SL_INVOKE_ON_CANCEL; } }
可以設置一個完成例程。實際上這是一個宏,它的任務就是設置完成例程到下層驅動程序對應的IO堆棧中。當下層驅動調用IoCompleteRequest()時,這個函數將檢查下層驅動對應的堆棧裏邊是否已經設置了完成例程。如果設置了,則調用相應的完成例程。IoCompleteRequest()將重複這個過程,直到已經到達了最上層驅動或者中間某個完成例程返回了STATUS_MORE_PROCESSING_REQUIRED狀態。
完成例程一般如下:
NTSTATUS CompletionRoutine(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID context)
{
if (Irp->PendingReturned)
IoMarkIrpPending(Irp);
...
return <some status code>;
}
其中DeviceObject就是設置完成例程的當前設備對象,調用IoGetCurrentIrpStackLocation()得到的堆棧也是當前設備對象對應的堆棧。
注意前面兩行代碼,任何一個不返回STATUS_MORE_PROCESSING_REQUIRED狀態的完成例程都應該有兩行代碼,這是爲了保證IoCompleteRequest()可以一直重複調用上一層驅動的完成例程。
如果一個完成例程返回STATUS_MORE_PROCESSING_REQUIRED狀態,IoCompleteRequest()將停止繼續調用上一層驅動設置的完成例程,這時候的IRP處於一箇中間狀態,因此相應驅動的派遣例程有責任繼續完成這個IRP,如下所示:
NTSTATUS
SfDirectoryControl(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
{
PIO_STACK_LOCATION IrpStack;
IrpStack = IoGetCurrentIrpStackLocation(Irp);
//
// DO something at here
//
Irp->IoStatus.Status = Status;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return Status;
}
//---------------------------------------------------------------------------
//
// 處理IRP_MJ_DIRECTORY_CONTROL的完成例程
//
NTSTATUS
SfDirectoryControlCompletion(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PVOID Context
)
{
UNREFERENCED_PARAMETER(DeviceObject);
UNREFERENCED_PARAMETER(Irp);
//
// 不能再調用IoMarkIrpPending()函數,因爲返回值是
// STATUS_MORE_PROCESSING_REQUIRED。
//
KeSetEvent((PKEVENT)Context,IO_NO_INCREMENT,false);
return STATUS_MORE_PROCESSING_REQUIRED;
}