Posts filed under 'カーネルモジュールの実装'

WEC のネットワークフィルタドライバ(NDIS フィルタ)

今回は、ネットワークドライバに対するフィルタドライバについて述べます。既存のドライバに対してフィルタドライバを attach することにより、ドライバの動作診断に役立てることができます。あるいは、どのように呼び出されるのかという視点からドライバの振る舞いを解析し、既存のドライバを改修する際に役立てたり、また、サンプルドライバの動作を解析して新規にドライバを実装する際の参考にする、といった利用もできるでしょう。

■はじめに~WEC 7 での NDIS 6.0 の導入
NDIS フィルタドライバは、WEC/WinCE で NDIS 6.0 に対応した版、つまり WEC 7 で導入されました。リファレンスにもサンプルコードにも、NDIS フィルタドライバが登場するのは WEC 7 からで、WinCE 6.0 にはありません。WEC 7 と WEC 2013 のリファレンスの、NDIS フィルタドライバの説明は、それぞれ次のページです:

 NDIS Functions for Filter Drivers (Compact 7)
 http://msdn.microsoft.com/en-us/library/gg158504(v=winembedded.70).aspx

 NDIS Filter Driver Reference (Compact 2013)
 http://msdn.microsoft.com/en-us/library/gg159366.aspx

NDIS 5.x だった WinCE 6.0 では、NDIS フィルタドライバに相当する機能は、NDIS 中間ドライバ(NDIS intermediate driver)として提供されていました。興味のある方は、WinCE 6.0 のリファレンスの次のページをご覧ください。

 Intermediate Drivers (Windows Embedded CE 6.0)
 http://msdn.microsoft.com/en-us/library/ee483408(v=winembedded.60).aspx

NDIS の 5.x と 6.0 の違いについては、次のページが参考になるでしょう。

 Port Miniport Drivers from NDIS 5.x to 6.0 (Compact 7)
 http://msdn.microsoft.com/en-us/library/jj838857(v=winembedded.70).aspx

 Port Miniport Drivers from NDIS 5.x to 6.0 (Compact 2013)
 http://msdn.microsoft.com/en-us/library/jj838857.aspx

 Introduction to NDIS 6.0
 http://msdn.microsoft.com/en-us/library/windows/hardware/ff556026%28v=vs.85%29.aspx

■NDIS フィルタドライバのサンプルコード
NDIS フィルタドライバのサンプルコードは、WEC 7 も WEC 2013 も、次のディレクトリに収録されています:

 %_WINCEROOT%/public/common/oak/drivers/netsamp/lwfilter/

このサンプルコード(lwfilter)は、WEC 2013 のサンプルコードページにも WEC 7 のサンプルコードページにも、説明が載っていません。そのため、見落としていた方もいらっしゃるかも知れません。もし興味がわいたら、この機会に、ご覧になってみて下さい。lwfilter は、Windows Vista/7 用の NDIS 6.0 フィルタドライバのサンプルコードがベースになっているようで、同じ名前の Win7 用のサンプルコードが、次のページで提供されています:

 NDIS 6.0 Filter Driver
 https://code.msdn.microsoft.com/windowshardware/NDISLWFSYS-Sample-NDIS-60-42b76875

上述した、NDIS 5.x の NDIS 中間ドライバのサンプルコードは、WEC 7 以降でも提供されており、次のディレクトリに収録されています:

 %_WINCEROOT%/public/common/oak/drivers/netsamp/passthru/

NDIS 中間ドライバが WEC 7 以降も使えるのは、NDIS 5.x のサポートが含まれているからです。NDIS 5.x 関連のドライバインタフェースは、リファレンスの次のページで説明されています:

 NDIS 5.x Legacy Reference (Compact 7)
 http://msdn.microsoft.com/en-us/library/gg158312(v=winembedded.70).aspx

 NDIS 5.x Legacy Reference (Compact 2013)
 http://msdn.microsoft.com/en-us/library/gg158312.aspx

ちなみに、WEC 7 と WEC 2013 のネットワーク関連のサンプルコードは、次のページで紹介されています。興味のある方は、こちらもご覧になってみて下さい。

 Networking Code Samples (Compact 7)
 http://msdn.microsoft.com/en-us/library/hh802406(v=winembedded.70).aspx

 Networking Code Samples (Compact 2013)
 http://msdn.microsoft.com/en-us/library/hh802406.aspx

■フィルタドライバの効用
最後に、フィルタドライバの効用について述べます。冒頭で、既存のドライバの動作診断や、振る舞いの解析に役立てることができると書きましたが、その他に、既存のドライバに対する機能追加、という効用があります。NDIS は、フィルタドライバが attach されたドライバを直接呼び出さず、フィルタドライバを経由して間接的に呼び出します。従って、フィルタドライバは、既存のドライバに対する NDIS からの呼び出しをフックして追加の処理を実行できるのです。つまり、既存のドライバの実装を変更せずに、フィルタドライバによって機能追加を行うことができる、というわけです。

フィルタドライバにより機能追加を行うという方策は、機能追加したい対象のドライバが複数存在する場合に、より効果的です。たとえば、NDIS フィルタドライバによってドライバに機能拡張したいデバイスが、複数のネットワークインタフェースを持つ場合のことを考えてみて下さい。有線 LAN のインタフェースと無線 LAN のインタフェースを両方持つデバイスは、近年珍しくありません。それら複数のネットワークインタフェースのドライバに対して、同じ機能追加を行う場合、個々のドライバの実装を変更して機能追加するよりも、NDIS フィルタドライバを使って機能追加する方が、実装変更箇所が一つで済みますから、より低コストで実現できるでしょう。既存のドライバの実装を変更しなくて済むというのは、既存のドライバのソースコードを入手できず、自分で改変できない場合にも役立つ点です。

NDIS は、ネットワークインタフェースのハードウェアを抽象化したレイヤですから、そのレイヤのインタフェースに対するフィルタドライバは、異なるネットワークインタフェースのドライバに対して共通に適用できます。機能追加の例として、「ネットワーク通信が行われている間は、WEC の Power Manager の activity timer のイベントを発火させ、自動サスペンドしないようにする」という機能があります。この機能は、個々のネットワークインタフェースのドライバの実装を改変して実装するよりは、NDIS フィルタドライバで実装し、フィルタドライバを各ネットワークインタフェースのドライバに attach する方がシンプルで良いでしょう。これは、あくまでも一つの例に過ぎませんが、フィルタドライバの役立て方として参考になれば幸いです。

Add comment 2014/09/07 koga

タスクバーの電源状態アイコンとバッテリドライバ

■電源管理ドライバとバッテリドライバ
WEC/WinCE の標準シェル(explorer)は、タスクバーに電源状態を示すアイコンを表示します。これは、デフォルトの動作であり、レジストリ設定で変更できます。タスクバーの電源状態アイコン表示に関するレジストリ設定は、リファレンスの次のページで説明されています。このページの、”Display Power Status” の項を見て下さい:

 Windows Embedded Compact Explorer Registry Settings (Windows Embedded Compact 7)
 http://msdn.microsoft.com/en-us/library/ee500839.aspx

ただし、OS の実装次第では、このアイコンは表示されません。このアイコンの表示には、電源管理ドライバとバッテリドライバが関係しており、それらの振る舞いによって、アイコンの表示動作が変わります。それぞれのソースコードは、WEC 7/WinCE 6.0 の次の場所にあります。

・タスクバーの電源アイコン表示
 %_WINCEROOT%/public/shell/oak/hpc/explorer/taskbar/power.{h,cpp}

・電源管理ドライバ
 %_WINCEROOT%/public/COMMON/oak/drivers/pm/

・バッテリドライバ
 %_WINCEROOT%/public/COMMON/oak/drivers/battdrvr/

電源管理ドライバとバッテリドライバは、どちらも二層構造の階層型ドライバ(layered driver)であり、MDD (Model Device Driver) と PDD (Platform Device Driver) で構成されています。階層型ドライバについては、WEC 7 のディベロッパーガイドにある次のページをご覧下さい:

 Layered and Monolithic Drivers (Windows Embedded Compact 7)
 http://msdn.microsoft.com/en-us/library/jj659821.aspx

PDD の実装は、プロセッサや CPU ボードの特性により、必要に応じてカスタマイズしますので、BSP (Board Support Package) にカスタマイズ部分が含まれます。カスタマイズの有無は BSP ごとに違いますが、WEC 7 付属の BSP ですと、Freescale i.MX313 のリファレンスボード用の BSP には、電源管理ドライバとバッテリドライバの両方のディレクトリが収録されています。次の場所です。

 %_WINCEROOT%/platform/IMX313DS/SRC/DRIVERS/BATTDRVR/
 %_WINCEROOT%/platform/IMX313DS/SRC/DRIVERS/PM/

この BSP の電源管理ドライバの方は、実は WEC 7 付属のものと違いません。上の PM/ ディレクトリには、ソースファイルは入っておらず、カスタマイズされていないのです。実際、sources ファイルを見ても、WEC 7 付属のデフォルト実装の .lib ファイルをリンクして pm.dll を生成する記述になっています。もしかすると、この BSP をもとにして電源管理ドライバをカスタマイズする開発者のために、テンプレートとして収録しているのかも知れません。

■電源状態の検出と表示
タスクバーに話を戻します。タスクバーは、現在の電源状態を表示に反映するために、電源管理ドライバが提供する電源状態監視機能を利用しています。電源管理 API の関数である RequestPowerNotifications() を呼び出すことにより、電源状態の変更をメッセージキューで受け取ることができます。そのメッセージキューに電源状態の変更通知が届いたことを検出すると、電源状態アイコンの表示を変更して通知内容を反映させるのです。

タスクバーのソースファイルの中で、電源管理ドライバから電源状態の変更通知を受け取る個所は、
 %_WINCEROOT%/public/shell/oak/hpc/explorer/taskbar/
