SCIENCE PARK

デバドラ講座

【 デバドラ講座27 : IOコントロールコード(IOCTL) 】

DeviceIoControlはユーザーモードアプリケーションがデバイスを制御するために使うWinAPI関数です。ReadFile、WriteFile関数は単純なデータの読み、書きのみを行います。一方、DeviceIoControl関数は比較的デバイスを自由に取り扱う事ができます。DeviceIoControlのプロトコルを次に示します。

BOOL DeviceIoControl (
    HANDLE hDevice,                         // 対象デバイスのハンドル
    DWORD dwIoControlCode,           // I/Oコントロールコード
    LPVOID lpInBuffer,        //デバイスに書き込むデータを渡すバッファへのポインタ
    DWORD nInBufferSize,                // デバイスに書き込むバッファの大きさ
    LPVOID lpOutBuffer,                    // 読み取ったデータを受け取るバッファへのポインタ
    DWORD nOutBufferSize,             // 読み取ったデータバッファの大きさ
    LPDWORD lpBytesReturned,       
// 実際に受け取ったバイト数を格納するバッファへのポインタ
    LPOVERLAPPED lpOverlapped   // 非同期に使用する構造体へのポインタ
 );

DeviceIoControlの各引数は関数を呼ぶ前にユーザーアプリケーションで用意しなければなりません。I/Oコントロールコードも他の引数と同じように、DeviceIoControlを呼ぶ際、必ず設定しなければなりません。ドライバはI/Oマネージャから渡されたI/Oコントロールコードによってこれから行う機能を判断します。I/Oコントロールコードは開発者が作成します。開発者は実際のデバイス制御方法を考えてI/Oコントロールコードを作成する必要があります。

I/Oコントロールコードは、提供されているCTL_CODEと呼ばれるカスタムコントロールコードによって作成されます。CTL_CODEは次のような形になっています。

CTL_CODE (DeviceType, Function, Method, Access)

DeviceType引数はI/Oコントロールコードが属するデバイスの種類を指定します。標準デバイスはWINDDK.Hで定義されてあるFILE_DEVICE_XXXを使います。例えば、ディスクドライバにはFILE_DEVICE_DISKの値を渡します。カスタムデバイスには、32768から65535の範囲で値が選ばれます。この値は、ドライバエントリでデバイスオブジェクトが作成されたときに使う値と一致する必要があります。

Function引数はドライバがI/Oコントロールコードを識別するための値です。カスタムデバイスには2048から4095の範囲で値が選ばれます。Function引数はドライバ内部でユニークな値である必要があります。従って、同じ値を他のドライバが使っていても、ドライバの内部でユニークな値であれば問題はありません。例えば、ドライバAでFunction引数に2222という値を設定したとします。もしドライバBでFunction引数に2222という値を設定しても問題はありません。しかし、ドライバAのなかでFunction引数に2222を2つ以上設定することはできません。

Method引数はこのI/Oリクエストで提供されるデータバッファのアクセス方法を指定します。データバッファはDeviceIoControlのInBufferとOutBufferが引数として渡されます。InBufferはデバイスに書き込むためのデータバッファのポインタで、OutBufferはデバイスからデータを読み込むバッファのポインタです。Method引数は次のように指定できます。

METHOD_BUFFERED
METHOD_IN_DIRECT/METHOD_OUT_DIRECT
METHOD_NEITHER

これらのアクセス方法について次に詳細を説明します。

METHOD_BUFFERED

METHOD_BUFFEREDはInBuffer、OutBufferともにバッファI/Oとしてアクセスします。I/Oマネージャはバッファの大きい方に合わせて非ページプールメモリにデータバッファを割り当てます。図1ではInBufferの方がOutBufferよりもサイズが大きいので、非ページプールメモリにはInBufferのバッファサイズを割り当てます。

図1:METHOD_BUFFERED
図1:METHOD_BUFFERED
  1. アプリケーションから仮想メモリ空間にInBuffer、OutBufferを確保します。
  2. アプリケーションからDeviceIoControl関数を呼びます。I/Oマネージャは仮想メモリ空間にあるInBufferとOutBufferの大きさを判断し、大きいほうに合わせて非ページプールメモリの確保を行います。そして、仮想メモリ空間にあるInBufferのデータを、確保したメモリにコピーします。
  3. ドライバはIRPのAssociatedIrp. SystemBufferフィールドから非ページプールメモリのアドレスを得て、システムバッファからInBufferのデータを取得します。
  4. ドライバはアプリケーションに返したいデータを非ページプールメモリ内のバッファにセットします。既にあったデータ(②でコピーされたデータ)は上書きされます。
  5. システムメモリ空間にセットされたデータを仮想メモリ空間にあるOutBufferにコピーし、コピーしたバイト数をlpBytesReturnedにセットします。また、I/Oマネージャは確保していたシステムメモリを解放します。

