SCIENCE PARK

デバドラ講座

【 デバドラ講座25 : IRPの構造、処理、操作関数 】

1.IRPの構造

IRP(I/O Request Packet)はアプリケーションからI/O要求が発生した際に、I/Oマネージャが作成する構造体です。IRPは適当なドライバのディスパッチルーチンを呼び出す時に、引数として渡されます。IRP構造体は図1のようにヘッダとスタックロケーションの二つの部分が存在します。スタックロケーションの数は、I/O要求に関わるドライバの階層と関係しています。例えば、上位、中間、物理の3つのドライバを介して一つのI/O要求を処理する場合、3つのスタックロケーションが存在します。

図1:IRP構造体
図1:IRP構造体

IRPはオブジェクトではなく、データのパケットで、その構造体は非常に複雑な形をしています。IRPは非ページプールメモリに作成されます。IRPのヘッダの値、大きさはドライバの階層に関係なく固定しています。次にIRPの簡略した構造体と、よく使うフィールドについて示します。

typedef struct _IRP{
              PMDL MdlAddress;
              ULONG Flags;
              union{
                            struct _IRP *MasterIrp;
                            PVOID SystemBuffer;
                            }AssocitatedIrp;
              IO_STATUS_BLOCK IoStatus;
              KPROCESSOR_MODE RequestorMode;
              BOOLEAN Cancel;
              KIRQL CancelIrql;
              PDRIVER_CANCEL CnacelRoutine;
              uion{
  struct{
        union{
                      KDEVICE_QUERY_ENTRY DeviceQueueEntry;
                                  Struct{
 PVOID DriverContext[4];
                                   };
                             };
                             PETHRED Thread;
                             LIST_ENTRY ListEntry;
                }Overlay;
              }Tail;
}IRP, *PIRP;
MdlAddress:
ドライバがダイレクトI/Oを使用する際に要求元のバッファを表すMDLを指します。ダイレクトI/OとMDLについてはレポート26で説明します。
Flags:
フラグを示します。このフィールドは一般にファイルシステムにのみ関係します。
AssociatedIrp.MasterIrp:
アソシエイトIRPに関連付けられたマスターIRPへのポインタです。このフィールドはファイルシステムのような最上階層のドライバにのみ関連します。アソシエイトIRPとマスターIRPについて、説明は省略します。
AssociatedIrp.SystemBuffer:
ドライバがバッファードI/Oを使用する際に非ページプールメモリにある中間バッファへのポインタを示します。バッファードI/Oについてはレポート26で説明します。
IoStatus:
I/Oステータスブロックです。IRPが完了するとその完了状態がIoStatus.Statusに設定されます。またIoStatus.Informationに読み書きされたバイト長が保持されます。
RequestorMode:
I/O要求元の実行モード(ユーザーモードかカーネルモード)を示します。
Cancel,CancelIrql,CancelRoutine:
実行中のIRP処理を取り消す際に使用します。
UserBuffer:
ドライバがニーザーI/Oを使用する際にデータバッファの仮想アドレスを入れます。
Tail.Overlay.DeviceQueueEntry:
システムキューを使うドライバのIRPキュー処理のために使用します。詳細について説明は省略します。
Tail.Overlay.Thread:
要求元のスレッド制御ブロックへのポインタです。詳細について説明は省略します。
Tail.Overlay.ListEntry:
ドライバがIRPを所有している間、このフィールドは次のIRPへのリンクに使います。詳細について説明は省略します。

IRPのスタックロケーションは、階層ドライバのそれぞれのドライバに対応しています。スタックロケーションはIO_STACK_LOCATION構造体により定義されています。次にスタックロケーションの構造体の一部を示します。