ディレクトリの taskbar.cpp です。このソースファイルにある CTaskBar::MessageLoop() の中で、RequestPowerNotifications() の呼び出しと、MsgWaitForMultipleObjectsEx() を使ったメッセージループを実行します。メッセージループにおいて、MsgWaitForMultipleObjectsEx() を使ってウィンドウズメッセージのキューとメッセージキューを同時に監視して、届いたメッセージに対する応答動作を実行します。電源状態アイコン表示の変更は、冒頭で挙げた power.{h,cpp} で定義・実装されている PowerManagerUI クラスが担当します。

RequestPowerNotifications() のリファレンスは、次のページです:

 RequestPowerNotifications (Windows Embedded Compact 7)
 http://msdn.microsoft.com/en-us/library/ee497721.aspx

以上が、タスクバーと電源管理ドライバの連携の仕組みです。次に、電源管理ドライバとバッテリドライバの連携を見てみましょう。

電源管理ドライバは、バッテリドライバから PowerPolicyNotify() の呼び出しによって電源状態の通知を受け取ります。PowerPolicyNotify() によって、電源管理ドライバが読み出すメッセージキューに通知メッセージが投入され、その結果、タスクバーなど、電源状態監視機能のクライアントへ通知されるというわけです。

PowerPolicyNotify() のリファレンスは、次のページです:

 PowerPolicyNotify (Windows Embedded Compact 7)
 http://msdn.microsoft.com/en-us/library/ee497744.aspx

PowerPolicyNotify() のソースコードは、
 %_WINCEROOT%/private/winceos/COREOS/core/thunks/tpm.cpp
にあり、この関数によってメッセージが投入されるメッセージキューから読み出しを行う処理は、
 %_WINCEROOT%/public/common/oak/drivers/pm/pdd/common/pddpolicy.cpp
 %_WINCEROOT%/public/common/oak/drivers/pm/pdd/default/pwsdef.cpp
で実装されています。興味がある方は、ご覧になってみて下さい。PmPolicyReadNotificationQueue() というのが、pdbpolicy.cpp で実装されている、メッセージキューから読み出しを行う関数です。

■バッテリドライバによる電源状態監視
バッテリドライバは、電源状態を監視して、状態変化を検出した際に電源管理ドライバへ通知しますが、そのためにスレッドを起動します。このスレッドが実行する手続きは、
 %_WINCEROOT%/public/common/oak/drivers/battdrvr/mdd/battdrvr.c
で実装されている BatteryThreadProc() です。このスレッドを生成する際、CreateThread() の第5引数(fdwCreate)に CREATE_SUSPENDED を渡しており、サスペンド状態で生成されます。従って、ResumeThread() を呼び出すまでは、このスレッドは動作しません。

このスレッドに対して ResumeThread() が呼び出されるのは、バッテリドライバに対して IOCTL_BATTERY_POSTINIT という制御コードで DeviceIoControl() が呼び出された場合です。それまでの間は、バッテリドライバ内の電源状態監視スレッドはサスペンド状態で生成されたままとなり、電源状態の監視動作は行われません。この結果、タスクバーには、電源状態アイコンが表示されないのです。

今回のエントリの冒頭で、「ただし、OS の実装次第では、このアイコンは表示されません。」と書きましたが、それは、このことを指したものなのです。つまり、OS の起動時にバッテリドライバ(デバイス名は BAT1:)に対して IOCTL_BATTERY_POSTINIT の DeviceIoControl() を呼び出さないと、OS が起動してタスクバーが表示された直後に、AC 電源接続を示すアイコンが表示されたのち、そのアイコンの表示は消えてしまいます。

バッテリドライバに対して IOCTL_BATTERY_POSTINIT の DeviceIoControl() を呼び出さないと(つまり、バッテリドライバの電源状態監視スレッドを始動しないと)電源状態アイコンの表示が消えてしまうのは、電源管理ドライバが持っている電源状態のキャッシュが、無効な内容で初期化されたままになるからです。このキャッシュは、
 %_WINCEROOT%/public/common/oak/drivers/pm/pdd/default/platform.cpp
で宣言されている gSystemPowerStatus という名前の、POWER_BROADCAST_POWER_INFO 型の大域変数です。gSystemPowerStatus は、
 C:\WINCE700\public\common\oak\drivers\pm\pdd\common\pddpower.cpp
で実装されている PmInitPowerStatus() で全てのフィールドを無効値(0xFF)に初期化されます。そのため、バッテリドライバの電源状態監視スレッドがサスペンドしたままだと、キャッシュの内容は更新されず、RequestPowerNotifications() の呼び出しに対して最初に一回だけ送付する電源状態通知の内容が無効値となります。その結果、タスクバーが電源状態アイコンの表示を消すのです。

バッテリドライバに対して IOCTL_BATTERY_POSTINIT の DeviceIoControl() 呼び出しを行うと、バッテリドライバの電源状態監視スレッドが一定間隔(デフォルトは5秒)で電源状態の取得動作を実行しますので、電源管理ドライバが持つ電源状態のキャッシュが正しい内容となります。すると、タスクバーに電源状態アイコンが表示されます。

バッテリドライバの電源状態監視スレッドが、デフォルト実装では停止したままになるのは、不要にスレッドを動かして CPU リソースを消費することを避けるためなのでしょう。電源状態監視スレッドは、バッテリ駆動のハードウェアで OS が動作する場合にのみ動作させればよいわけです。デフォルト実装では AC 電源接続としていますから、電源状態監視スレッドを始動しないようになっているのではないかと思います。

■電源状態アイコン表示のレジストリ設定
最後に、レジストリ設定について補足します。バッテリ駆動のハードウェアで動かす場合に、バッテリドライバをカスタマイズして、電源状態の監視処理の実装を行ったとしましょう。そして、OS の起動時に自動実行されるアプリケーションなどを使って、バッテリドライバの電源状態監視スレッドを始動するようにした場合です。

バッテリドライバによって、電源監視状態の監視は行うが、タスクバーに電源状態アイコンを表示したくない、という場合は、レジストリ設定を変更する必要があります。デフォルトでは、バッテリドライバの電源監視スレッドが動作すると電源状態アイコンが表示されるからです。電源状態アイコンをタスクバーに表示させないためのレジストリ設定は、次の二通りです:

 ・HKEY_LOCAL_MACHINE\Software\Microsoft\Power キーの ShowIcon の値に 0 を設定する。

 ・HKEY_LOCAL_MACHINE\Software\Microsoft\Power キーのみをレジストリに登録して、ShowIcon は設定しない。

ShowIcon の値は、デフォルト値が 0 として扱われるため、HKEY_LOCAL_MACHINE\Software\Microsoft\Power キーの下に ShowIcon が存在しなければ、0 として扱われます。ただし、HKEY_LOCAL_MACHINE\Software\Microsoft\Power キーそのものが存在しない場合は、デフォルト値として 1 が使われるのです。バッテリドライバのデフォルトの設定では、HKEY_LOCAL_MACHINE\Software\Microsoft\Power キーがレジストリに登録されないため、ShowIcon の値が 1 として扱われ、電源状態アイコンが表示されます。

Add comment 2013/01/20 koga

Telnet サーバと udevice.exe(2/2)

前回(2012/08/27)から、だいぶ間が空いてしまいましたが、続きです。

■サービスとユーザモードドライバの起動の仕組み
前回の説明で、telneted や ftpd などのサービスは、それらの DLL のホストプロセスが servicesd.exe であり、ユーザモードドライバのホストプロセスは udevice.exe だということを述べました。そして、各サービスは、ユーザモードのデバイスドライバとして実装されており、そのため、サービスのホストである servicesd.exe と、通常のユーザモードドライバのホストである udevice.exe は、中核部の実装を共有していることも説明しました。

今回は、両者について、もう少し詳しく見てみます。

まず、サービスとユーザモードドライバが、それぞれ、どのようにしてロードされて起動するのかを見てみましょう。両者ともに、OS の起動時に自動的に起動するように設定することもできれば、アプリケーションからの要求に応じて起動することもできます。

(OS の起動時に自動起動されない設定の)ユーザモードドライバを起動するには、カーネルモードのドライバと同じく、ActivateDevice() または ActivateDeviceEx() を使います。前回のエントリでも紹介したリファレンスのページに書かれている通り、ActivateDevice() や ActivateDeviceEx() の第一引数に渡すドライバのレジストリキーにおいて、Flags の値が DEVFLAGS_LOAD_AS_USERPROC ビット(0×10)を含んでいると、そのドライバは、カーネルではなく、udevice.exe プロセスによってロードされ、ユーザ空間で動作します。

一方、サービスを起動するには、ActivateService() という関数を使います:

 ActivateService (Windows Embedded Compact 7)
 http://msdn.microsoft.com/en-us/library/ee501277.aspx

ActivateDevice[Ex]() に渡すレジストリキーは、HKEY_LOCAL_MACHINE\Drivers\ 配下のサブキーですが、ActivateService() に渡すレジストリキーは、HKEY_LOCAL_MACHINE\Services\ 配下のサブキーです。つまり、レジストリ設定においては、ユーザモードドライバとサービスは、HKEY_LOCAL_MACHINE\Drivers\ 配下に記述されるのか HKEY_LOCAL_MACHINE\Services\ 配下に記述されるのかによって区別されます。この区別は、OS の起動時における自動起動処理においても使われます。

■デバイスドライバ(ユーザモードドライバ)の自動起動処理
OS 起動時のデバイスドライバの自動起動処理は、デバイスマネージャの働きによって行われます。具体的には、次の手順で、レジストリキー KEY_LOCAL_MACHINE\Drivers\BuiltIn\ 配下に記述されたビルトインのドライバ群が起動されます。この起動手順は、カーネルモードドライバとユーザモードドライバで共通です。上述したように、各ドライバのレジストリキーにおいて、Flags キーの値に DEVFLAGS_LOAD_AS_USERPROC ビットを含むドライバが、ユーザモードドライバとしてロードされます。

