Posts filed under 'カーネルモジュールの実装'
■ウォッチドッグタイマの必要性
組み込み機器、特に、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
をご覧になってみて下さい。
2012/03/19
koga
前回のエントリで、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 の設定で割り当てる方が、どちらかといえば簡単になるでしょう。
2012/01/04
koga
カーネルモードで動作するデバイスドライバでは、アプリケーションから使える 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 によるネットワーク入出力が可能になるのだと思われます。
2008/07/22
koga