METHOD_IN_DIRECT/METHOD_OUT_DIRECT

METHOD_IN_DIRECT/METHOD_OUT_DIRECTは、InBufferをバッファI/Oとしてアクセスし、OutBufferをダイレクトI/Oとしてアクセスします(図2)。

図2. METHOD_IN_DIRECT/METHOD_OUT_DIRECT
図2. METHOD_IN_DIRECT/METHOD_OUT_DIRECT
  1. アプリケーションから仮想メモリ空間にInBuffer、OutBufferを確保します。
  2. アプリケーションからDeviceIoControl関数を呼びます。I/Oマネージャは仮想メモリ空間にあるInBufferと同じ大きさの非ページプールメモリの確保を行います。そして、仮想メモリ空間にあるInBufferから確保したメモリにデータをコピーします。
  3. I/Oマネージャはドライバを通してMmProbeAndLockPagesを呼び、仮想メモリ空間のOutBufferに対応する物理メモリをロックします。
  4. ドライバはMmGetSystemAddressForMdlを使い、MDLを介して直接物理メモリにアクセスします。ドライバはIRPのAssociatedIrp. SystemBufferフィールドから非ページプールメモリのアドレスを得て、システムバッファからInBufferのデータを取得します。
  5. ドライバは④で得たMDLを通して、アプリケーションに返すデータを物理メモリにセットします。コピーしたバイト数をlpBytesReturnedにセットします。I/OマネージャはInBuffer確保したシステムメモリを解放し、MmUnlockPages関数を使いロックされたOutBufferの物理メモリを解放します。

2つの違い

METHOD_IN_DIRECTとMETHOD_OUT_DIRECTの違いはI/OマネージャによるOutBufferのアクセスチェックにあります。METHOD_IN_DIRECTでは、I/Oマネージャは要求元がOutBufferに読み込みアクセスできるかをチェックします。一方METHOD_OUT_DIRECTでは、I/Oマネージャは要求元がOutBufferに書き込みアクセスできるかをチェックします。

METHOD_NEITHERではInBuffer、OutBufferともにニーザーI/Oとしてアクセスします。ドライバのルーチンが要求元と同じコンテキストにある場合にのみ使用可能なアクセス方法です。詳細については省略します。

ACCESS引数はドライバのアクセス方法を示します。この値は、CreateFile関数を呼び出す際に与えられたアクセスモードと関連があります。この引数に指定可能な値を次に示します。

FILE_ANY_ACCESS:
CreateFile関数で指定されたアクセス方法を示します。
FILE_READ_ACCESS:
読み込みアクセスを示します。
FILE_WRITE_ACCESS:
書き込みアクセスを示します。

通常I/Oコントロールコードはドライバのヘッダファイルに次のように定義されます。

ファイル名:gpioctl.h
#define GPD_TYPE         40000
#define IOCTL_GPD_READ_PORT_UCHAR \
CTL_CODE (GPD_TYPE, 0x900, METHOD_BUFFERED, FILE_READ_ACCESS )
#define IOCTL_GPD_READ_PORT_USHORT \
CTL_CODE (GPD_TYPE, 0x901, METHOD_BUFFERED, FILE_READ_ACCESS)
#define IOCTL_GPD_READ_PORT_ULONG \
CTL_CODE (GPD_TYPE, 0x902, METHOD_BUFFERED, FILE_READ_ACCESS)
#define IOCTL_GPD_WRITE_PORT_UCHAR \
CTL_CODE(GPD_TYPE,  0x910, METHOD_BUFFERED, FILE_WRITE_ACCESS)
#define IOCTL_GPD_WRITE_PORT_USHORT \
CTL_CODE(GPD_TYPE,  0x911, METHOD_BUFFERED, FILE_WRITE_ACCESS)
#define IOCTL_GPD_WRITE_PORT_ULONG \
CTL_CODE(GPD_TYPE,  0x912, METHOD_BUFFERED, FILE_WRITE_ACCESS)

参考資料

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