1.) デバイスマネージャ(device.dll)のエントリルーチンである DevMainEntry() において、StartDeviceManager() を呼び出す。
2.) StartDeviceManager() は、DevLoadInit() を呼び出すことにより、ブートの第1フェーズでロードされるデバイスドライバ群をロードして起動する。
3.) StartDeviceManager() は、その後、InitDevices() を呼び出して、ブートの第2フェーズでロードされるデバイスドライバ群をロードして起動する。

上の (2) および (3) において、レジストリキー HKEY_LOCAL_MACHINE\Drivers\BuiltIn を指定して ActivateDevice() または ActivateDeviceEx() が呼び出されます。その結果、指定されたレジストリキー配下のドライバ群を数え上げて起動する BusEnum という特殊なドライバが起動し、このドライバが、HKEY_LOCAL_MACHINE\Drivers\BuiltIn\ 配下に記述されたドライバ群をロードして初期化します。

ここで、「ブートの第1フェーズ」と「第2フェーズ」については、このブログの 2011/02/21 のエントリ(「レジストリ変更内容の永続化(2/2)」)をご覧下さい。

BusEnum は、ブートの第1フェーズと第2フェーズのそれぞれにおいてインスタンスが生成されます。以下に、ブートの第1フェーズにおいて BusEnum が生成・起動された時の呼び出し履歴(コールスタック)を示します。興味のある方は、これを手掛かりにして、デバイスマネージャのソースコードをご覧になるのも面白いでしょう:

 BUSENUM!BusEnum::BusEnum()
 BUSENUM!Init()
 DEVMGR!DriverFilterMgr::DriverInit()
 DEVMGR!DeviceContent::EnableDevice()
 DEVMGR!DeviceContent::InitialEnable()
 DEVMGR!I_ActivateDeviceEx()
 DEVMGR!DM_ActivateDeviceEx()
 K.COREDLL!xxx_ActivateDeviceEx()
 DEVMGR!InitDevices()
 DEVMGR!DevloadInit()
 DEVMGR!StartDeviceManager()
 DEVICE!DevMainEntry()
 K.COREDLL!ThreadBaseFunc()

注意:ただし、ユーザモードドライバは、ブートの第1フェーズではロードできません。ユーザモードドライバをロードできるのは、ブートの第2フェーズおよび、ブート完了後です。

■サービスの自動起動処理
サービスの自動起動処理は、serviceStart.exe によって行われます。serviceStart.exe は、レジストリキー HKEY_LOCAL_MACHINE\init\ 下に登録され、device.dll 、つまりデバイスマネージャの起動完了後に起動されるように設定されています。具体的には、次の手順で、レジストリキー HKEY_LOCAL_MACHINE\Services\ 配下に記述されたサービス群が起動されます。

1.) serviceStart.exe は、レジストリキー HKEY_LOCAL_MACHINE\Services を指定して ActivateDevice() または ActivateDeviceEx() を呼び出す。

HKEY_LOCAL_MACHINE\Services を指定した ActivateDevice[Ex]() の呼び出しの結果、このレジストリキー配下のサービス群を数え上げて起動する ServicesEnum という特殊なドライバが起動し、ServicesEnum が、HKEY_LOCAL_MACHINE\Services\ 配下に記述されたサービス群をロードして初期化します。

この動作は、”HKEY_LOCAL_MACHINE\Drivers\BuiltIn” が “HKEY_LOCAL_MACHINE\Services” に代わり、そして BusEnum が ServiceEnum に代わった以外は、デバイスドライバの場合と概ね同じです。デバイスドライバ(ユーザモードドライバ)やサービスをロードするホストプロセスは、ユーザモードドライバが udevice.exe でサービスが servicesd.exe という違いはありますが、大枠の処理の流れは同じです。そのため、実装上も、前回のエントリから述べてきたように、udevice.exe と servicesd.exe が中核機能を共有している、というわけです。

以下に、serviceStart.exe から ServiceEnum が呼び出された時のコールスタックを示します:

 SERVICESENUM!ServicesEnum::ServicesEnum()
 SERVICESENUM!Init()
 DEVMGR!DriverFilterMgr::DriverInit()
 DEVMGR!DeviceContent::EnableDevice()
 DEVMGR!DeviceContent::InitialEnable()
 DEVMGR!I_ActivateDeviceEx()
 DEVMGR!EX_DM_ActivateDeviceEx()
 COREDLL!xxx_ActivateDeviceEx() ★
 SERVICESSTART!WinMain()
 SERVICESSTART!WinMainCRTStartupHelper()
 SERVICESSTART!WinMainCRTStartup()
 COREDLL!MainThreadBaseFunc()

ちなみに、上のコールスタックで★を付けた行の、coredell.dll の ActivateDeviceEx() の呼び出しまでがユーザランドで、それより上、つまり、devmgr.dll の EX_DM_ActivateDeviceEx() 以降は、カーネルランドにおける呼び出しです。ActivateDeviceEx() から EX_DM_ActivateDeviceEx() の間には、システムコールが介在しているのですが、カーネルデバッガは、その遷移を通常の関数呼び出しのように見せてくれるのです。

■ホストプロセスを指定する仕組み
次に、ユーザモードドライバやサービスが、どのようにしてホストプロセスに割り当てられるのかを見てみます。前回のエントリで紹介した、WinCE 6.0 のリファレンスにある User Mode Driver Framework のアーキテクチャ図を見て下さい。

 User Mode Driver Framework Architecture (Windows Embedded CE 6.0)
 http://msdn.microsoft.com/en-US/library/ee486510(v=winembedded.60)

この図にある Reflector が、ユーザモードドライバやサービスをホストプロセスに割り当てる処理を実行します。Reflector を呼び出すのは、上のページのアーキテクチャ図にある通り、デバイスマネージャです。

デバイスマネージャは、デバイスドライバの DLL をロードするよう要求された際、その DLL に対して DEVFLAGS_LOAD_AS_USERPROC が指定されている場合は、Reflector のインスタンスを生成します。サービスの場合も、同様にデバイスマネージャに対して DLL のロードが要求され、その際、DEVFLAGS_LOAD_AS_USERPROC が指定されます。その結果、ユーザモードドライバに対してもサービスに対しても、それらの DLL ごとに Reflector のインスタンスが生成されて、DLL に割り当てられます。そして、デバイスマネージャは、Reflector を介して、ユーザモードドライバやサービスとやり取りします。つまり、Reflector が proxy の役割を担います。

Reflector は、自身に割り当てられた DLL をロードさせるホストプロセスを探し、存在しない場合は、それを起動します。DLL をロードさせるホストプロセスが何かというのは、レジストリの設定によって決まります。具体的には、ユーザモードドライバやサービスのレジストリキーにおける UserProcGroup の値によって、ホストプロセスの実体が決まります。WEC 7 のリファレンスですと、次のページに説明があります:

 User Mode Driver Framework Registry Settings (Windows Embedded Compact 7)
 http://msdn.microsoft.com/en-us/library/ee482921.aspx

ただし、上のページには、UserProcGroup ではなく ProcGroup と記載されています。これは間違いだと思われます。実際、後述する
 %_WINCEROOT%/public/common/oak/files/common.reg
では、ホストプロセスの ID を指す値は UserProcGroup となっています。また、’ProcGroup’ という値を指定しても、その値は無視されてしまいます。Reflector のソースファイルにある CreateReflector() の実装を見ても、’UserProcGroup’ という値を参照していますから、上のページの説明が間違っているのだと思います。

Reflector によって生成されたホストプロセスの情報は、デバイスマネージャ内の大域変数に束縛された連結リストに格納され、それぞれのホストプロセスには ID が付けられます。そして、UserProcGroup で指定された ID のホストプロセスが既に生成済みであれば、そのプロセスを呼び出して、ユーザモードドライバやサービスの DLL をロードさせます。UserProcGroup で指定された ID のホストプロセスが、未だ生成されていなければ、生成したうえで、DLL をロードさせます。これらの処理の詳細に興味のある方は、Reflector のソースコードをご覧になってみて下さい。Reflector のソースファイルは、
 %_WINCEROOT%/private/winceos/COREOS/device/devcore/reflector.cpp
です。

参考までに、serviceStart.exe によってサービス群がロード・起動される際の、一つのサービスに対して Reflector が生成されるまでの呼び出しのコールスタックを以下に示します:

 DEVMGR!CreateReflector()
 DEVMGR!Reflector_Create()
 DEVMGR!DeviceContent::LoadLib()
 DEVMGR!I_ActivateDeviceEx()
 DEVMGR!DM_ActivateDeviceEx()
 K.COREDLL!xxx_ActivateDeviceEx() ★★
 SERVICESENUM!DeviceFolder::LoadDevice()
 SERVICESENUM!ServicesEnum::ActivateAllChildDrivers()
 SERVICESENUM!ServicesEnum::PostInit()
 SERVICESENUM!DefaultBusDriver::FastIOControl()
 SERVICESENUM!ServicesEnum::FastIOControl()
 SERVICESENUM!DefaultBusDriver::IOControl()
 SERVICESENUM!IOControl()
 DEVMGR!DriverFilterMgr::DriverControl()
 DEVMGR!DriverControl()
 DEVMGR!IoPckManager::DevDeviceIoControl()
 DEVMGR!DevDeviceIoControl()
 DEVMGR!DM_DevDeviceIoControl()
 KERNEL!MDCallKernelHAPI()
 KERNEL!NKHandleCall()
 K.COREDLL!DirectHandleCall()
 K.COREDLL!xxx_DeviceIoControl()
 DEVMGR!DevicePostInit()
 DEVMGR!DeviceContent::EnableDevice()

