irp概述

一、簡述
任何內核模式程序在創建一個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;

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章