typedef struct _IO_STACK_LOCATION {
  UCHAR  MajorFunction;
  UCHAR  MinorFunction;
  UCHAR  Flags;
  UCHAR  Control;
  union {
               //IRP_MJ_CREATEのパラメータ
        struct {
            PIO_SECURITY_CONTEXT SecurityContext;
            ULONG Options;
            USHORT POINTER_ALIGNMENT FileAttributes;
            USHORT ShareAccess;
            ULONG POINTER_ALIGNMENT EaLength;
        } Create;
               //IRP_MN_START_DEVICEのパラメータ
        struct {
            PCM_RESOURCE_LIST AllocatedResources;
            PCM_RESOURCE_LIST AllocatedResourcesTranslated;
        } StartDevice;
  } Parameters;
  PDEVICE_OBJECT  DeviceObject;
  PFILE_OBJECT  FileObject;
} IO_STACK_LOCATION, *PIO_STACK_LOCATION;
MajorFunction:
I/O要求に関連したIRP_MJ_READ, IRP_MJ_WRITEなどのメジャーI/O機能コードを示します。
MinorFunction:
I/O要求に関連したマイナーI/O機能コードを示します。Window 2000/XPドライバのPnP機能と電源管理によく使用されます。
Flag:
I/O操作に特有の実行フラグを示します。このフラグは主にファイルシステムと関係します。
Control:
I/Oマネージャによって設定、参照されるフラグ群を表します。特定のIRPをどのように処理する必要があるか示しています。開発者がこのフラグを設定することはほとんどありません。
Parameters:
このフィールドはメジャーI/Oコードによって定義されている共用体です。
DeviceObject:
I/O要求の対象となるデバイスオブジェクトへのポインタです。
FileObject:
I/O要求に関連するファイルオブジェクトへのポインタです。

IRPのヘッダとスタックロケーションの構造を図2に示します。

図2:RPのヘッダとスタックロケーションの構造
図2:RPのヘッダとスタックロケーションの構造

2.IRPの処理

IRPがどのように処理されるか図3に簡単に示します。

図3.IRPの処理
図3.IRPの処理
  1. アプリケーションがI/O要求を出します。
  2. I/OマネージャはI/O要求を受けると、IRPを非ページプールメモリに割り当てます。また、IRPのヘッダと最初のスタックロケーションを初期化します。
  3. I/Oマネージャは適切なドライバのディスパッチルーチンを呼び出します。また、I/OマネージャはディスパッチルーチンにIRPのポインタを渡します。
  4. ディスパッチルーチンはIRPをチェックし、HALとカーネルを介してデバイスにアクセスします。
  5. ディスパッチルーチンはIRPにステータスを設定し、制御をI/Oマネージャに返します。I/OマネージャはIRPを解放し、完了コードを取得してアプリケーションに返します。
  6. デバイスから割り込みが発生したとします。割り込みが発生すると、HALを介して割り込みサービスルーチン(ISR)が呼ばれます。
  7. ISRはDPCを呼び出します。このとき、I/OマネージャによってIRPが作成されます。
  8. DPCはIRPにステータスを設定し、制御をI/Oマネージャに返します。I/OマネージャはIRPを解放し、完了コードを取得してアプリケーションに返します。

階層ドライバにおいて、I/OマネージャはユーザーモードからI/O要求を受けると、ドライバと同じ数のスタックロケーションを非ページプールメモリに確保します。そして、I/Oマネージャはヘッダと最初のスタックロケーションのみを初期化します。階層ドライバがどのようにIRP処理を行うか図4に示します。

図4.階層ドライバのIRP処理
図4.階層ドライバのIRP処理

階層ドライバにおいて、IRPはスタックを下方向に通過する際に各ドライバによって処理されます。また、スタックを上方向に戻る際にも、各ドライバによって処理されます。図4において、Driver1はDriver2のドライバのためにスタックロケーションを初期化します。ドライバは自分自身のスタックロケーションとその直下のスタックロケーションにのみ安全にアクセスすることが可能です。

IRPが上位のドライバに渡されたとき、ドライバは現在のスタックロケーションへのポインタを取り出し、IRPのパラメータを調べます。その際、上位ドライバは次の3つの処理を選択して実行することができます。

1. このドライバ自身で処理する
このドライバでこのIRPの処理を完了できる場合、直ちにこの処理を行います。キューを使用するドライバの場合、このIRPをキューに入れ、後で処理を行います。
2. IRPを低い層のドライバへ受け渡す
このドライバで処理を行うと同時に、下層のドライバにIRPを受け渡すことができます。このドライバ自身は全く処理を行わず下層のドライバに渡すことも可能です。IRPを下層に渡すには、IRPの次のスタックロケーションを設定する必要があります。
3. 新しくIRPを作成し、低い層のドライバに受け渡す
ドライバは新たにIRPを作成することができます。IRPを作成後、スタックロケーションを含むすべてのフィールドは0に設定されます。また、IRPを下層ドライバに渡すために、次のスタックロケーションを設定する必要があります。