devmgr.dll の DeviceContent::EnableDevice() が呼び出されるまでの経路は、前項の「サービスの自動起動処理」に示したコールスタックと同じですから、ここでは省略します。上のコールスタックでは、★★を付けた行に注目して下さい。ServiceEnum.dll の DeviceFolder::LoadDevice() から ActivateDeviceEx() が呼び出されています。実は、サービスに対しても、デバイスマネージャ内部では、ロードする際には ActivateDeviceEx() が呼び出されるのです。これは、アプリケーションから ActivateService() を呼び出してサービスを起動する場合も同じです。ActivateService() によってデバイスマネージャにサービスのロードが要求されると、デバイスマネージャは、上のコールスタックと同様、ServiceEnum を介して ActivateDeviceEx() を呼び出すのです。

■ホストプロセスを複数起動する
ユーザモードドライバやサービスを割り当てるホストプロセスは、デバイスマネージャ内の Reflector において ID により識別され、必要に応じて(つまり、UserProcGroup で指定された ID のホストプロセスが起動済みでなければ)起動されることを上で述べました。ユーザモードドライバとサービスを割り当てるホストプロセスの ID は、それぞれデフォルト値があり、ユーザモードドライバのホストプロセス(udevice.exe)は 3 で、サービスのホストプロセスは 2 です。このデフォルト値は、
 %_WINCEROOT%/public/common/oak/files/common.reg
で定義されています。common.reg の中にある、PROCGROUP_DRIVER_MSFT_DEFAULT というのが udevice.exe のデフォルト ID で、PROCGROUP_SERVICE_MSFT_DEFAULT が、servicesd.exe のデフォルト ID です。

ユーザモードドライバやサービスに対するレジストリ設定で、デフォルトの ID 以外の値を UserProcGroup に指定すると、デフォルトのものとは別にホストプロセスが生成・起動されます。たとえば、FTP サーバ(ftpd)を組み込んだ OS イメージにおいて、 OS Design のレジストリ設定ファイル(OSDesign.reg)に次の行を追加すると、ftpd 専用の servicesd.exe が起動します。

[HKEY_LOCAL_MACHINE\Services\FTPD]
    "UserProcGroup"=dword:8

[HKEY_LOCAL_MACHINE\Drivers\ProcGroup_0008]
    "ProcName"="servicesd.exe"
    "ProcVolPrefix"="$services"
    "ProcTimeout"=dword:20000


上の例では、ID が 8 で servicesd.exe を実行するホストプロセスを設定して、そのプロセスに ftpd.dll がロードされるように、FTPD の UserProcGroup の値に 8 を指定しています。このようにすると、servicesd.exe のプロセスが二つ起動されて、二番目の方には ftpd.dll だけがロードされて動きます。下の図は、カーネルデバッガの「スレッド」ウィンドウで、ftpd.dll だけがロードされた servicesd.exe を表示した画面です。

ftpd.dll だけをロードした servicesd.exe

ftpd.dll だけをロードした servicesd.exe

■Reflector によるホストプロセスの呼び出し
ところで、カーネルの一部であるデバイスマネージャ内の Reflector から、ホストプロセスのユーザモードドライバやサービスを呼び出す処理は、どうなっているのでしょうか?

アプリケーションからカーネルモードのデバイスドライバを呼び出す場合であれば、DeviceIoControl() の呼び出しによってシステムコールが実行され、カーネルに制御が移ったのちに、カーネル内部でデバイスマネージャからデバイスドライバが呼び出されます。しかし、ユーザモードドライバの場合には、カーネルモードからユーザモードへの遷移が必要です。

カーネル内部からユーザプロセス内の DLL を呼び出す機能は、カーネル内部で実装されており、システムコールと似た仕組みです。カーネルのソースコードでいうと、
 %_WINCEROOT%/private/winceos/coreos/nk/kernel/apicall.c
にある NKHandleCall() の中で呼び出している MDCallUserHAPI() という関数が、カーネルからユーザプロセスを呼び出すためのものです。この MDCallUserHAPI() は、MD (Machine Dependent) という接頭辞の通り、CPU アーキテクチャごとに異なる実装となり、アセンブラで書かれています。WEC 7 の場合ですと、ARM, MIPS, x86 用のソースが、それぞれ次の場所にあります:

 %_WINCEROOT%/private/winceos/coreos/nk/kernel/arm/armtrap.s
 %_WINCEROOT%/private/winceos/coreos/nk/kernel/mips/except.s
 %_WINCEROOT%/private/winceos/coreos/nk/kernel/x86/fault.c (※インラインアセンブラ)

MDCallUserHAPI() を呼び出している NKHandleCall() は、API の実体を呼び出すための関数ですが、API の「ハンドル」がユーザモードに所属している場合は、MDCallUserHAPI() によってカーネルモードからユーザモードへの遷移を伴う呼び出しを行い、それ以外の場合は、カーネル内部での呼び出しを実行します。カーネルモードからユーザモードへの遷移処理の実体は、apicall.c にある SetupCallToUserServer() (および、この関数から呼び出される、各種プロセッサ依存の実装を持つ関数)です。

■WinCE 6.0 以前の仕組み
さて、WinCE 6.0 以前、つまり WinCE 5.0 までは、デバイスドライバは、全てユーザモードで動作していました。

WinCE 5.0 までは、純粋なマイクロカーネル構造であり、デバイスマネージャは、カーネルにロードされる DLL ではなく、マイクロカーネルとは独立したプロセス(device.exe)だったのです。各デバイスドライバは、ユーザプロセスで動作するため、通常のアプリケーションと同様に API を呼び出すことができ、デバイスドライバが直接 GUI 表示を行うことも可能だったようです。そして、ユーザモードで動作するデバイスドライバがハードウェアを直接制御できるように、ユーザモードから物理アドレスを直接アクセスすることが可能になっていました。

WinCE 5.0 から WinCE 6.0 への移行において、この構造に見直しが加えられ、カーネルランドとユーザランドを明確に区別して、ユーザモードから物理アドレスを直接アクセスできないようになったのです。それに合わせて、仮想記憶機構にも大幅な変更が加えられています。WinCE 6.0 において、デバイスマネージャが、カーネルから独立したプロセスではなくカーネルにロードされる DLL となったことに伴い、デバイスドライバは、デフォルトではカーネルモードで動作するようになりました。これは、パフォーマンスの面では有利である一方、システムの堅牢性という観点から見ると、好ましくない面があります。前回のエントリでも述べたように、デバイスドライバのバグによって、カーネル全体が障害を起こしてしまう可能性があるからです。

そのため、WinCE 6.0 では、(WinCE 5.0 までと同様に)ユーザモードでデバイスドライバを動かすことも可能なように、User Mode Driver Framework が導入されたのです。WinCE 5.0 から WinCE 6.0 への移行における、デバイスドライバ回りのアーキテクチャの変更については、WinCE 6.0 のβ版がリリースされた頃に提供されたと思われるドキュメントが Microsoft 社のサイトからダウンロードできますので、そちらをご覧になると、参考になるでしょう。

 Future Directions For The Windows CE Device Driver Architecture
 http://download.microsoft.com/download/5/b/9/5b97017b-e28a-4bae-ba48-174cf47d23cd/WCE030_WH06.ppt

WinCE 5.0 までのデバイスドライバと、WinCE 6.0 以降のユーザモードドライバを比べると、物理アドレスを直接アクセスできるかどうかという点が異なります。また、アプリケーションからデバイスドライバを呼び出す場合のオーバーヘッドを考えると、WinCE 6.0 以降のユーザモードドライバは、Reflector を介するために、WinCE 5.0 までのドライバ呼び出しに加えると、若干オーバーヘッドが大きくなっていると思われます(※一方、WinCE 6.0 のカーネルモードドライバは、WinCE 5.0 までとは異なり、カーネルからデバイスマネージャのプロセスを呼び出すシーケンスがありませんから、上述したように、オーバーヘッドが小さくなっていると考えられます)。

デバイスドライバから GUI 表示を行う場合のことを考えると、WinCE 6.0 以降では、ハードウェアを制御する部分をカーネルモードのドライバ、GUI 表示を行う部分をユーザモードドライバとして、分割しなければならず、WinCE 5.0 までのデバイスドライバに慣れ親しんだ開発者にとっては、不便に思える変更だったことでしょう。

なお、堅牢性と柔軟性の観点から考えると、今回のエントリで説明したように、WinCE 6.0 で導入された User Mode Driver Framework では、個々のユーザモードドライバを、それぞれ異なるホストプロセス(udevice.exe)に割り当てることも可能なため、WinCE 5.0 までのドライバアーキテクチャよりも強力だと言えるんじゃないかと思います。開発途上の、不安定なデバイスドライバは、レジストリ設定で専用のホストプロセスを割り当てて動かし、安定した時点で、他のユーザモードドライバと同じホストプロセスへ移す、といった開発の進め方も可能になっているからです。

■マイクロカーネルの考え方~Android との比較
今回のエントリを終える前に、マイクロカーネルの考え方を採用した他の OS として、Android について書いてみます。皆さんご存じの通り、Android は、カーネルに Linux カーネルを用いて構築された OS であり、マイクロカーネルの OS ではありません。WinCE 5.0 を除いて、現在市場で広く使われている純粋マイクロカーネル構造の OS といえば、前回も述べた QNX があります。

しかし、Android という OS の設計には、マイクロカーネルの考え方を踏襲している部分があると僕は思うのです。おそらくは30代以上の、マニアックな OS を好きな方なら、もしかすると BeOS という OS のことをご存じかも知れません。1990年代の後半、SMP 型のマルチプロセッサ機に対応し、パーソナルコンピュータの分野で普及することを目指した OS が BeOS です。実は、Android の 1.0 が発表された頃、公表されていた Android 開発チームのコアメンバー10数人の半分は、その BeOS に関わっていたエンジニアでした。そのためか、Android の内部には、BeOS に由来する仕組みが残っているようです。

