特集
» 2007年03月12日 00時00分 公開

Windows CE 5.0開発者に贈る:Windows Embedded CE 6.0ドライバ開発の要点 (2/3)

[岩崎平(Microsoft MVP - Windows Embedded/安川情報システム),@IT MONOist]

ドライバ開発のポイント

 アーキテクチャが変更になったからといって、CE 6.0のドライバ開発が大幅に変更になったかというと、実はそうでもありません。基本的には、前述したメモリモデルの変更に対応すればよいようになっています。DLLとして実装すべきエントリポイントや使用できるAPIも、できる限り互換性を維持した形で実装されています。ドライバ開発での変更点としては、

  • アクセスチェック
  • マーシャリング
  • スレッドパーミッション
  • セキュアコピー
  • ユーザーインターフェイス実装

が挙げられます。

 これから1つ1つ説明していきますが、本稿では「ストリームインターフェイスドライバ」を前提として説明します。GWESで管理されるネイティブドライバやネットワークアダプタ用のドライバなどには当てはまらない部分もあります。あらかじめご了承ください。

 なお、CE 6.0のヘルプも参照いただければと思います。

アクセスチェック

 ドライバがカーネルモードで動作することになり、ドライバ内におけるユーザープロセスへのメモリアクセスの方法が変わりました。ユーザープロセスから渡された構造体のポインタを使用してアクセスする場合を例に解説します。

struct SampleStruct { 
  UCHAR            *pBuff;
  DWORD            dwSize;
};
 
SampleStruct *p = ( SampleStruct *) pBuffIn; 

 いま、ドライバのIOCTLの引数であるpBuffInにSampleStruct構造体のポインタが渡されたとします。SampleStruct構造体には*pBuffとdwSizeのメンバがあります。ドライバ内でpBuffInをSampleStructポインタとしてキャストした*pを使用し、*pBuffからデータを取得するために*p->pBuffとして値を取り出すことを考えてみましょう。p->pBuffにはポインタ変数が入っていますが、このアドレスがアクセス可能なものか否かをドライバが判断することはできません。悪意のあるユーザープロセスが、別のカーネルデータが存在するアドレスを設定しているかもしれません。この場合、OSの動作にダメージを与えることになります。従って、ドライバは構造体に組み込まれたポインタ変数に対して、アクセス可能かどうかをチェックする必要があります。

 CE 5.0では、以下のAPIでアクセス可能か否かをチェックします。

pEmbBuff = MapCallerPtr(p->pBuff); 

 CE 6.0では、以下のAPIを使用します。

hr = CeOpenCallerBuffer((PVOID*)&pEmbBuff, p->pBuff, p->dwSize, ARG_I_PTR, FALSE);
ポインタへのアクセス
hr = CeCloseCallerBuffer((PVOID)pEmbBuff, p->pBuff, p->dwSize, ARG_I_PTR ); 

 CE 5.0との大きな違いは、チェックする範囲です。CE 5.0までは、MapCallerPtr APIによる1byteのアクセスチェックでした。CE 6.0ではCeOpenCallerBuffer APIを使用して、すべてのバッファに対してのアクセスチェックを行います。これにより、確実にバッファへのアクセスを行うことを保証できます。また、使用しなくなった場合はCeCloseCallerBuffer APIを使う必要があります。

 なお、MapCallerPtr APIはCE 6.0では削除されているので、このAPIを利用しているドライバは変更が必要になります。

マーシャリング

 次は、「アクセスするアドレスはどうなるか」についてです。ユーザープロセスがドライバを呼び出すときに指定したポインタ変数は、ドライバに渡った時点でドライバからアクセス可能なアドレスに変換されています。しかし、先ほどの構造体のように、組み込まれているポインタ変数までは変換してくれません。従って、ドライバではこの組み込まれたポインタ変数のアドレスをアクセス可能なアドレスに変換する必要があります。このように、アクセスするためのアドレス変換などの準備を行うことを「マーシャリング」と呼びます。

 CE 5.0でのマーシャリングは、アクセスチェックで使用したMapCallerPtr APIが行っています。CE 6.0も同様に、アクセスチェックで使用したCeOpenCallerBuffer APIが行っています。

 ただし、CE 6.0ではプロセスごとにユニークな2Gbytesのエリアが割り当てられるので、すべてのデータに対してアクセス可能とは限りません。そのため、ドライバからユーザープロセス上のメモリへのアクセスに対して、同期するのか非同期なのかを意識する必要があります。

 CE 6.0のアーキテクチャ仕様として、ユーザープロセスから呼び出されている間のアクセスはすべて同期アクセスとなり、前述したCeOpenCallerBuffer/CeCloseCallerBufferで直接メモリアクセスできます。呼び出したスレッドのコンテキストに対してのアクセスを行うことになるためです。

 では、「ユーザープロセスから呼び出されている間」以外の場合を考えてみましょう。ドライバ内にスレッドが存在し、あるタイミングでユーザープロセス上のメモリを参照もしくは更新したいことがあります。つまり、ドライバ内のスレッドが動作しているときにユーザープロセスのエリアに対してアクセスを行うことになります。この場合、対象となるバッファに対して非同期なアクセスを行う必要があります。CE 6.0では非同期アクセスを行うため、以下のAPIが新しく提供されています。

  • CeAllocAsynchronousBuffer
  • CeFreeAsynchronousBuffer

 先ほどのソースコードを例に使用方法を示します。

hr = CeOpenCallerBuffer((PVOID*)&pEmbBuff, p->pBuff, p->dwSize,
 ARG_I_PTR, FALSE);
hr = CeAllocAsynchronousBuffer((PVOID*)&pMarshalled, pEmbBuff,
 p->dwSize, ARG_I_PTR);
pMarshalledを使用してアクセス
hr = CeFreeAsynchronousBuffer((PVOID)pMarshalled, pEmbBuff,
 p->dwSize, ARG_I_PTR);
hr = CeCloseCallerBuffer((PVOID)pEmbBuff, p->pBuff, p->dwSize,
 ARG_I_PTR ); 

 CeOpenCallerBuffer APIを使用してバッファチェックを行った後に、CeAllocAsynchronousBuffer APIを使用しています。このAPIを使用すれば非同期にユーザープロセスのエリアに対してのアクセスが可能になります。アクセス終了時にはCeFreeAsynchronousBuffer APIをコールしてバッファアクセスの使用を終了します。

Copyright © ITmedia, Inc. All Rights Reserved.