IRPにアクセスするための関数

IRPは非ページプールメモリに割り当てられます。ドライバは安全にIRPのフィールドにアクセスするためにDDKに用意された関数を使用しなければなりません。次にIRPにアクセスするための関数を示します。

IoGetCurrentIrpStackLocation

この関数はスタックロケーションのポインタを取得します。

PIO_STACK_LOCATION IoGetCurrentIrpStackLocation
(IN PIRP Irp);     //現在のIRPのポインタ
IoCallDriver

この関数は次の階層ドライバにIRPを渡します。

NTSTATUS IoCallDriver ( IN PDEVICE_OBJECT DeviceObject, //デバイスオブジェクト IN OUT PIRP Irp); //次に低いドライバに渡すIRPのポインタ
IoSkipCurrentIrpStackLocation

この関数は現在のスタックロケーションの内容を次のスタックロケーションにコピーします。この関数では、完了ルーチンを設定できません。

VOID IoSkipCurrentIrpStackLocation(IN PIRP Irp);             /現在のIRPのポインタ
IoCopyCurrentIrpStackLocationToNext

この関数は現在のスタックロケーションの内容を次のスタックロケーションにコピーします。この関数では、完了ルーチンを設定できます。

VOID IoCopyCurrentIrpStackLocationToNext (
IN PIRP Irp);                    //現在のIRPのポインタ
IoCompleteRequest

この関数はIRPの完了をI/Oマネージャに通知します。

VOID IoCompleteRequest(
IN PIRP Irp,                                   //現在のIRPのポインタ
IN CCHAR PriorityBoost);              //一時的にスレッドの優先順位を引き上げる
IoSetCompletionRoutine

この関数は完了ルーチンを設定します。

VOID IoSetCompletionRoutine(
IN PIRP Irp,                                   //IRP処理完了時、 IRPへのポインタ
IN PIO_COMPLETION_ROUTINE CompletionRoutine,
//IRPが完了した時に呼ばれる関数のポインタ
IN PVOID Context,                        // CompletionRoutineに渡される引数のポインタ
IN BOOLEAN InvokeOnSuccess,
BOOLEAN InvokeOnError,
IN BOOLEAN InvokeOnCancel);
IoMarkIrpPending

この関数は現在のIRPをペンディングさせます。

VOID IoMarkIrpPending (
IN OUT PIRP Irp);           //ペンディングしようとするIRPのポインタ
IoGetNextIrpStackLocation

この関数は次に低い階層のIRPスタックロケーションのポインタを取得します。

PIO_STACK_LOCATION IoGetNextIrpStackLocation (
IN PIRP Irp);      //次に低い階層IRPのポインタ
IoSetNextIrpStackLocation

この関数は次に低い階層のIRPスタックロケーションにアクセス可能にします。

            VOID IoSetNextIrpStackLocation(
            IN OUT PIRP Irp);           //次に低い階層IRPのポインタ 
IoStartPacket、IoStartNextPacket
この関数は、キューにある次のIRPを開始します。プロトコルについては省略します。
IoAllocateIrp、IoFreeIrp
IoAllocateIrp関数は新たにIRPを作成します。IoFreeIrp関数は作成したIRPを削除します。ドライバ内でIRPを作成した場合、ドライバでIRPを削除する必要があります。
IoBuildAsynchronousFsdRequest、IoBuildDeviceIoControlRequest、IoBuildSynchronousFsdRequest
これらの関数は、特別なケースのIRPの割り当てと初期化を行います。

参考資料

「NTドライバプログラミング」
Peter G. Viscarola、W. Anthony Mason著 久保雅俊、三浦秀朗訳 サイエンスパーク株式会社 監修
「Windows NTデバイスドライバプログラミング」
アート・ベーカー著 WinProgDDK ML和訳プロジェクト訳
「WDMデバイスドライバプログラミング完全ガイド」上
Edword N. Dekker, Jpseph M. Newcomer著 株式会社クイック訳
「WDMデバイスドライバ」
Chris Cant著 エクストランス株式会社訳 サイエンスパーク株式会社 監修
page up