特に、Linux カーネルに独自の改変を加える形で実現されている、”binder” という名前の軽量なプロセス間通信機構および、binder を支える “ashmem” という名前の共有メモリ機構は、BeOS の考え方を踏襲したものだと僕には思えます。Android では、Linux カーネルを採用してはいるものの、ユーザランドは全く独自であり、Linux ディストリビューションと呼べる存在では、ありません。Java API として提供される各種ミドルウェアの内部実装を見ると、Linux カーネルを一種のマイクロカーネルとして使い、各種ミドルウェア内部で動作するサービスモジュール(サービスプロセス)群が、軽量プロセス間通信機構と共有メモリ機構を駆使して連携する仕組みとなっているように思われます。

WinCE 5.0 までのデバイスマネージャのような、デバイスドライバをホストするサービスプロセスこそありませんが、音声や映像の入出力・レンダリング処理を司る media server というサービスプロセスなどは、BeOS のファンだった僕にとって、その世界を彷彿とさせるものです。

Android では、それらのサービスプロセス群を起動・監視する役割を担うプロセスとして init が動作し、あるサービスが障害により動作を停止したり強制終了してしまった場合には、それを再起動する仕組みになっているようです。これは、OS が提供するシステム機能を、複数のサービスプロセスに分割して、堅牢性を高めるという、マイクロカーネル構造の考え方に通じるものだと思います。

Android は、Linux カーネルを採用することで、Linux 用に開発されたデバイスドライバをそのまま流用し、そのうえで、開発チームが慣れ親しんでいた BeOS の構造を踏襲した設計を行った OS ではないかというのが、僕の想像です。

振り返って WEC/WinCE を見てみると、WinCE 5.0 までの純粋マイクロカーネル構造から、UNIX 系 OS に近い(そして、WindowsNT 系統のカーネルにも近い)メモリモデルやデバイスドライバモデルに移行しつつも、純粋マイクロカーネル構造の時からモジュール同士のインタフェースを大幅に変更することなく(※実際、デバイスマネージャは、.exe から .dll に変わったものの、インタフェースと内部の構造には、必要最小限の変更しか加わっていません)、移行前の資産のうち活かせる部分は残して、堅牢性を高める工夫をしたと言えるんじゃないかと思います。そして、前回と今回の二回にわたってとりあげた User Mode Driver Framework の設計は、可用性を高める効用があったと評価できる、と思うのです。

Add comment 2012/12/03 koga

WinCE/WEC のウォッチドッグタイマ機能

■ウォッチドッグタイマの必要性
組み込み機器、特に、PC のような対話的な操作ができない、ヘッドレスのデバイスなどでは、より高い信頼性や耐障害性が求められます。逆に、タッチパネルディスプレイのような、表示と入力のインタフェースを備えたデバイスであれば、PC と同様に、アプリケーションがフリーズするなど異常が起きた時に、デバイスを操作するユーザが異常に気づき、アプリケーションを終了したり、デバイスをリセットしたりして、すぐに対処できます。

しかし、ユーザが対話的に操作せずに、常時稼働するタイプの組み込み機器の場合には、アプリケーションや OS がフリーズした場合、自動的に異常を復旧する仕組みがないと、機器が異常を起こしたままになってしまいます。その結果、深刻な問題を引き起こすおそれがあります。たとえば、自動販売機がフリーズして動かなくなってしまったり、工場のラインの自動制御システムで、制御装置のアプリケーションがフリーズした場合のことを、考えてみて下さい。

アプリケーションや OS がフリーズした場合に、デバイスをリセットしたり、あるいは、フリーズしたアプリケーションを終了して再始動したりするための仕組みとして、ウォッチドッグタイマがあります。Linux であれば、/dev/watchdog として提供されており、ウォッチドッグタイマを更新する watchdog デーモンも提供されています:

 The Linux Watchdog driver API
 http://kernel.org/doc/Documentation/watchdog/watchdog-api.txt

 watchdog(8)
 http://linux.die.net/man/8/watchdog

組み込み機器向けのプロセッサは、ウォッチドッグタイマを内蔵しているものが多く、それを利用するのが一般的です。ハードウェアのウォッチドッグタイマ機能がない場合には、OS のカーネルで、ソフトウェアによるウォッチドッグタイマ機能が提供されることもあります。Linux の場合、/dev/watchdog のウォッチドッグタイマ機能は、ハードウェアで実装されることもあれば、/dev/watchdog はデフォルト実装のカーネル内部のソフトウェアタイマとして提供し、プロセッサ内蔵のウォッチドッグタイマに対しては、別途ブートローダで設定する、という仕組みで提供されることもあるようです。PC 用の Linux の場合、/dev/watchdog は、BIOS の機能を使ったハードウェアタイマとして実装されているようです。

■ハードウェアタイマとソフトウェアタイマ
ウォッチドッグタイマ機能の、ハードウェアによる実装(ハードウェアタイマ)とソフトウェアによる実装(ソフトウェアタイマ)の違いは、何でしょう?

答えは、OS がフリーズした場合に動作できるかどうかです。ハードウェアタイマの場合は、OS が動作しているかどうかに関係なく、独立したハードウェアの機能として動作しますから、たとえ OS 全体がフリーズしていても、設定した通りに動作します。つまり、設定したタイマ満了時間以内にタイマを更新(リフレッシュ)しなければ、タイマが満了した時点で、プロセッサが強制リセットされます。一方、ソフトウェアタイマの場合は、OS 全体がフリーズすると、タイマの動作も停止してしまいますから、タイマ満了時間が過ぎても、何も起きません。つまり、ソフトウェア実装のウォッチドッグタイマ機能は、OS がフリーズした場合には、役に立たないのです。

■ソフトウェアタイマの長所
OS 全体がフリーズした場合(カーネルのスケジューラも停止した場合)には機能しない、という点は、ソフトウェアタイマの短所です。一方、長所もあります。ハードウェアタイマは、一つしか満了時間を設定できませんが、ソフトウェアタイマには、ハードウェアの制約がありませんので、複数のタイマを設定できるように実装することが可能です。

ウォッチドッグタイマを使う目的は、OS や、特定のアプリケーション(プロセス)、あるいはスレッドが、正常に動作し続けているかどうかを監視することにあります。ウォッチドッグタイマが満了した場合、何らかの障害が起きて、それらの監視対象が動作していないということを意味します。そのため、ウォッチドッグタイマが満了した場合は、OS をリブートするなどの、異常からの復旧処理を発火させる、というわけです。

さて、ソフトウェアタイマによって、複数のウォッチドッグタイマを設定できるということは、監視対象を複数にできる、ということです。つまり、複数のウォッチドッグタイマを設定できるなら、常時稼働していなければならないアプリケーション(プロセス)やスレッドが複数ある場合に、それらに対して個別にウォッチドッグタイマを設定し、どれか一つでも動作していないことが検出されたら、復旧処理を発火できます。WinCE/WEC では、それが可能です。さらに、ウォッチドッグタイマの満了時に発火させる復旧処理として、OS をリブートする以外の動作を設定できます。

■WinCE/WEC のウォッチドッグタイマ機能
WinCE/WEC のウォッチドッグタイマ機能には、ハードウェアタイマとソフトウェアタイマがあり、ソフトウェアタイマは、カーネルの API を使って利用できます。ソフトウェア実装のウォッチドッグタイマについては、リファレンスの次のページをご覧下さい:

 CreateWatchDogTimer (Windows Embedded CE 6.0)
 http://msdn.microsoft.com/en-US/library/ee482966(v=winembedded.60).aspx

 CreateWatchDogTimer (Windows Embedded Compact 7)
 http://msdn.microsoft.com/en-us/library/ee482966.aspx

上のページで説明されている CreateWatchDogTimer() を使って、ソフトウェア実装のウォッチドッグタイマを設定できます。この関数では、タイマの満了時間に加え、タイマ満了時に発火させるアクションを指定できます。指定できるアクションは、次の三つです:

  • WDOG_KILL_PROCESS    監視対象のプロセスを強制終了する
  • WDOG_NO_DFLT_ACTION  何もしない
  • WDOG_RESET_DEVICE    OS をリブート(デバイスをリセット)する

CreateWatchDogTimer() は、戻り値として、ウォッチドッグタイマのハンドルを返します。このハンドルは、non signaled な状態ですが、タイマが満了した時点で、signaled な状態にセットされます。タイマ満了時に発火させるアクションとして、WDOG_NO_DFLT_ACTION を指定すると、ウォッチドッグタイマのハンドルに対して WaitForSingleObject() を呼び出すことにより、タイマが満了したことを検出できます。アプリケーション内で、特定のスレッドが動作しなくなったことを検出したいなどの場合には、WDOG_NO_DFLT_ACTION を使うのが便利でしょう。

CreateWatchDogTimer() で生成したウォッチドッグタイマを更新(リフレッシュ)するには、RefreshWatchDogTimer() を呼び出します。ウォッチドッグタイマを生成した後、StartWatchDogTimer() でタイマを始動したら、CreateWatchDogTimer() の dwPeriod 引数で指定したタイムアウト時間よりも短い間隔で、RefreshWatchDogTimer() を繰り返し呼び出さなければいけません。そうしないと、タイマが満了してしまいます。

次に、ハードウェアタイマについて見てみましょう。

