SCIENCE PARK

デバドラ講座

【 デバドラ講座26 : データバッファのアクセス方法 】

アプリケーションからI/O要求を行い、デバイスにデータを読み書きする際、デバイスに書くデータ、デバイスから読み込んだデータを一時的に保存するためにバッファを用意する必要があります。システムのI/O操作に関連したデータバッファへのアクセス方法には次の3つが提供されています。

  1. ダイレクトI/O
  2. バッファI/O
  3. ニーザーI/O

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

1. ダイレクトI/O

ダイレクトI/Oはアプリケーションのデータに直接アクセスします。ダイレクトI/Oは転送するデータ量が多い時に使われ、DMAデバイスドライバでよく使われます。ダイレクトI/Oのデータの転送方法を図1に示します。ユーザーアプリケーションのデータはMDLにマッピングします。図1のようにユーザーアプリケーションのデータバッファは物理メモリ内で連続して存在しません。MDL(メモリディスクリプタリスト)はI/Oマネージャが生成する構造体で、データバッファに関する情報が含まれています。MDLは物理メモリ内で連続していないバッファを一つの集合体として扱います。I/OマネージャはIRPのMdlAddressにMDL構造体のアドレスをドライバに渡します。

例えば、データをデバイスに書き込む際、I/OマネージャはドライバにMDLのアドレスを渡します。ドライバはMDLを介してデータバッファからデータを読み込み、デバイスに書き込みを行います。MDLがマッピングしている際、元のデータバッファはロックされます。データバッファをロックすることで、要求元の物理メモリはロックが解除されるまでページアウトしません。IRPがすべて完了すると、ロックしたページを解放します。I/OマネージャはMDLを介してデータバッファに関する情報を取得できる関数を提供します。

図1:ダイレクトI/O
図1:ダイレクトI/O

2. バッファI/O

バッファI/Oはアプリケーション空間のデータをシステム空間のバッファにコピーしてデータにアクセスします。バッファI/Oは実装が最も簡単であり、初心者は基本的にこのアクセス方法を使いドライバを開発します。ユーザーモードからI/O要求があると、I/Oマネージャはまず呼び出し元がデータバッファ全体にアクセスできるかをチェックします。次に、I/Oマネージャはデータバッファと同じ大きさのシステムバッファを非ページプールメモリに確保します。I/O要求が読み込み又は書き込み操作の場合、IRPのAssociateIrp.SystemBufferフィールドを通して、バッファのカーネル仮想アドレスを渡します。バッファI/Oを使用する際、元のデータバッファを構成するメモリページはI/O操作の間ロックされません。

例えば、データをデバイスに書き込む際、I/OマネージャはIRPをドライバに渡す前にシステム空間内にバッファを用意し、アプリケーションのデータバッファコピーします。システム空間内のバッファとドライバはやり取りを行います。データをデバイスから読み込む際、I/OマネージャはIRPをドライバに渡す前にアプリケーションから与えられたデータバッファを基に、システム空間内にバッファを用意します。デバイスから読み込んだデータをシステム空間内のバッファに一時的に保存し、アプリケーションのバッファにコピーします。IRPが完了したとき、非ページプールメモリ内に確保された領域は解放されます。

図2.バッファI/O
図2.バッファI/O

3. ニーザーI/O

ニーザーI/OはダイレクトI/OでもバッファI/Oでもないアクセス方法を利用します。このアクセス方法では、I/Oマネージャはドライバに要求元のデータバッファの仮想アドレスを提供します(図3)。アプリケーション空間内のデータバッファアドレスは、IRPのUserBufferフィールドに渡されます。ニーザーI/Oは呼び出し元のスレッドとドライバが同じコンテキストにある時のみ使用することが可能です。つまり、ドライバが最上位のドライバのときのみこのアクセス方法が使えます。この方法は特殊なケースの時のみ使用できるので、できるだけこの方法は避けるべきです。

図3:ニーザーI/O
図3:ニーザーI/O

ドライバでI/O要求を処理するためには、ドライバエントリに上記のデータバッファアクセス方法を選択しなければなりません。具体的には、デバイスオブジェクトを作成後にFlagフィールドを設定する必要があります。Flagフィールドには複数の値をORで指定する事も可能です。例えば、Flagの初期値にDO_BUFFERED_IOまたはDO_DIRECT_IOが含まれる場合、転送方法はバッファI/OまたはダイレクトI/Oになります。Flagが初期化されていない場合、ニーザーI/Oを使用します。読み取り(ReadFile)と書き込み(WriteFile)は同じ方式で使わなければなりません。

Flagフィールドを設定する以外の方法として、DeviceIoControlを使いデバイスからデータを読み込む際に、I/Oコントロールコード(IOCTL)でデータバッファの使い方を設定することができます。詳細についてはレポート27で説明します。

参考資料

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