ハードウェアタイマに対しては、API は提供されておらず、実装用のインタフェースが、カーネル移植レイヤ(OAL)で定義されています。ハードウェアがウォッチドッグタイマをサポートしていない場合は、OAL の初期化関数である OEMInit() の中で、ウォッチドッグタイマのインタフェース設定を変更せず、デフォルトのままにします。デフォルトでは、ハードウェアタイマ機能は、使用されません。ハードウェアタイマを有効にする場合は、OAL において、ハードウェアタイマの初期化処理とリフレッシュ処理を実装し、ウォッチドッグタイマのインタフェースを設定します。具体的には、OEMGLOBAL 構造体の dwWatchDogPeriod と pfnRefreshWatchDog を、OEMInit() で設定し、pfnRefreshWatchDog に設定した関数で、リフレッシュ処理(および、必要ならハードウェアタイマの初期化処理も)を実行します。 dwWatchDogPeriod は、デフォルトでは 0 に設定されており、この値が 0 の場合は、ハードウェアタイマが存在しないものとして扱われます。なお、pfnRefreshWatchDog のデフォルト値は、NULL ではなく、何もしない関数を指す関数ポインタです。

 dwOEMWatchDogPeriod (Windows Embedded CE 6.0)
 http://msdn.microsoft.com/en-US/library/ee479342(v=winembedded.60).aspx

 pfnOEMRefreshWatchDog (Windows Embedded CE 6.0)
 http://msdn.microsoft.com/en-US/library/ee479294(v=winembedded.60).aspx

 OEMGLOBAL (Windows Embedded CE 6.0)
 http://msdn.microsoft.com/en-US/library/ee478176(v=winembedded.60).aspx

上は、WinCE 6.0 のリファレンスです。WEC 7 のリファレンスは、以下にあります。

 dwWatchDogPeriod (Windows Embedded Compact 7)
 http://msdn.microsoft.com/en-us/library/ee479342.aspx

 OEMRefreshWatchDog (Windows Embedded Compact 7)
 http://msdn.microsoft.com/en-us/library/ee479294.aspx

 OEMGLOBAL (Windows Embedded Compact 7)
 http://msdn.microsoft.com/en-us/library/ee478176.aspx

ハードウェアタイマに対しては、OAL で設定したリフレッシュ間隔(dwWatchDogPeriod)以内の間隔で、WinCE/WEC カーネルが定期的に pfnRefreshWatchDog の関数を呼び出すことにより、リフレッシュ動作を行います。一方、ソフトウェアタイマの場合は、CreateWatchDogTimer() を呼び出したアプリケーション自身が、リフレッシュ動作を行います(あるいは、監視対象のアプリケーションと、監視するアプリケーションの二つに役割を分割し、監視する役割のアプリケーションが CreateWatchDogTimer() を呼び出し、監視対象のアプリケーションが RefreshWatchDogTimer() を呼び出す、というやり方も考えられます)。

■ウォッチドッグタイマ機能の実装を見てみる
上で述べた、WinCE/WEC のウォッチドッグタイマ機能について、どのように実装されているのか、カーネルのソースコードを見てみるのも、面白いでしょう。カーネルが提供するウォッチドッグタイマ機能のソースコードは、
 %_WINCEROOT%/PRIVATE/WINCEOS/COREOS/NK/KERNEL/watchdog.c
にあります。wachdog.c で実装されている WatchDogTimerThrd() が、ウォッチドッグタイマのカーネルスレッドが実行する手続きです。

ウォッチドッグタイマのカーネルスレッドは、ハードウェアタイマが存在する場合か、または、CreateWatchDogTimer() が呼び出された場合に生成されます(*1)。ハードウェアタイマが存在する場合、つまり、OEMInit() の中で、OAL によって OEMGLOBAL 構造体の dwWatchDogPeriod と pfnRefreshWatchDog が設定された場合は、カーネルのスケジューラが初期化時に呼び出す InitWatchDog() の中で、ウォッチドッグタイマのカーネルスレッドを生成・始動します。ハードウェアタイマが存在しない場合には、CreateWatchDogTimer() が最初に呼び出された時に、ウォッチドッグタイマのカーネルスレッドが生成・始動されます(*1)

(2012-04-19 追記)
*1: これは、WinCE 6.0 の場合です。WEC 7 のカーネルでは、ハードウェアタイマが存在するか否かに関わらず、
InitWatchDog() において、ウォッチドッグタイマのカーネルスレッドが生成されます。

ウォッチドッグタイマのカーネルスレッドは、ハードウェアタイマとソフトウェアタイマの両方を管理します。WatchDogTimerThrd() の実装を見ると分かりますが、システムが終了するまで実行を続けるループの中で、ハードウェアタイマと全てのソフトウェアタイマのタイマ満了時刻に従って待ち動作を行い、適切なタイミングで、各タイマをリフレッシュします。ハードウェアタイマもソフトウェアタイマも存在しない状態では、待ち時間を無限に設定して待ち動作を行います。この待ち動作は、CreateWatchDogTimer() や StartWatchDogTimer(), StopWatchDogTimer() を呼び出した際に解除され、ウォッチドッグタイマのカーネルスレッドが、待ち時間を再調整します。

ハードウェアタイマについては、たとえば、WinCE 6.0 のデバイスエミュレータであれば、以下のソースコードを見て下さい:
 %_WINCEROOT%/PLATFORM/DEVICEEMULATOR/src\oal/oallib/watchdog.c
 %_WINCEROOT%/PLATFORM/DEVICEEMULATOR/src\oal/oallib/timer.c
 %_WINCEROOT%/PLATFORM/DEVICEEMULATOR/src\oal/oallib/init.c

上のソースファイルのうち、watchdog.c にある SMDKInitWatchDogTimer() と RefreshWatchdogTimer() が、ハードウェアタイマ用の初期設定と、ハードウェアタイマのリフレッシュ処理を行う関数です。SMDKInitWatchDogTimer() は、timer.c にある OALTimerInit() を経由して、init.c にある OEMInit() から呼び出されます。つまり、OEMInit() の中で、(SMDKInitWatchDogTimer() によって)OEMGLOBAL 構造体の dwWatchDogPeriod と pfnRefreshWatchDog が設定されます。RefreshWatchdogTimer() は、最初に呼び出された時は、プロセッサ(S3C2410)内蔵のウォッチドッグタイマを初期化し、それ以降の呼び出しでは、ウォッチドッグタイマをリフレッシュ(タイマのカウンタをリセット)します。

なお、SMDKInitWatchDogTimer() を見ると、dwWatchDogPeriod と pfnRefreshWatchDog という変数に対して代入を行っていますが、これらの名前は、OEMGLOBAL 型の大域変数のメンバを指すように #define されています。興味のある方は、
 %_WINCEROOT%/PUBLIC/COMMON/OAK/INC/bcoemglobal.h
をご覧になってみて下さい。

Add comment 2012/03/19 koga

WinCE デバイスのリモート操作~その2(2/2)

前回のエントリで、WinCE/WEC の「リモートディスプレイアプリケーション」のソースディレクトリについて述べました。
 %_WINCEROOT%/PUBLIC/COMMON/OAK/DRIVERS/CERDISP/
の配下には、前回のエントリで紹介した CERDisp と CERHost の他に、CERDRV というソースディレクトリがあるのです。今回は、前回触れなかった CERDRV について紹介します。

■CERDRV の組み込み
%_WINCEROOT%/PUBLIC/COMMON/OAK/DRIVERS/CERDISP/ の下にある CERDRV ディレクトリには、ddi_cer という、仮想フレームバッファ機能を提供するディスプレイドライバのソースファイルが収録されています。より正確には、CERDRV ディレクトリは、ddi_cer_lib.lib という static library のソースディレクトリであって、
 %_WINCEROOT%/PUBLIC/COMMON/CESYSGEN/makefile
に記述された設定によって、ddi_cer.dll が生成されます。%_WINCEROOT%/PUBLIC/COMMON/ 配下のソースのビルドについては、2011/08/14 のエントリも、ご覧になってみて下さい。

ddi_cer を OS イメージに組み込むには、BSP_DISPLAY_CER という環境変数を設定して下さい。このことは、
 %_WINCEROOT%/PUBLIC/COMMON/OAK/FILES/common.bib
に書かれています。common.bib の、CE_MODULES_DISPLAY に対する条件記述は、以下のようになっています。


; @CESYSGEN IF CE_MODULES_DISPLAY
; @CESYSGEN IF CE_MODULES_MULTIMON
   multimon.dll         $(_FLATRELEASEDIR)\multimon.dll        NK   SHK
; @CESYSGEN ENDIF CE_MODULES_MULTIMON

IF BSP_NODISPLAY !
;
; MGDI Display drivers
;
;       To use driver           set this env var
;
;       ddi_flat.dll            BSP_DISPLAY_FLAT
;       ddi_3dr.dll             BSP_DISPLAY_SMI3DR
;       ddi_ragexl.dll          BSP_DISPLAY_RAGEXL
;       ddi_cer.dll             BSP_DISPLAY_CER
;       ddi_nop.dll             BSP_DISPLAY_NOP

IF BSP_DISPLAY_NOP
   ddi_nop.dll          $(_FLATRELEASEDIR)\ddi_nop.dll               NK  SHK
ENDIF BSP_DISPLAY_NOP
IF BSP_DISPLAY_CER
   ddi_cer.dll          $(_FLATRELEASEDIR)\ddi_cer.dll               NK  SHK
ENDIF BSP_DISPLAY_CER
…(途中略)

ENDIF BSP_NODISPLAY !
; @CESYSGEN ENDIF CE_MODULES_DISPLAY

最後の方にある、IF BSP_DISPLAY_CER と ENDIF BSP_DISPLAY_CER で囲われた行を見て下さい。

また、
 %_WINCEROOT%/PUBLIC/COMMON/OAK/FILES/common.reg
には、以下の行があります。


IF BSP_DISPLAY_CER
[HKEY_LOCAL_MACHINE\System\GDI\Drivers]
    "Display"="ddi_cer.dll"
ENDIF

Platform Builder で環境変数 BSP_DISPLAY_CER を設定することにより、common.bib と common.reg の上記個所が有効になり、ddi_cer.dll が OS イメージに組み込まれる、というわけです。

なお、お使いの BSP にディスプレイドライバが付属している場合には、BSP 付属のディスプレイドライバを無効にして下さい。BSP 付属のディスプレイドライバを無効にする方法ですが、platform.reg の中に、レジストリキー [HKEY_LOCAL_MACHINE\System\GDI\Drivers] に対する設定行があれば、ddi_cer.dll を組み込む時は、その行をコメントアウトしたうえで、環境変数 BSP_DISPLAY_CER を設定して OS イメージをビルドして下さい。

ここで、platform.reg は、BSP 固有のレジストリ設定を記述するファイルであり、
 %_WINCEROOT%/PLATFORM/<BSP 名>/FILES/
に配置されています。

■WinCE/WEC のディスプレイドライバ
ddi_cer について、「仮想フレームバッファ機能を提供するディスプレイドライバ」と書きましたが、これについて、もう少しだけ詳しく書きます。

まず、WinCE/WEC のディスプレイドライバの考え方については、WinCE 6.0 のリファレンスの、以下のページが参考になります:

 Display Driver Development Concepts
 http://msdn.microsoft.com/en-US/library/ee485877(v=WinEmbedded.60).aspx

WEC 7 のリファレンスのディスプレイドライバの章には、このページに相当する、考え方(concept)を説明したページは無いようです:

 Display Drivers (Windows Embedded Compact 7)
 http://msdn.microsoft.com/en-us/library/gg159569.aspx

WinCE 6.0 と WEC 7 では、ディスプレイドライバのアーキテクチャは変わっていません。ディスプレイドライバの役割は、おおざっぱにいえば、GWES の描画エンジン機能が生成したレンダリング結果を、フレームバッファ上で合成・蓄積し、ディスプレイへ出力することです。そして、仮想フレームバッファ機能というのは、RAM 上にフレームバッファ領域を割り当て、「GWES の描画エンジン機能が生成したレンダリング結果を、フレームバッファ上で合成・蓄積」するだけの機能のことです。

Linux の場合、同様の仮想フレームバッファ機能として、X Window System の Xvfb (X virtual framebuffer) や、Linux framebuffer (fbdev) があるようです:

 Xvfb
 http://en.wikipedia.org/wiki/Xvfb

 Linux framebuffer
 http://en.wikipedia.org/wiki/Linux_framebuffer

さて、ddi_cer による仮想フレームバッファ機能と、前回紹介した「リモートディスプレイアプリケーション」(CERDisp と CERHost)を組み合わせれば、ディスプレイを持たない「ヘッドレス」のデバイスで、GUI 画面によるリモート操作を行うことが可能です。ディスプレイ付きのデバイスの場合は、CERDisp と CERHost により、デバイスに接続されたディスプレイと PC のディスプレイの両方に、WinCE の画面が表示されます。これに対し、ヘッドレスのデバイスの場合(または、ディスプレイ付きのデバイスのディスプレイドライバを無効にして、ddi_cer を組み込んだ場合)は、PC のディスプレイにだけ WinCE の画面が表示される、というわけです。

x86 ベースのボード/デバイスを除く、組み込み機器用プロセッサを用いたデバイスの場合、フレームバッファ領域は、専用の VRAM ではなく、RAM 上に確保するのが一般的です。従って、それらのデバイスについて考えると、実は、ddi_cer と通常のディスプレイドライバの違いは、フレームバッファの内容をディスプレイへ出力するかどうかだけなのです。

ただし、ddi_cer には、CERDisp と同じ機能も実装されていますので、フレームバッファ内容をリモート表示するディスプレイドライバ、という見方もできます。WinCE 6.0 のリファレンスでは、ddi_cer は “Windows Embedded CE remote graphics adapter.” と説明されています:
 http://msdn.microsoft.com/en-us/library/ee482180(v=winembedded.60).aspx (Common Windows Embedded CE Modules)

ddi_cer に実装されている CERDisp 相当の機能は、レジストリ設定でオフにすることができます。ddi_cer のレジストリ項目については、
 %_WINCEROOT%/PUBLIC/COMMON/OAK/DRIVERS/CERDISP/CERDRV/
に入っている cerdrv.reg を見て下さい。cerdrv.reg には、二つのレジストリキーが記述されています。[HKEY_LOCAL_MACHINE\System\GDI\Drivers] と [HKEY_LOCAL_MACHINE\Drivers\Display\DDI_CER] です。このうち、[HKEY_LOCAL_MACHINE\System\GDI\Drivers] の方は、上述した common.reg に記述されているものと同じです。cerdrv.reg の内容は、ビルド時に OS イメージに反映されませんので、[HKEY_LOCAL_MACHINE\Drivers\Display\DDI_CER] キーの内容については、platform.reg か project.reg にコピーするのがよいでしょう。

ddi_cer の、CERDisp 相当の機能をオフにするには、[HKEY_LOCAL_MACHINE\Drivers\Display\DDI_CER] キーの NoServer サブキーを 1 に設定して下さい。ddi_cer は、NoServer の値が 1 に設定されていると、CERDisp と同様の動作を行うサーバースレッドを始動せず、仮想フレームバッファ動作のみを行います。デフォルトでは、ddi_cer の CERDisp 相当の機能が有効なので、CERDisp を実行せず、PC 上で CERHost を実行するだけで接続することができます。NoServer を 1 に設定した場合は、CERDisp も実行する必要があります。

ddi_cer の CERDsip 相当の機能と CERDisp との主な違いは、次の通りです:

 ・ddi_cer は、ディスプレイドライバとしてフレームバッファを自分が持っているため、画面内容をクライアントへ転送する際は、フレームバッファを直接アクセスする。
  CERDisp は、BitBlt() を使って画面内容をキャプチャしたのち、キャプチャした内容をクライアントへ転送する。

 ・ddi_cer は、カーネルモードドライバなので、TCP/IP 通信処理は、CERDisp の場合とは異なり、カーネルモジュール用の WinSock DLL がロードされる。

ここで、ddi_cer と CERDisp は、WinSock ライブラリとして、どちらも winsock.lib をリンクしています。従って、CERDisp に対しては、winsock.dll がロードされ、ddi_cer に対しては、k.winsock.dll がロードされます。カーネルモジュール用の DLL については、2008/07/22 に書いたエントリ(「カーネルからWinSock」)も、ご覧になってみて下さい。

なお、2007/07/22 のエントリでは、WinSock の DLL は、ws2.dll と k.ws2.dll だと書いており、ddi_cer と CERDisp がロードする DLL とは違っています。実は、winsock.dll(および k.winsock.dll)の方は、古い版の WinSock で、ws2.dll は、WinCE .NET 4.2 から導入された新しい版です。真相は分かりませんが、ddi_cer が k.winsock.dll を使うようになっているのは、2007/07/22 のエントリに書いた、k.ws2.dll に関する制限を回避するためなのかも知れません。
(あるいは、単に、ddi_cer も CERDsip も、WinCE .NET 4.2 より前から付属していて、[k.]ws2.dll を使うように改訂されていない、ということなのかも知れませんが。)

■通常のディスプレイドライバとの比較

興味のある人は、ddi_cer と、WinCE 6.0 のデバイスエミュレータのディスプレイドライバのソースを読み比べてみると、面白いんじゃないかと思います。WinCE 6.0 のデバイスエミュレータは、Samsung の S3C2410 という、ARM9 コアのプロセッサのリファレンスボードをエミュレーションしています。S3C2410 は、LCD コントローラを内蔵していますので、デバイスエミュレータのディスプレイドライバは、S3C2410 の LCD コントローラを制御する処理が実装されています。このドライバのソースコードを収録したディレクトリは、
 %_WINCEROOT%/PLATFORM/DEVICEEMULATOR/SRC/DRIVERS/DISPLAY/LCD
です。また、S3C2410 のデータシートは、
 http://www.alldatasheet.com/view.jsp?Searchword=s3c2410
などからダウンロードできます。

ddi_cer と、WinCE 6.0 のデバイスエミュレータのディスプレイドライバの大きな違いは、次の通りです:

・ddi_cer は、フレームバッファ領域を確保する際、C++ の new 演算子を使って確保する。従って、連続した物理メモリ領域は、確保しない。
 (※より正確には、GPE ライブラリの GPESurf クラスのインスタンスを生成し、GPESurf のコンストラクタによってバッファを確保します。)
 デバイスエミュレータのディスプレイドライバは、config.bib の設定により、フレームバッファとして予約した領域を使用する。

・ddi_cer は、ディスプレイデバイスを制御しない。GWES の描画エンジン機能が生成したレンダリング結果を、フレームバッファ上で合成・蓄積するのみ。ただし、レジストリ設定で NoServer が 0 に設定されているか、または何も設定されていない場合は、クライアントに対してフレームバッファ内容を定期的に転送し、「リモートディスプレイ」動作を行う。
 デバイスエミュレータのディスプレイドライバは、プロセッサ(S3C2410)の LCD コントローラのレジスタを、カーネル仮想アドレス空間にマップして、必要な制御動作を行う。LCD コントローラの制御動作の一部として、フレームバッファの物理アドレスを、DMA 転送対象領域として LCD コントローラに設定する。

つまり、異なるのは、ディスプレイ出力用のコントローラ(プロセッサ内蔵の LCD コントローラなど)を制御するかどうか、および、フレームバッファ領域として、連続した物理メモリ領域を確保するかどうかです。ディスプレイ出力用のコントローラを制御する、通常のディスプレイドライバの場合には、フレームバッファ全体を一括して DMA 転送できるように、フレームバッファを、連続した物理メモリ領域に確保する必要があります。

なお、デバイスエミュレータのディスプレイドライバでは、フレームバッファの物理アドレスを LCD コントローラに設定する処理は、行っていません。この処理は、OAL の初期化処理として、以下のソースファイルで実装されています:
 %_WINCEROOT%/PLATFORM/DEVICEEMULATOR/src/oal/oallib/init.c

init.c の中にある、OEMInit() から呼び出される InitDisplay() で、LCD コントローラの初期化を行い、その際に、フレームバッファの物理アドレスを設定しています。

LCD コントローラの初期化動作を OAL で行う実装になっているのは、カーネル起動時にスタートアップスクリーンを表示するためのようです。ここで、LCD コントローラに設定するフレームバッファのアドレスは、物理アドレスですが、ディスプレイドライバがフレームバッファ領域としてアクセスするアドレスは、カーネル仮想アドレスであることに注意して下さい。RAM の同じ領域であっても、ハードウェア(LCD コントローラ)は、物理アドレスでしかアクセスできず、逆に、カーネルモジュールは、物理アドレスをカーネル仮想アドレス空間にマップしたカーネル仮想アドレスでしかアクセスできません。

余談になりますが、連続した物理メモリ領域をフレームバッファに割り当てる際に、必ずしも config.bib で予約する必要は、ありません。AllocPhysMem() を使って動的に割り当てて使用することも可能です。ただし、AllocPhysMem() では、割り当て先の物理アドレスを指定できないので、ブートローダや OAL がスタートアップスクリーンを表示する際に使う領域と同じ領域を割り当てることが、できません。従って、フレームバッファとして使用する領域のサイズを、仕様として固定してしまえるのであれば、config.bib の設定で割り当てる方が、どちらかといえば簡単になるでしょう。

1 comment 2012/01/04 koga

カーネルからWinSock

カーネルモードで動作するデバイスドライバでは、アプリケーションから使える API を同じように呼び出すことは、できません。一般に、呼び出せる API には制限が加わります。ここで「API」と書きましたが、デバイスドライバから呼び出す関数やルーチンを “API”(Application Programming Interface) と呼ぶのは、適切ではありませんね。「システム関数」や「システムルーチン」と呼ぶべきなのかも知れません。

さて、デバイスドライバを実装する際に、アプリケーションと同じシステム関数を呼び出したい場合があります。たとえば、デバイスドライバから GUI 表示を行いたい場合や、TCP/IP 通信などのネットワーク入出力を行いたい場合です。WinCE 6.0 では、このような場合、そのデバイスドライバを、カーネル・モード・ドライバではなく、ユーザ・モード・ドライバとして動かすというのが、最も安直でしょう。

デバイスドライバをユーザ・モード・ドライバとして動かす場合、そのドライバに対する、.bib ファイルの MODULES セクションの記述と .reg ファイルのレジストリ項目記述による指定が必要です。.bib ファイルの MODULES セクションでは、Type エントリに記述するフラグから K を外すか、または、K の代わりに Q を指定しなければいけません。MSDN のリファレンスの、
 http://msdn.microsoft.com/en-us/library/aa908680.aspx
 http://msdn.microsoft.com/en-us/library/aa909662.aspx
が参考になるでしょう。.reg ファイルのレジストリ項目では、そのドライバのキーの Flags 値に 0×10(EVFLAGS_LOAD_AS_USERPROC)を設定します。これについては、MSDN のリファレンスの、
 http://msdn.microsoft.com/en-us/library/aa930532.aspx
に説明があります。

しかし、ユーザ・モード・ドライバの場合、アプリケーションからドライバを呼び出す際のオーバーヘッドが大きいという短所があります。カーネル・モード・ドライバの場合とは異なり、ユーザ・モード・ドライバの呼び出しでは、システムコールと Device Manager を介した先に、さらに User Mode Driver Reflector と User Mode Driver Host が介在するからです。この様子は、MSDN のリファレンスの
 http://msdn.microsoft.com/en-us/library/aa932450.aspx
で説明されています。また、今年8月号の『インターフェース』誌に掲載された解説記事にも、説明が載っているようです(第2章「–新しくなったOSアーキテクチャと変更点– Windows CEにおけるデバイス・ドライバ開発の実際」)。
 http://www.cqpub.co.jp/INTERFACE/contents/2008/JA/200808.htm

僕は『インターフェース』誌を購読していないので、記事の詳細は分かりませんが、参考になる記事ではないかと思います。ちなみに、上のページでは、記事の冒頭2ページが載った PDF をダウンロードできます。

ところで、ユーザ・モード・ドライバだと、アプリケーションからの呼び出しのオーバーヘッドが大きいので、カーネル・モード・ドライバとして動かしたい、と考えることも多いでしょう。なおかつ、そのドライバで、どうしても GUI 表示やネットワーク入出力処理を行いたい、という場合には、別の手段があります。カーネル・モード・ドライバから GUI 表示を行う場合には、CeCallUserProc() というカーネル関数を使います。この関数の説明は、MSDN のリファレンスの
 http://msdn.microsoft.com/en-us/library/aa909021.aspx
にあります。また、Windows CE Base Team Blog の
 http://blogs.msdn.com/ce_base/archive/2006/11/09/CE6-Drivers_3A00_-What-you-need-to-know.aspx
も参考になるでしょう。CeCallUserProc() をカーネル側から呼び出した場合、udevice.exe、つまり User Mode Driver Host を介して、指定した DLL のルーチンが呼び出されます。GUI の表示処理は、その DLL で実装しておくというわけです。

次は、GUI 表示ではなく、ネットワーク入出力です。実は、ネットワーク入出力については、WinSock API をカーネル・モード・ドライバから呼び出すことができるのです。つまり、GUI 表示の場合とは異なり、CeCallUserProc() を使わず、直接 WinSock API を呼び出すことが可能です。これについては、
 http://download.microsoft.com/download/a/0/9/a09e587c-4ff9-4a58-a854-56fe50b862b2/release%20notes.htm
に記載があります(”Do not use k.ws2.dll from kernel mode”)。このページでは、”k.ws2.dll を使わないように”という注意書きがあるのですが、これについては、
 http://msdn.microsoft.com/en-us/library/aa915026.aspx
を併せ読むのがよいでしょう。WinCE での WinSock API の DLL は ws2.dll であり、WinSock API を使うアプリケーションは、ws2.lib をリンクライブラリに加えてビルドします。この ws2.dll のカーネルモジュール用のものが k.ws2.dll というわけです。

上の二番目の Web ページの説明(”Loading DLLs in Kernel Mode or User Mode: Windows CE 5.0 vs. Windows Embedded CE 6.0″)で書かれているように、カーネル・モード・ドライバ、つまりカーネルモジュールがリンクした DLL をロードする際、WinCE 6.0 のカーネルは、リンク指定された DLL の名前の前に ‘k.’ を付けた DLL があれば、そちらを自動的にロードするようです。従って、カーネル・モード・ドライバのビルド設定で、リンクライブラリとして ws2.dll が指定されていた場合、k.ws2.dll がロードされます。しかし、上の一番目の Web ページの注意書きでは、k.ws2.dll を使わないようにと言っていますので、WinSock API を使うカーネル・モード・ドライバでは、リンクライブラリとして、ws2.lib(または k.ws2.lib)を指定せず、カーネルモジュール専用の ws2k.lib をリンクするのがよいでしょう。

なお、WinCE ではなく、WinXP までの WinNT 系カーネルでは、カーネルモジュールからネットワーク入出力を行う場合には、このようなことができず、TDI(Transport Driver Interface) を使う必要があったようです。Vista からは、この点が改善され、WinSock と同様のインタフェースを持つ WSK(WinSock Kernel) が提供されているそうです:
 http://www.exconn.net/Blogs/windows/archive/2006/03/04/7383.aspx
 http://www.atmarkit.co.jp/fwin2k/network/baswinlan017/baswinlan017_03.html
 http://www.themssforum.com/Drivers/Driver-serial-104389/

WinCE 6.0 の ws2k.dll は、この WSK と同じ位置づけだと言えるでしょう。ただし、現状では、ws2k.dll の呼び出しはカーネル側で完結せず、CeCallUserProc() を使ってユーザランド側の DLL を呼び出す場合と同様に、ユーザランド側のモジュールを介して、ユーザランド側から WinSock が呼び出される仕組みになっているようです。これは、上に挙げた
 http://download.microsoft.com/download/a/0/9/a09e587c-4ff9-4a58-a854-56fe50b862b2/release%20notes.htm
で説明されています。実際、WinSock API を使って TCP/IP 通信を行うカーネル・モード・ドライバを作り(※もちろん、そのドライバをビルドする際には、ws2k.lib をリンクします)、カーネルデバッガを接続して動かすと、その様子を見ることができます。たとえば、Ethrnet コントローラのドライバのソースのパケット送出関数にブレークポイントをセットして、そのブレークポイントがヒットした時のコールスタックを見ると、ws2serv.exe をロードした udevice.exe のスレッドからの呼び出しになっています。その状態で、当該デバイスドライバからの WinSock 呼び出しを見ると(※カーネルデバッガの「スレッド」メニューを使えば、executable ごとのスレッド一覧と、各スレッドのコールスタックを表示できます)、ws2k.dll から User Mode Driver Reflector を介して ws2serv.exe を呼び出し、その呼び出しの完了待ちになっている様子が分かります。

将来的には、Vista の WSK と同様、WinCE の ws2k.dll が提供するインタフェースの実装が全てカーネル側に置かれ、それによって、カーネル側からユーザランドを呼び出して戻るオーバーヘッド無しで、WinSock によるネットワーク入出力が可能になるのだと思われます。

Add comment 2008/07/22 koga


Categories

Links

Posts by Authors

Recent Posts

Calendar

2017年4月
« 9月    
 1
2345678
9101112131415
16171819202122
23242526272829
30  

Posts by Month

Posts by Category

Meta