Search Results for ‘なくなっ’

insmod と ActivateDevice()

今回は、WEC/WinCE の、カーネルモジュールを動的にロード/アンロードする方法について述べます。Linux を御存知の方であれば、insmod や modprobe、rmmod に相当するものだといえば分かるでしょう。

■デバイスドライバの動的ロードとアンロード
Linux の insmod コマンドに相当するコマンドプログラムは、WEC/WinCE には存在しませんが、同様のことを実現可能な API が提供されています。それが、ActivateDevice[Ex]() です。

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

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

これらの API を使ってデバイスドライバをロードする手順は、次の通りです:

1.) ロードするデバイスドライバを DeviceManager に登録するための、レジストリ項目を設定する。

2.) ActivateDevice[Ex]() を呼び出す。

デバイスドライバを使い終え、必要なくなったら、DeactivateDevice() を呼び出します。これで、カーネル内にロードされていたデバイスドライバの DLL がアンロードされます。

上の手順を insmod コマンドの場合と比べると、レジストリ設定が必要なぶん手順が一つ多いですが、十分シンプルだと言えるでしょう。レジストリに設定する項目は、そのドライバ用のサブキー配下の ‘Dll’ と ‘Prefix’ だけです。以下は、’MyDriver’ という名前で、デバイスファイル名に使われる3文字プレフィクスが ‘MYD’ というドライバの例です:


[HKEY_LOCAL_MACHINE\Drivers\AddOn\MyDriver]
   "Dll"="MyDriver.dll"
   "Prefix"="MYD"

上の例では、レジストリキー HKEY_LOCAL_MACHINE\Drivers の下に、動的ロード対象の意味で ‘AddOn’ というサブキーを割り当て、その下に、ドライバ用のサブキー ‘MyDriver’ を設定しています。

■LoadKernelLibrary()
ところで、Linux の insmod コマンドは、カーネルのローダブルモジュールをロードすることができ、対象はデバイスドライバに限定されていません。一方、WEC/WinCE の ActivateDevice[Ex]() は、DeviceManager が管理するデバイスドライバに対象が限定されています。

実は、WEC/WinCE にも、ローダブルモジュール(DLL)をカーネルにロードさせる API があります。LoadKernelLibrary というのが、その関数です。ただし、以下のリファレンスページで説明されているように、この API は、カーネルのログ機能を司る CeLog.dll と、カーネルデバッガの DLL をロードする用途に限定されています。実際には、それ以外の DLL もロードは可能だと思いますが、推奨はされないと考えて下さい。

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

WEC/WinCE カーネルのログ機能と、ログデータの解析ツール(Kernel Tracker)については、2012/08/16 に書いたエントリ(「CeLogFlush.exe と Kernel Tracker」)で紹介しました。このエントリで、ログ機能を有効にしていないデバイスに対しても、一時的にログ機能を有効にできると書きました。CeLogFlush.exe は、LoadKernelLibrary() を呼び出すことにより、カーネルに CeLog.dll をロードさせるのです。

■動的ロードについて、もう少し
DLL をプロセスにロードする API として、LoadLibrary() がありますが、カーネル内部でも、デバイスドライバをロードする際に LoadLibrary() を呼び出す場合があります。これについては、ActivateDeviceEx() のリファレンスで説明されています。

通常は、ドライバのロードには LoadDriver() という関数が使われるのですが、ロードするドライバに対して ‘Flags’ というレジストリ項目が設定されていて、その値が 0×2 ビット(DEVFLAGS_LOADLIBRARY)を含んでいる場合は、LoadLibrary() が使われます。LoadLibrary() と LoadDriver() の違いは、ロード対象の DLL に対してデマンドページングを行うかどうかです。LoadDriver() は、ロードする DLL をデマンドページングの対象外とします。

WEC/WinCE カーネルのデマンドページングについては、リファレンスの次のページで説明されています。

 Demand Paging Considerations (Windows Embedded CE 6.0)
 http://msdn.microsoft.com/en-us/library/ee482784(v=winembedded.60).aspx

時間制約の厳しいデバイスドライバの場合は、LoadDriver() によりデマンドページング無しでロードします。これが通常動作です。一方、サイズが大きく、かつ、時間制約も厳しくないドライバの場合には、LoadLibrary() を使うことにより、デマンドページング有りでロードすれば、メモリ使用量を抑えることが可能というわけです。

デバイスドライバにおいて、通常動作ではデマンドページング無しでロードするのは、リアルタイム性を確保するための仕組みです。WinCE 6.0 のリファレンスには、上のページを含む、“Real-Time Performance” という節があります。興味のある方は、ご覧になってみて下さい。

WEC 7 のリファレンスには、デマンドページングについて述べたページは見当たりませんが、割り込み応答動作のタイミングを計測するツール(ILTiming.exe)の説明などが載っています。こちらも、参考になるでしょう:

 ILTiming.exe Real-Time Measurement Tool (Windows Embedded Compact 7)
 http://msdn.microsoft.com/en-us/library/ee483144.aspx

Add comment 2013/02/10 koga

デバイスエミュレータで WEC 7 を動かす

最初にことわっておきますが、今回の内容は、WinCE 6.0 (Windows Embedded CE 6.0) と WEC 7 (Windows Embedded Compact 7 ) を両方お使いの方が主な対象です。WinCE 6.0 しか使っていらっしゃらない方には、特に関係ない内容ですし、また、WEC 7 だけをお使いの方にも、必要性は低い内容だと思います。もし、あなたが、WinCE 6.0 の ARM ベースのデバイスエミュレータをご利用になっていて、同じものを WEC 7 でも使えないだろうか?と思ったことがあるのなら、今回の内容は、あなたのためのものです。お急ぎなら、途中を飛ばして、今回のエントリの最後の方にある「DeviceEmulator BSP を WEC 7 へ移植する」を読んでみて下さい。

■WinCE 6.0 と Windows Mobile のデバイスエミュレータ
Visal Studio 2008 までは、Pro 以上の版に含まれる Smart Device Development 機能の一部として、ARM ベースのデバイスエミュレータが付属しています。このデバイスエミュレータは、単体でも提供されており、最新版の 3.0(VS 2008 付属のものと同じ版)を、以下のページからダウンロードできます:

 Microsoft Device Emulator 3.0
 http://www.microsoft.com/ja-jp/download/details.aspx?id=5352

デバイスエミュレータがエミュレートしているのは、Samsung 製の、S3C2410 という ARM9 コアのプロセッサを搭載したリファレンスボードです。WinCE 6.0 の Platform Builder をお使いの方ならご存知の通り、WinCE 6.0 には、デバイスエミュレータの BSP が付属しています。そのため、WinCE 6.0 を動かす実機がなくても、デバイスエミュレータの BSP を使って OS イメージをビルドすれば、その OS イメージをデバイスエミュレータで動かし、実機に依存しない部分の開発が可能です。

WinCE 5.0 までは、デバイスエミュレータには Virtual PC が使われていました。つまり、ターゲットプロセッサは x86 でした。WinCE 6.0 では、それが ARM プロセッサになった、というわけです。しかし、後述するように、WEC 7 では、再び Virtual PC がデバイスエミュレータとして採用されました。

以下に、WinCE 6.0 のデバイスエミュレータ、つまり、S3C2410 リファレンスボードのエミュレータの特徴をまとめます:

  • Windows Mobile のエミュレータとしても利用されている。
  • 設定ファイルを作成することにより、スキン画像と入力ボタン/キーパッドを設定できる。
  • ARM プロセッサをエミュレートしている。

どれも、Virtual PC ベースのエミュレータには無いものです。専用の BSP を使って作成した OS イメージをロードして動かせるのは、どちらのエミュレータも同じです。一方、エミュレータから利用できるホスト PC の周辺機器機能は、Virtual PC ベースのエミュレータの方が豊富です。WinCE 6.0 のデバイスエミュレータの方は、実質、Ethernet とシリアルポートのみです。

WinCE 6.0 のデバイスエミュレータでは、周辺機器のエミュレート機能として、ホスト PC のマウス入力に対する、タッチパネル入力のエミュレートや、ホスト PC のディレクトリをメモリカードとしてマウント/エミュレートする、というものがあります。Virtual PC に比べると、利用できるホスト PC の周辺機器機能は貧弱ですが、入力ボタン/キーパッドのエミュレートが可能なことと、ARM プロセッサをエミュレートしているのは、便利な場合があります。

さて、WinCE 6.0 のデバイスエミュレータがエミュレート(シミュレート)しているのは、上述したように、Samsung の ARM9 コアのプロセッサ(S3C2410)のリファレンスボードです。S3C2410 の ARM9 コアは、ARM920T、つまり、命令セットが v4T である ARM9TDMI ファミリです。WinCE 6.0 までの ARM コンパイラは、ARMv4 の命令セットにしか対応していませんでしたので、それで十分でした。しかし、WEC 7 の ARM コンパイラでは、ARMv5/v6/v7 のサポートが追加された代わりに、ARMv4 には対応していません。このことは、このデバイスエミュレータで WEC 7 を動かそうとする場合に、問題となるように思われます。

しかし、心配はいりません。VS 2008 の Smart Device Development 機能に付属する、最新版(3.0)のデバイスエミュレータ(※上述したページから、単体でダウンロードできます)では、ARMv5 の命令セットにも対応しています。デバイスエミュレータのコマンドラインオプションのリファレンスを見ると、/cpucore オプションで ARMv5 を指定することにより、ARMv5 命令セットが有効になると説明されています(デフォルトは、ARMv4):

 デバイス エミュレータのコマンド ライン リファレンス
 http://msdn.microsoft.com/ja-jp/library/aa188169(v=VS.90).aspx

ちなみに、VS 2005 に付属していた版(1.0)、つまり、WinCE 6.0 用としても使われるデバイスエミュレータは、ソースコードを入手可能です:

 Shared Source Microsoft Device Emulator 1.0 Release
 http://www.microsoft.com/en-us/download/details.aspx?id=10865

デバイスエミュレータ 1.0 のソースコードは、上のページに書かれているように、Shared Source ライセンスで提供されていますので、興味がある方は、ご覧になってみて下さい。QEMU など、他のエミュレータと同様なところはあると思いますので、両者を比較しながら読んでみるのも、面白いでしょう。

■WEC 7 での Virtual PC 対応
WEC 7 では、WinCE 6.0 にあった ARM ベースのデバイスエミュレータの BSP は付属せず、代わりに、Virtual PC 用の BSP が付属しています。この BSP に対応した、Virtual PC の仮想マシンイメージ(cevm.vmc)も付属しており、BSP を使って OS イメージを作成すれば、すぐに動かせるようになっています。この仮想マシンイメージを有効にすると、Windows 7 で Windows XP Mode をお使いの場合、Windows Virtual PC の仮想マシンとして、Windows XP Mode に加えて cevm が表示されるようになる筈です。cevm には、WEC 7 のブートローダが組み込まれていますので、起動してブートローダのメニュー画面を表示させ、ネットワーク設定を行えば、Platform Builder を使って OS イメージをダウンロードできます。

WEC 7 での Virtual PC ベースのデバイスエミュレータを使う手順については、リファレンスの次のページをご覧下さい。

 Develop with Virtual CEPC (Windows Embedded Compact 7)
 http://msdn.microsoft.com/en-us/library/jj200433

お使いの PC に WEC 7 をインストール済みの方であれば、
 C:/Program Files/Windows Embedded Compact 7/Documentation/
ディレクトリの中に入っている
 Getting Started with Virtual CEPC.pdf
というドキュメントも、参考になるでしょう。

Virtual PC ベースのデバイスエミュレータを、WinCE 6.0 のデバイスエミュレータと比べると、上述したように、エミュレータから利用できるホスト PC の周辺機器機能は、Virtual PC ベース(前者)の方が豊富です。また、原理上、前者の方が高速でもあります。一見すると、Virtual PC ベースのエミュレータの方が良いことずくめで、WinCE 6.0 のデバイスエミュレータ(ARM ベースのエミュレータ)を使う理由は、ありません。

しかし、ARM ベースのエミュレータの方が便利な場合も、あります。たとえば、次の場合です。

  • 実機のプロセッサが ARM の場合。
  • 実機にハードウェアボタンを搭載予定であり、そのモック動作を、エミュレータで行いたい場合。

ハードウェアボタンのシミュレートについては、上述したように、設定ファイル(スキンファイル)へ記述することにより任意に設定できます。ARM ベースのデバイスエミュレータの設定ファイルの記述仕様は、リファレンスの次のページで説明されています:

 デバイス エミュレータのスキンの XML スキーマ リファレンス
 http://msdn.microsoft.com/ja-jp/library/aa188144(v=vs.90)

 デバイス エミュレータ構成の XML スキーマ リファレンス
 http://msdn.microsoft.com/ja-jp/library/bb531167(v=vs.90)


2012-09-05 追記:
デバイスエミュレータのスキン設定ファイルにおいて、ボタン/キーパッドに割り当てるキーコードについて、上記のリファレンスでは、特に説明されていません。割り当て可能なキーコードの説明は、WinCE 5.0 のリファレンスにある、次のページをご覧下さい:

 http://msdn.microsoft.com/en-us/library/ms905130.aspx

実機のプロセッサが ARM の場合ですが、Virtual PC ベースのデバイスエミュレータ、つまり、x86 のエミュレータで開発すると、エミュレータでの動作中には起きなかった例外送出が、実機の ARM プロセッサでは発生する、という可能性があります。これが起きる典型的なケースは、TCP/IP 通信や USB 通信でデータ転送を行う際に、転送プロトコルのパケットのペイロードを、構造体に cast して、構造体のメンバにアクセスする場合です。この時、構造体のメンバが 2Byte 以上のサイズの整数値型([unsigned] short, [unsigned] long など)であり、かつ、構造体に cast したメモリ領域のアドレスのアラインメントが、それらの整数値型のサイズに対して揃っていなければ、ARM プロセッサの場合は例外が発生します。しかし、x86 の場合には、アラインメントが揃っていなくても、例外が発生しません。

従って、Virtual PC ベースのデバイスエミュレータでは起きなかった、アプリケーションや汎用デバイスドライバ(USB のクラスドライバなど)の不具合が、ARM プロセッサの実機へ移植すると発生する、という可能性があります。同様のことは、x86 PC でしかテストされていないソフトウェアを、ARM や MIPS などのプロセッサへ移植する場合にも問題となります。これら、ワードアラインメントに関わる不具合を、デバイスエミュレータでの開発時に検出することができるのは、WinCE 6.0 において、ARM ベースのエミュレータが導入された利点の一つだったと思います。

ちなみに、WEC 7 の ARM コンパイラでは、WinCE 6.0 までの ARM コンパイラに比べて Compiler Intrinsics が強化されているため、WinCE 6.0 では発生しなかった例外送出が、WEC 7 では発生する、というケースもあります。つまり、WinCE 6.0 では、バイト単位のアクセス実行にしかコンパイルされなかったソースコードが、ワード単位でのアクセス実行にコンパイルされる場合がある、ということです。同じソースコードから生成されるバイナリの実行効率が上がるようにコンパイラが改善された一方で、その「副作用」として、不用意に書かれたソースコードによって新たに例外送出が発生する場合もある、というわけですね。

ところで、WEC 7 のデバイスエミュレータが、WinCE 6.0 での ARM ベースから Virtual PC ベースに戻ったのは、使用できるホスト PC の周辺機器や実行速度の違いが、主な要因だと思われます。それに加えて、WEC 7 からは PCMCIA がサポートされなくなったことも、要因の一つではないかと思います。WinCE 6.0 のデバイスエミュレータは、NE2000 互換の PCMCIA カードをエミュレートしているのですが、WEC 7 では PCMCIA がサポートされないため、その PCMCIA カード(NE2000 互換のネットワークカード)を利用できないのです。

このことは、Device Emulator の BSP を WinCE 6.0 から WEC 7 へ移植する際に、問題となります。さて、いよいよ、今回のエントリの本題です。

■Device Emulator BSP を WEC 7 へ移植する
これまでの説明で、WinCE 6.0 と WEC 7 では、デバイスエミュレータが異なっており、一方は、ARM プロセッサベース(Samsung S3C2410 のリファレンスボード)、他方は、Virtual PC ベースだということを述べました。また、WinCE 6.0 には付属していた、ARM プロセッサベースのデバイスエミュレータの BSP が、WEC 7 には付属していないことも述べました。そのため、Visual Studio 2008 の Smart Device Development 機能に含まれる、ARM ベースのデバイスエミュレータ(3.0; ARMv5 対応)で WEC 7 を動かそうとしても、OS イメージを作成する手段がないということについても、お分かり頂けたのではないかと思います。

では、Visual Studio 2008 付属のデバイスエミュレータで WEC 7 を動かすには、どうすればよいのでしょうか?答えは簡単です。WinCE 6.0 に付属するデバイスエミュレータの BSP を、WEC 7 に移植して、移植した BSP を使って OS イメージを作成すればよいのです。必要な作業は、以下の通りです:

  1. WinCE 6.0 の %_WINCEROOT%/platform/DEVICEEMULATOR/ ディレクトリを、WEC 7 の %_WINCEROOT%/platform/ ディレクトリへコピーする。
  2. WinCE 6.0 の %_WINCEROOT%/PUBLIC/COMMON/OAK/DRIVERS/PCCARD/ ディレクトリを、WEC 7 にコピーした DEVICEEMULATOR/ ディレクトリの中の SRC/DRIVERS/ ディレクトリへコピーする(※その際、DRIVERS/ の下に PCMCIA/ というディレクトリを作り、その中に PCCARD/ ディレクトリの内容をコピーするのが良いでしょう)。
  3. コピーした DEVICEEMULATOR/ ディレクトリと PCCARD/ ディレクトリの内容、つまり、WinCE 6.0 のデバイスエミュレータの BSP と PCMCIA スタックに対して、WEC 7 への移植(必要な改訂)を施す。
  4. WinCE 6.0 の %_WINCEROOT%/PUBLIC/COMMON/DDK/INC/ ディレクトリから、PCMCIA 関連のヘッダファイル(tuple.h, cardsv2.h, socksv2.h, cardserv.h)を、WEC 7 にコピーした DEVICEEMULATOR/ ディレクトリの中の SRC/INC/ へコピーする。
  5. WinCE 6.0 の %_WINCEROOT%/PUBLIC/COMMON/OAK/files/common.reg ファイルから、PCMCIA の NE2000 互換カードに関するレジストリ項目を、WEC 7 にコピーしたデバイスエミュレータの platform.reg へコピーする。

上で述べたように、デバイスエミュレータがエミュレートするネットワークインタフェースは、NE2000 互換の PCMCIA カードなのですが、WEC 7 では PCMCIA がサポートされず、PCMCIA スタックが付属していません。そのため、WinCE から PCMCIA スタックを移植する必要があります。PCMCIA スタックを移植するにあたっては、%_WINCEROOT%/PUBLIC/COMMON/OAK/DRIVERS/ ディレクトリへコピーせず、デバイスエミュレータ専用のコンポーネントとして、デバイスエミュレータの BSP ディレクトリへコピーするのが良いでしょう。そうすれば、WEC 7 のソースツリー全体を「汚さずに」済みます。なお、デバイスエミュレータの BSP では、DRIVERS/ の下に PCCARD/ というディレクトリがあります。そのため、WinCE 6.0 の PCMCIA スタックのディレクトリ(PCCARD/)を、そのまま DRIVERS/ の下にコピーするのではなく、DRIVERS/ の下に PCMCIA/ というディレクトリを作って、その下に、WinCE 6.0 の PCARD/ ディレクトリの内容をコピーするのが良いでしょう。

デバイスエミュレータの BSP の移植作業は、他の BSP を WinCE 6.0 から WEC 7 へ移植する場合と同様です。中間ディレクトリのパスが WEC 7 で変わったことに伴う、各コンポーネントの sources ファイルの修正や、WinCE 6.0 時点で古くなっていた型定義が WEC 7 では削られてしまったことへの対応などが必要です。たとえば、デバイスエミュレータのディスプレイドライバでは、GPERotate や GPESurfRotate という型を参照していますが、これらは、WEC 7 のヘッダファイルでは削られてしまっているため、WinCE 6.0 のヘッダファイルからドライバのソースファイルへコピーする必要があります。

また、PCMCIA スタックの移植では、%_WINCEROOT%/PUBLIC/COMMON/DDK/INC/ にある devload.h や cebuscfg.h において、PCMCIA 関連の定数の定義が削られてしまっていることへの対応(WinCE 6.0 の同じ名前のファイルから、PCMCIA 関連の定数の定義を PCMCIA スタックのソースファイルへコピーする)も必要です。

上記のように、それなりの移植の手間は必要ですが、WEC 7 のコアのソース(PRIVATE/ および PUBLIC/ ディレクトリ配下)に手を加えずに、BSP および BSP 固有のコンポーネントとして追加するだけで済みます。これは、Windows Embedded Compact が、明確にモジュール化されていることの証だと思います。

移植後の BSP の品質を確保するためには、より多くの作業が必要ですが、「とりあえず動かす」レベルであれば、それほど大変な作業では、ありません。今回は、デバイスエミュレータで WEC 7 が起動して、ホスト PC でのマウス操作でタッチパネル入力をシミュレートでき、さらに、デバイスエミュレータのネットワークアダプタを使えるようになるまでの作業で分かったことを書いています。

最後に、デバイスエミュレータのネットワークアダプタを使えるようにするための移植作業に関して、注意点を二つ書きます。移植作業の細かい説明は省きましたが、次の二点は、見落としがちな要点なので、自分でも移植してみようと思った方のために書いておきます。

  • PCMCIA の NE2000 互換カードに関するレジストリ項目を、WinCE 6.0 の common.reg からコピーする際、BusType を 0 に変更する。
  • PCMCIA ホストコントローラのドライバ(pcc_smdk2410.dll)のロード順序を、後にする。

一番目の注意点ですが、[HKEY_LOCAL_MACHINE\Comm\NE20001\Parms] キーの下にある BusType キー値のことです。WinCE 6.0 の common.reg では、8、つまり PCMCIA の bus type 値が設定されています。しかし、WEC 7 では、PCMCIA がサポートされないため、この値を指定すると、「未知/未定義の bus type」というエラーになってしまい、NE2000 ミニポートドライバの初期化に失敗します。このキー値は、NE2000 ミニポートドライバがロードされた際に、初期化動作の一つとして NdisMRegisterIoPortRange() を呼び出す際に参照されるのですが(※NdisMSetAttributes() を使って、NDIS_HANDLE に対して bus type を設定したのち、NdisMRegisterIoPortRange() を呼び出します)、その呼び出しがエラーとなってしまいます。

このエラーは、bus type を 0 (Internal) に変更することで回避できます。今回、デバイスエミュレータで WEC 7 を動かすために上記の移植を行った際、当初は、NE2000 ミニポートドライバを自前実装し、NdisMRegisterIoPortRange() を呼び出さない仕組みで実装し直す必要があるのではないかと考えました。しかし、分析してみたところ、bus type の設定変更だけで済むことが分かり、移植の手間を減らすことができました。

最後に、二番目の注意点です。オリジナルの実装では、[HKEY_LOCAL_MACHINE\Drivers\BuiltIn\PCC_SMDK2410] キーの下にある Order キー値が 1 になっています。しかし、その設定では、デバイスエミュレータの PCMCIA ホストコントローラがネットワークカードを認識して、NE2000 ミニポートドライバをロードする際に、エラーになってしまうのです。その原因は、解明できていないのですが、pcc_smdk2410.dll がロードされる順序を遅らせることにより、エラーを回避できるようです。設定する値は、dword:10 などにしてみて下さい。

二番目の点については、今後、もし調査の時間がとれたら、さらに追ってみたいと思います。もし、正確な原因や、より根本的な対処策をご存じの方がいらしたら、ぜひ教えて下さい。

1 comment 2012/07/04 koga

WinCE/WEC のウォッチドッグタイマ機能(補足)

前回のエントリでは、WinCE/WEC のウォッチドッグタイマ機能について、ハードウェアタイマとソフトウェアタイマの二種類があることを述べました。そして、ハードウェアタイマとソフトウェアタイマでは、ソフトウェアタイマの方が、より柔軟であり、以下の点が異なると説明しました:

  1. ソフトウェアタイマは、複数の監視対象を設定できる(タイマを複数作成できる)
  2. ソフトウェアタイマは、タイマ満了時に発火させるアクションとして、デバイスをリセットする以外の動作を設定できる
  3. ソフトウェアタイマの場合は、カーネルではなく、監視対象のアプリケーションやスレッドが、タイマのリフレッシュ動作を行う

今回のエントリでは、上に示した、ハードウェアタイマとソフトウェアタイマの違いのうち、2. について補足します。ウォッチドッグタイマ機能において、ソフトウェアタイマとハードウェアタイマのどちらも、タイマの満了時にデバイスをリセットできますが、リセット動作の詳細は、異なるのです。

■ハードウェアタイマについての注意点
前回のエントリでは、ソフトウェアタイマがタイマ満了時に発火させることのできる三通りのアクションのうち、WDOG_RESET_DEVICE のことを、「OS をリブート(デバイスをリセット)する」動作だと説明しました。この「OS をリブート」というのが、ハードウェアタイマとは違う点です。ハードウェアのウォッチドッグタイマは、プロセッサを強制リセットします。強制リセットする際、OS がどのように動作しているのかは関知しません。

ここで、ハードウェアタイマの場合には、WinCE/WEC のカーネルが(より正確には、ウォッチドッグタイマ用のカーネルスレッドが)タイマのリフレッシュ動作を行います。従って、ハードウェアタイマが満了したということは、カーネルが動作を停止しているということですから、プロセッサを強制リセットするのが妥当です。通常は、その通りです。しかし、何らかの不具合で OS 全体がフリーズしているわけではないのに、カーネルが動作を停止する場合もあるのです。どんな場合でしょうか?

それは、カーネルデバッガを使っている時です。カーネルデバッガを使ってデバッグ動作している状態では、ブレークポイントにヒットすると、OS 全体の動作が停止します。OS 全体の動作が停止した状態であっても、ハードウェアタイマは動作し続けますから、ハードウェアタイマの満了時間が過ぎると、その時点で、ハードウェアのウォッチドッグタイマの働きにより、プロセッサが強制リセットされてしまうのです。ソフトウェアタイマの場合には、タイマの動作も停止していますから、そのようなことは、起きません。

カーネルデバッガを使ったデバッグ中に、予期せぬ強制リセットが起きしてしまわないよう、カーネルデバッガを使う時にはハードウェアタイマを動かさないか、または、タイマの満了時間をできるだけ長い値に設定して下さい。

■ソフトウェアタイマによるリセット動作
さて、ソフトウェアタイマのリセット動作は、「OS をリブート」する点がハードウェアタイマと違うと上で述べました。これについて、説明します。

まず、ハードウェアタイマのリセット動作では、プロセッサのリセット信号線を使って、ハードウェアによるリセットを行います。つまり、ハードウェアのウォッチドッグタイマが、プロセッサに対してリセット信号を出力する(プロセッサのリセット入力信号線に供給する電圧の論理値を、リセット用の値に変化させる)ことによって、プロセッサを強制リセットします。

一方、ソフトウェアタイマでは、ウォッチドッグタイマ用のカーネルスレッドが、OS のリブート処理を始動することによって、リセット動作を起こします。具体的には、IOCTL_HAL_REBOOT を引数として KernelIoctl() を呼び出します。興味のある方は、ソースコードをご覧になってみて下さい。ウォッチドッグタイマ用のカーネルスレッドのソースは、前回述べた通り、
 %_WINCEROOT%/PRIVATE/WINCEOS/COREOS/NK/KERNEL/watchdog.c
にあり、WatchDogTimerThrd() が、そのスレッドが実行する手続きです。WatchDogTimerThrd() を見ると、ソフトウェアタイマのどれか一つが満了すると、タイマの状態が WD_STATE_SIGNALED になり、その結果、そのタイマに設定されたアクションが実行されることが分かります。タイマに設定されたアクションの実行は、WDTakeDfltAction() を呼び出すことによって行われますが、WDTakeDfltAction() では、タイマに設定されたアクションが WDOG_RESET_DEVICE の場合、IOCTL_HAL_REBOOT を引数として KernelIoctl() を呼び出す実装になっています。

IOCTL_HAL_REBOOT を引数として KernelIoctl() を呼び出すことによって OS のリブート動作が起きる仕組みについては、2009/07/20 のエントリ(「アプリケーションからリブート(または電源 OFF)~その1(1/2)」)に書いています。もし興味があれば、お読みになってみて下さい。

2009/07/20 のエントリで書いたように、カーネルスレッドが、IOCTL_HAL_REBOOT を引数として KernelIoctl() を呼び出した場合には、レジストリやファイルシステムのフラッシュ動作が実行されたのち、OALIoCtlHalReboot() が呼び出されます。OALIoCtlHalReboot() は、カーネル移植レイヤ(OAL)で実装されており、プロセッサごとに異なるリセット動作を実行します。一般的には、ハードウェアのウォッチドッグタイマの発火時間を短い値にセットしたのち、無限ループを実行し、ハードウェアタイマによってプロセッサを強制リセットさせます。

■ハードウェアタイマについて、もう一度だけ注意
ここで重要なことは、ソフトウェアタイマによるリセット動作では、プロセッサをリセットする前に、レジストリやファイルシステムのフラッシュ動作を実行する、という点です。このことにより、ウォッチドッグタイマが発火してリセット動作が起きた際に、永続記憶域上のレジストリ内容やファイルシステムが、壊れてしまう危険性が抑えられるのです。ソフトウェアタイマが発火する原因になった監視対象のアプリケーションが、ファイル操作を行っていた場合には、ファイル内容が壊れてしまう可能性はあります(ファイルの書き込みを行っている途中でフリーズした場合など)。しかし、ディレクトリが壊れて読めなくなってしまったりするといった致命的なことは、起きないようにするというわけです。

この点も、ハードウェアタイマとは違う点です。通常動作中に OS 全体がフリーズしてしまった場合は、強制リセットが起きるのは仕方ありません。しかし、カーネルデバッガを使ったデバッグ中に、ハードウェアタイマが満了してしまい、予期せぬ強制リセットによってレジストリやファイルシステムが壊れてしまう、というようなことが起きないよう、ハードウェアタイマを使う場合には、ご注意ください。

Add comment 2012/03/27 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

OS の部分アップデート方策~その1(1/2)

WinCE 6.0/WEC 7 では、通常、OS 全体を ROM イメージファイル(nk.bin)に格納し、Flash メモリなどに書き込んで使います。Flash メモリに書き込んだ ROM イメージファイルは、電源投入後に、ブートローダによって RAM にロードされ、ブートローダがカーネルのスタートアップルーチンへ制御を移すことにより、WinCE/WEC が起動する、というわけです(NOR Flash の場合には、XIP; Execute-In-Place 用の ROM イメージファイルを Flash メモリへ書き込むことにより、イメージファイルを RAM にロードせず、Flash メモリから直接カーネルを実行することも可能です)。

ここで、OS 全体を ROM イメージファイルに格納するということは、デバイスドライバやアプリケーションを修正した場合、その修正を反映するには、ROM イメージファイルを作り直して上書きしなければならない、ということを意味します。ROM イメージファイルのサイズは、コンフィグレーション次第で変わりますが、数十MB、場合によっては 64MB を超えることもあります。PC ならばともかく、一般の組み込み機器では、アップデートのたびに ROM イメージファイル全体を書き換えなければならないというのは、大きなコストだと言えるでしょう。

ROM イメージファイル全体を書き変えずに、アップデートしたいアプリケーション(.exe)やデバイスドライバ(.dll)だけを置き換えることは、できないのでしょうか?それは可能です。アップデートしたいものだけを置き換える、つまり部分アップデートには、二通り、ないしは三通りの方法があります。WinCE 6.0/WEC 7 を動かすデバイスのハードウェア構成の違いや、デバイスのメンテナンス方策、および、カーネル移植レイヤとブートローダのカスタマイズが可能かどうかにより、どの方法が最も適しているかは変わりますので、それぞれについて、順に紹介したいと思います。

今回は、ファイルシステム用の永続記憶域を使った部分アップデートの方法を二通り紹介し、次回は、ROM イメージファイルを複数に分割する方式の部分アップデートの方法を紹介します。

■アップデート対象を OS イメージに含めない方法
部分アップデートの最も簡単な方法は、部分アップデートする対象(アプリケーションなど)を OS イメージ(ROM イメージファイル)に含めず、永続記憶域に配置することです。たとえば、WinCE/WEC に付属しない、そのデバイス専用に開発したアプリケーションなどを、OS イメージには含めずに、ハードディスクや microSD カード、USB メモリなどに格納する、という方策です。

この方法の利点は、次の通りです:

 ・WinCE/WEC に対するカスタマイズの必要がない。
 ・OS イメージを配置する記憶域と、部分アップデート対象を配置する記憶域を、それぞれ違うものにできる。

たとえば、NOR Flash と NAND Flash を搭載したデバイスの場合であれば、NOR Flash に OS イメージを配置して、NAND Flash 上に部分アップデート対象を配置する、という方法です。NAND Flash 上にファイルシステムが構築されていれば、この方策を使えます。あるいは、NOR Flash と、microSD/SD スロットや USB ポートを搭載したデバイスであれば、microSD/SD カードや USB メモリを常時装着しておき、それらに部分アップデート対象を配置する、という方法が使えます。

ところで、部分アップデートする対象のアプリケーションや DLL を、Platform Builder の OS Design サブプロジェクトにして開発する場合、それらの .exe や .dll は、Platform Builder で設定すれば、OS イメージに格納されません。従って、ビルドや、カーネルデバッガを使ったデバッグまでは、OS イメージに含めるものと同様、Platform Builder で統合して行うことが可能です。

■永続記憶域上にルートファイルシステムを構築する方法
部分アップデートの二番目に簡単な方法は、永続記憶域上のファイルシステムを、ルートファイルシステムとしてマウントすることです。デフォルトのコンフィグレーションでは、WinCE/WEC は、RAMFS (Object Store) をルートファイルシステムとしてマウントしますが、永続記憶域上のファイルシステムをルートファイルシステムにすることも出来るのです。

ルートファイルシステムの設定は、WinCE 6.0 のリファレンスにある、次のページで説明されています:

 Mounting an Installable File System as the Root Directory
 http://msdn.microsoft.com/en-us/library/ee490194(v=winembedded.60).aspx

WEC 7 のリファレンスでは、上のページの説明に比べると若干分かりづらいですが、次のページで説明されています:

 RAM File System Registry Settings (Windows Embedded Compact 7)
 http://msdn.microsoft.com/en-us/library/ee490789.aspx

 Mount Settings (Windows Embedded Compact 7)
 http://msdn.microsoft.com/en-us/library/ee490773.aspx

WinCE 6.0 の場合、上記のページの説明を見て頂くと分かるように、必要な設定は、レジストリ内容を永続化する場合と一部共通します。レジストリ内容を永続化するための設定と、その仕組みについては、2011/02/15 のエントリ(「レジストリ変更内容の永続化(1/2)」)と、2011/02/21 のエントリ(「レジストリ変更内容の永続化(2/2)」)をご覧ください。

WinCE 6.0 の場合に、永続記憶域をルートファイルシステムとしてマウントするためには、レジストリ内容を永続化する場合の手順に加え、以下の手順が必要です:

・Platform Builder のカタログ項目ビューで、次のカタログ項目を選択する:

 コア OS
  CEBASE
   ファイルシステムおよびデータストア
    ファイルシステム – 内部(1つ選択)
★    ROM のみに適用されるファイルシステム

・当該ファイルシステムに対するレジストリ設定で、MountAsRoot の値を 1 に設定する。

2011/02/21 のエントリに書いた例(Armadillo-440 の microSD カードを使う場合)だと、次のように設定して下さい。


[HKEY_LOCAL_MACHINE\System\StorageManager\Profiles\SDMemory\FATFS]
    "MountPermanent"=dword:1
    "MountAsBootable"=dword:1
    "MountAsRoot"=dword:1

この方法の利点は、次の通りです:

 ・WinCE/WEC のカーネル移植レイヤ(OAL)とブートローダをカスタマイズする必要がない。
 ・OS イメージに含めたアプリケーションやデバイスドライバも、アップデートすることが可能(ただし、出来ないものもある)。

OS イメージに含めたアプリケーションやデバイスドライバをアップデートできるのは、ルートファイルシステムに配置されたファイルによる、”shadowing” の仕組みがあるからです。この仕組みは、WinCE 6.0/WEC 7 の、以下のソースファイルで実装されています。

 %_WINCEROOT%/private/winceos/COREOS/storage/fsdmgr/virtroot.cpp
 %_WINCEROOT%/private/winceos/COREOS/storage/fsdmgr/pathapi.cpp

virtroot.cpp と pathapi.cpp は、FSD Manager (fsdmgr.dll) のソースファイルの一つですが、CreateFile() によるファイルオープンや、Find{First,Next}File() によるファイル走査の際に、ルートファイルシステム配下のパスが指定された場合は、そのファイルがルートファイルシステム上に存在しなければ、ROM イメージのファイルシステムをアクセスするようになっています。この仕組みにより、同じパス名のファイルが、ルートファイルシステムと ROM イメージのファイルシステム(つまり、OS イメージ)の両方に存在すると、ルートファイルシステム上のファイルが優先される、というわけです。これが、shadowing です。
(※ただし、virtroot.cpp と pathapi.cpp による shadowing は、.exe と .dll 以外のファイルに対して行われるようです。これについては、次の項で述べます。)

永続記憶域上のファイルシステムをルートファイルシステムに設定している場合、アップデート手順は、上で述べた一番目の方法と同じくらい簡単です。たとえば、ディスプレイドライバをアップデートしたい場合は、ディスプレイドライバの新しい .dll を /Windows ディレクトリに上書きコピーして、リブートするだけです。/Windows ディレクトリに上書きコピーした .dll は、永続記憶域上のファイルシステムに書き込まれ、リブートした際に、OS イメージ内の古い .dll を隠します(shadowing)。つまり、OS イメージ(ROM イメージファイル)全体を書き換えることなく、目的のファイルだけをアップデートできる、というわけです。

ただし、この方法で全てのデバイスドライバや DLL をアップデートできるわけでは、ありません。アップデートできるのは、ブートの初期フェーズ以降にロードされるデバイスドライバや DLL だけです。たとえば、microSD カード上にルートファイルシステムを構築する場合、SD ホストコントローラのドライバは、この方法ではアップデートできません。SD ホストコントローラのドライバは、ルートファイルシステムをマウントするために必要なので、ルートファイルシステムがマウントされる前にロードされます。従って、たとえルートファイルシステムに、更新した SD ホストドライバの .dll を配置したとしても、shadowing は行われず、OS イメージ内の「古い」方の .dll が動作します。

■shadowing について、もう少し
ちなみに、ROM イメージのファイルシステムのドライバは、romfsd.dll です。このドライバのソースは、開示されていません。romfsd.dll は、FSD Manager の初期化処理でロードされ、”ROM” という名前のボリュームとしてマウントされます。FSD Manager が romfsd.dll をロードする処理は、

 %_WINCEROOT%/private/winceos/COREOS/storage/fsdmgr/fsdmain.cpp

にある InitializeROMFileSystem() で実装されています。InitializeROMFileSystem() は、同じファイルで実装されている STOREMGR_Initialize() の中で、最後に呼び出される関数です。つまり、WinCE/WEC のストレージ機能の中核を初期化する処理の、最終段階で呼び出される関数です。

romfsd.dll のソースコードが開示されていないので、詳細は不明ですが、おそらく、romfsd.dll は、OS イメージ内の全てのファイルに対するファイルシステムではなく、OS イメージの FILES セクションに収録されたファイルに対するものだと思われます。OS イメージの MODULES セクションに収録されたファイル(.dll および .exe)は、カーネルのローダー機能がアクセスする仕組みになっているようです。カーネルのローダー機能のソースは、

 %_WINCEROOT%/private/winceos/coreos/nk/kernel/loader.c

です。loader.cpp で実装されている OpenFileFromROM() が、OS イメージ/ROM イメージの MODULES セクションから .exe や .dll をロードする関数です。この関数は、OpenExecutable() から呼び出されるのですが、OpenExecutable() は、 まず OpenFileFromFilesys() を呼び出し、ファイルシステム上にファイルが見つからなければ、OpenFileFromROM() を呼び出します。従って、永続記憶域上のルートファイルシステムに .exe や .dll が存在すれば、そちらが優先されます(shadowing)。

なお、カーネルデバッガで追ってみると、.dll や .exe は、romfsd.dll に対するアクセスでは見つからず、必ず、OpenFileFromROM() が呼ばれるようです。上で、romfsd.dll は、OS イメージの FILES セクションに収録されたファイルに対するものだと思われると書いたのは、この観測結果からの推察です。

■耐障害性の強化に関する考察
さて、永続記憶域上にルートファイルシステムを構築することにより、OAL とブートローダのカスタマイズ無しで部分アップデートできることを説明しました。ただし、ここで一つ問題があります。それは、ルートファイルシステムが破損する可能性です。不意の電源断などにより、ルートファイルシステムが破損してしまうと、WinCE/WEC は、起動できなくなってしまいます。Flash メモリなどに格納された OS イメージが無事でも、永続記憶域上のルートファイルシステムが破損してしまうと起動できない理由については、2011/08/02 のエントリ(「レジストリの永続化~RAM-Based の場合」)をご覧ください。このエントリの、三種類のレジストリ永続化方式の長短を述べた個所で、「(a) Hive-Based のレジストリ」の短所で述べたことが、ここでも当てはまるのです:

 短所:レジストリの Hive を配置するファイルシステムを Bootable に設定しなければならないため、そのファイルシステムが破損するなどしてマウントできなくなった場合、ブートの第2フェーズへ遷移できず、第1フェーズの完了待ちで WinCE カーネルの起動が停止してしまう。

この問題を回避する方策としては、次のようなものが考えられます。

・ルートファイルシステム破損時の復旧用に、もう一つ OS イメージを作成し、ブートローダの操作で、復旧用の OS イメージを起動できるようにする。復旧用の OS イメージでは、ルートファイルシステムを永続記憶域に割り当てず、RAMFS (Object Store) をルートファイルシステムに割り当てる。
 ←復旧用の OS イメージの起動後、破損したファイルシステムをフォーマットするなどの、復旧作業を行えるようにする。
  OS イメージを格納するストレージ(Flash メモリやハードディスクなど)の容量が十分大きく、OS イメージを二つ格納できる場合は、この方策が向いているでしょう。

・ファイルシステムを AutoLoad 可能な永続記憶域上に、ルートファイルシステムを割り当て、かつ、ルートファイルシステムを Bootable に設定しない。ルートファイルシステムとして正しく動作できるように、ブートの第1フェーズでファイルシステムをロードさせる。ルートファイルシステムを Bootable に設定しないので、RAMFS (Object Store) が Bootable なファイルシステムとしてマウントされる結果、永続記憶域上のルートファイルシステムをマウントできなくても、WinCE/WEC が起動する。
 ←この方策は、microSD/SD カードや USB メモリに対しては、使えません。使えない理由は、2011/02/21 のエントリ(「レジストリ変更内容の永続化(2/2)」)をご覧ください。SD メモリカードのドライバや USB メモリカードのドライバは、クライアントドライバなので、ロードするブートフェーズを明示的に指定できないのです。ファイルシステムを AutoLoad 可能な場合は、ブートの第1フェーズでロードされるように、レジストリの BootPhase 値を設定しておけば、たとえ RAMFS が Bootable なファイルシステムとしてマウントされても、ブートの第2フェーズへ遷移する前に、ルートファイルシステムがマウントされる筈です。
  この方策は、NAND Flash やハードディスクを搭載したデバイスの場合に使えるでしょう。この方策を使った場合、そのままでは、レジストリが永続化されないという問題が残りますが、Hive ではなく、RAM ベースのレジストリにして、ルートファイルシステムを割り当てるのとは異なるストレージ(NOR Flash など)にレジストリを配置する、といった解決策が考えられます。

1 comment 2012/01/10 koga

レジストリの永続化~RAM-Based の場合

2011/02/21 に書いたエントリ(「レジストリ変更内容の永続化(2/2)」では、Hive-Based と RAM-Based の二種類があるレジストリのうち、Hive-Based のレジストリを永続化する仕組みについて説明しました。今回は、RAM-Based のレジストリの永続化について、Hive-Based のレジストリとの比較を交えて書きます。

まず、RAM-Based のレジストリを永続化する方法については、リファレンスの次のページに記載されています:

 http://msdn.microsoft.com/en-us/library/ee489958(v=WinEmbedded.60).aspx (Persisting Data with the RAM-Based Registry)
 http://msdn.microsoft.com/en-US/library/ee490769(v=WinEmbedded.60).aspx (Data Persistence with the RAM-based Registry Using the OAL)

実は、RAM-Based のレジストリを永続化する方法は二つあり、上の二つのページで、その各々について説明されています。それぞれの概要は、次の通りです。

(1) Oemregistry.dll という DLL を組み込む。この DLL で、ReadRegData(), WriteRegData(), RegistryOperation() を実装する。

(2) OAL (OEM Adaptation Layer) に、WriteRegistryToOEM() と ReadRegistryFromOEM() の実装を追加する。

これら二つの方法のうち、(1) は、永続記憶域としてファイルシステムを使う場合に適しています。一方、(2) は、NOR Flash の特定領域を割り当てるなどして、ファイルシステムを介さず、生の(raw)ストレージメディアにレジストリ内容を保存する場合に適しています。

どちらの場合も、レジストリの変更内容を永続記憶域に保存するためには、RegFlushKey() の呼び出しが必要です。Hive-Based のレジストリとは異なり、RAM-Based のレジストリでは、RegFlushKey() を呼び出さない限り、レジストリの変更内容は保存されません。

さて、上記 (1) と (2) のどちらも、WinCE 6.0 ではサンプル実装が提供されています。(1) は、リファレンスのページでも紹介されているように、%_WINCEROOT%/Public/Common/Oak/Drivers/FSD/Oemfs/ がサンプル実装です。このサンプル(Oemfs)では、永続記憶域として、リリースディレクトリファイルシステムを使用します。つまり、開発ホスト機の Flat Release Directory に、RAM-Based レジストリの内容がファイルとして保存されます。当然ですが、使用する場合は、OS Design のカタログ項目で、「リリースディレクトリファイルシステム」を選択して OS イメージをビルドしなければいけません。また、サンプルのままでは、ビルドの target name が Oemfs になっており、’Oemregistry’ とは違います。さらに、他の Public/ ディレクトリ配下のコンポーネントと同様、target type が LIBRARY になっていて、一旦 static library (.lib) としてビルドされた後、sysgen 変数でビルド対象に設定された場合にのみ、DLL (.dll) が生成されるように sources ファイルが記述されています。従って、sources ファイルを作り直し、target name を Oemregistry、target type を DYNLINK にする必要があります。

(2) のサンプル実装は、%_WINCEROOT%/Platform/Common/Src/Common/PerReg/ です。このソースから、oal_perreg.lib という static lilbrary がビルドされます。NOR Flash を搭載したボードの BSP で、NOR Flash に対する OALFlashErase() と OALFlashWrite() が実装されている場合には、oal_perreg.lib を oal.exe のリンクライブラリに追加して、PerReg の初期化関数を OAL の初期化時に呼び出せば(※OEMInit() の末尾がよいでしょう)、そのまま使用することができます。そのようにして oal_perreg.lib を組み込むだけで、NOR Flash の特定領域を使った永続化が、RAM-Based レジストリに対して可能になります。PerReg の初期化関数は、%_WINCEROOT%/Platform/Common/Src/INC/oal_perreg.h で宣言されている OALPerRegInit() です。

ところで、ここまで見てきて、レジストリを永続化する方法には、次の三つがあることが分かりました:

(a) Hive-Based のレジストリを組込み、永続化の設定を行う。

(b) RAM-Based のレジストリと Oemregistry.dll を組み込む(※配置先ファイルシステムは、適切に変更する必要あり)。

(c) RAM-Based のレジストリを組込み、OAL に WriteRegistryToOEM() と ReadRegistryFromOEM() の実装を追加する。

どれが最も良いのでしょうか?結論としては、WinCE 6.0 を動かすハードウェア次第であり、ケースバイケースだと言えます。ただし、NOR Flash を搭載したハードウェアであり、かつ、レジストリの書き換えが頻繁には起こらず、あらかじめ意図した変更内容だけを永続記憶域に保存したい場合には、(c) の方法が最も良いのではないかと思います。

なお、WinCE 6.0 のリファレンスを見ると、次のように書かれています:

 ”The preferred method for persisting registry data is to use the hive-based registry.”
 (Persisting Data with the RAM-Based Registry

 ”The preferred method for persisting data with the RAM-based registry is to include the OEMRegistry.dll sample file in your OS image.”
 (Data Persistence with the RAM-based Registry Using the OAL

これを見ると、最も preferred なのが (a)、その次が (b) で、最後が (c) という順番です。しかし、ここで言う “preferred” の順番は、堅牢性の観点から推奨されるものではなく、より一般的に使える方策の順、および、OS のカスタマイズが必要な度合いが小さい順であると思います。以下に、(a)~(b) の各々について、長所・短所を述べます。

 (a) Hive-Based のレジストリ
  長所:OS のカスタマイズは最小限。「レジストリ変更内容の永続化(2/2)」 で説明したように、OS イメージに組み込むレジストリの設定だけで対応できる。

  短所:レジストリの Hive を配置するファイルシステムを Bootable に設定しなければならないため、そのファイルシステムが破損するなどしてマウントできなくなった場合、ブートの第2フェーズへ遷移できず、第1フェーズの完了待ちで WinCE カーネルの起動が停止してしまう。

  その他:レジストリの Hive ファイルが、WinCE によってオープンされたままとなる。このため、WinCE の起動後にバックアップファイルで上書きするといったことができない。また、RegFlushKey() を呼び出さなくても、暗黙裡に Hive ファイルが更新されるため(※試してみたところでは、起動のたびに、ファイルが更新されます)、レジストリ内容の永続記憶域への書き出しを、意図したタイミングでしか行いたくない場合には、不向き。

 (b) RAM-Based のレジストリ + Oemregistry.dll
  長所:Oemregistry.dll を組み込むだけで対応でき、OAL の改変が不要。レジストリ内容を、ファイルシステム上にファイルとして保存するが、そのファイルシステムは Bootable に設定する必要がない。そのため、保存先のファイルシステムが破損しても、WinCE は起動する。

  短所:レジストリ内容をファイルとして保存するため、レジストリ内容を隠したい場合には不向き。

 (c) RAM-Based のレジストリ + OAL に WriteRegistryToOEM() と ReadRegistryFromOEM() を追加
  長所:レジストリ内容は、ファイルシステムを介さずに保存できるため、ユーザから隠して保護するのが容易。また、NOR Flash を搭載したハードウェアであれば、NOR Flash 用のサンプル実装(PerReg)をそのまま使える。

  短所:OAL の改変が必要。NOR Flash を搭載していないハードウェアの場合には、WriteRegistryToOEM() と ReadRegistryFromOEM() の実装は、それなりに手間がかかる。

どうでしょうか?それぞれの長所と短所は、相対的なものですから、どれかが絶対的に優れているとはいえず、ケースバイケースだと思います。

たとえば、ハードディスク(や SSD)を搭載し、そこに OS イメージも配置する設計のデバイス(ハードウェア)であれば、(a) が最も適しているでしょう。あるいは、上述したように、NOR Flash を搭載していて、レジストリの更新内容を特定の場合にしか保存しなくてよい仕様のデバイスであれば、最も適しているのは (c) だと思います。また、CF カードなどのストレージメディアに Bootable なファイルシステムを置くデバイスで、Bootable なファイルシステムへの書き込みを極力抑えたい、という場合には、(b) が適しているのではないかと思います。

※2011/08/13 追記
RAM-Based のレジストリを永続化した場合、OS イメージを書き換える際に問題となることがあります。新しい OS イメージで、レジストリ項目の追加や削除など、レジストリ内容の変更を行っていても、OS イメージを書き換えた際に、その変更が反映されない、という問題です。これについて、「RAM-Based レジストリの永続化に関する補足」に書きました。

2 comments 2011/08/02 koga

アプリケーションからリブート(または電源 OFF)~その2(2/2)

前回の続きです。WinCE 6.0 のサスペンド動作と、電源 OFF 動作について書きます。

■サスペンド動作
前回、SetSystemPowerState() の第二引数に POWER_STATE_SUSPEND を渡せば、WinCE がサスペンド状態になると書きました。つまり、WinCE をホールト(halt)するには、SetSystemPowerState() を使えばよい、ということです。デバイスの電源を切ってよい状態にすることが目的であれば、それで問題ないでしょう。

SetSystemPowerState() に POWER_STATE_SUSPEND を渡すと、POWER_STATE_RESET を渡した場合と同様、Power Management 機能によって、レジストリやファイルシステムのフラッシュ動作が発火します。その後、Power Management 機能によって、ボードや CPU 固有のサスペンド処理が実行され、電源を切ってよい状態に遷移します。なお、「電源を切ってよい状態」と書きましたが、その状態は、Linux などの UNIX 系 OS で、shutdown コマンドを -h オプション付きで実行した場合に遷移する状態とは、異なります。

上で「サスペンド状態」と書いたように、SetSystemPowerState() に POWER_STATE_SUSPEND を渡すと、いわゆるシャットダウン動作を行うのではなく、サスペンド動作、WinXP で言えば「スタンバイ」動作を行います。つまり、ここで言うサスペンド動作は、Suspend To RAM のことです。Suspend To RAM については、たとえば
 http://e-words.jp/w/STR.html
が参考になるでしょう。Linux で言えば、shutdown コマンドではなく、uswsusp パッケージの s2ram コマンドを使ってサスペンドした状態に相当するのではないかと思います:
 http://suspend.sourceforge.net/
 http://suspend.sourceforge.net/intro.shtml
 http://sourceforge.net/projects/suspend/

さて、サスペンド動作というからには、対応するレジューム動作、つまり、サスペンド状態から通常動作状態に復帰できることを期待したくなります。もちろん、WinCE でも、サスペンド動作とレジューム動作をサポートしています。サスペンド動作による、通常状態からサスペンド状態への遷移、および、レジューム動作による、サスペンド状態から通常状態への遷移(復帰)については、WinCE のリファレンスの次のページで説明されています:

 Suspend State
 http://msdn.microsoft.com/en-us/library/aa916003.aspx

 Resume State
 http://msdn.microsoft.com/en-us/library/aa914868.aspx

上の “Suspend State” のページを見ると、サスペンド動作の発火は、SetSystemPowerState() ではなく、GwesPowerOffSystem() の呼び出しが基点となっています。OS デザインに GWES を組み込んでいる場合、サスペンドするには、SetSystemPowerState() ではなく、GwesPowerOffSystem() を呼び出す方が、より適切です。単に、デバイスの電源を切ってよい状態にするためにサスペンドするのであれば、SetSystemPowerState() を呼び出すだけでも構わないと思いますが、後でレジュームする前提でサスペンドするのなら、GwesPowerOffSystem() を呼び出す必要があります。

GwesPowerOffSystem() を呼び出す代わりに、keybd_event() の第一引数を VK_OFF にして呼び出すのでも構いません。keybd_event() に VK_OFF を渡した場合は、WinCE のスタートメニューから [サスペンド] を選択した場合と、ほぼ等価な動きになります。
GWES と Power Management を組み込んだ OS デザインの場合には(※標準シェルを組み込んでいる場合が、その典型例です)、keybd_event() に VK_OFF を渡してサスペンド動作を発火するのが、GwesPowerOffSystem() を呼び出すよりも簡単かも知れません。

GwesPowerOffSystem() に話を戻します。上の “Suspend State” のページには、GwesPowerOffSystem() を呼び出すと、システムがレジュームするまで呼び出しが完了しないという説明があります。GwesPowerOffSystem() のリファレンスにも、同じ説明が書かれています:
 http://msdn.microsoft.com/en-us/library/aa912858.aspx

レジュームするまで呼び出しから戻らないという振る舞いは、実際には、SetSystemPowerState() が行う PowerOffSystem() の呼び出しにより、カーネルが呼び出す OEMPowerOff() によって達成されます。GwesPowerOffSystem() は、GWES 自身のサスペンド処理を行った後で、SetSystemPowerState() を呼び出します。つまり、keybd_event() や GwesPowerOffSystem()、または SetSystemPowerState() のどれを使ってサスペンド動作を発火しても、サスペンド動作の最終段階で OEMPowerOff() が呼び出されます。

ところで、”Suspend State” のページには、SetSystemPowerState() の呼び出しによって Power manager が行うサスペンド動作において、PowerOffSystem() を呼び出した後、カーネルがサスペンド動作の最終段階の処理を行えるようにするために Sleep(0); を実行する、と書かれています。この説明は、WinCE 6.0 に対しては正しくないように思われます。Power Management モジュール(pm.dll)は、デバイスマネージャにリンクされてロードされますが、WinCE 5.0 までとは異なり、デバイスマネージャは、カーネル(nk.exe)にロードされ、カーネルの一部として動作します。従って、Power manager による PowerOffSystem() の呼び出しは、通常の関数呼び出しと同様にカーネル本体に引き継がれます。つまり、PowerOffSystem() を呼び出したスレッドによって、カーネル本体側で実装されているサスペンド処理が実行されるのです。

Power manager が、PowerOffSystem() を呼び出した後、Sleep(0); を実行することによって、カーネルにサスペンド動作の最終段階処理を行わせる(※スケジューラにスケジューリング処理させ、その結果として、カーネルのサスペンド動作を起動する)という説明は、WinCE 5.0 までのマイクロカーネル構造に基づく説明のように思われます。WinCE 6.0 では、デバイスマネージャはカーネルにロードされる DLL ですが(device.dll)、WinCE 5.0 までは、DLL ではなく EXE であり(device.exe)、独立したプロセスとして動作する仕組みでした。その場合、Power manager から PowerOffSystem() を呼び出すと、デバイスマネージャのプロセスからカーネルのプロセスに対する呼び出しになりますので、カーネル本体側のサスペンド処理は、PowerOffSystem() を呼び出したスレッドとは別のスレッドによって実行される筈です。このため、Power manager が PowerOffSystem() の呼び出しを行った後、カーネル本体のサスペンド処理が実行されるのを待たずに、その先の処理へ進んでしまうことがないように、Sleep() を呼び出すのではないかと思います。

ここで、「その先の処理」というのは、PowerOffSystem() の呼び出しから戻った後の処理、つまり、Power manager によるレジューム動作を指します。Power manager によるサスペンドとレジューム処理は、
 <WINCE600>/PUBLIC/COMMON/OAK/DRIVERS/PM/PDD/DEFAULT/platform.cpp
にある PlatformSetSystemPowerState() で実装されています(431行目付近)。

PowerOffSystem() に話しを戻します。PowerOffSystem() に対応するカーネル本体側のサスペンド処理は、
 <WINCE600>/PRIVATE/WINCEOS/COREOS/NK/KERNEL/kwin32.c
にある NKPowerOffSystem() で実装されています。このソースを見れば、”Suspend State” のページに書かれている、カーネルが行うサスペンド動作の最終段階処理が実際に行われているのが分かるでしょう。NKPowerOffSystem() では、まず、自身のスレッド優先度を最高値(0)に変更して、次に GWES と FileSys の Power off 動作を行った後、内部関数の CallOEMPowerOff() を介して OEMPowerOff() を呼び出します。OEMPowerOff() の呼び出しから戻ると、上の “Resume State” のページに書かれた動作を行います。

OEMPowerOff() は、OAL の関数であり、ボードや CPU ごとに異なるサスペンド処理の実装を提供します:
 http://msdn.microsoft.com/en-us/library/aa916963.aspx

OEMPowerOff() の役割は、レジュームに必要なデータ(レジスタの内容など)を RAM に退避した後、CPU をホールト(halt)状態にして、外部からの割り込みが発生するまで CPU を休眠させることです。休眠を解除するトリガとなる割り込みは、CPU をホールト状態にする前に設定します。割り込みが発生して CPU が起床すると、RAM に退避していたデータを使って、ホールト前の状態を復元し、OEMPowerOff() の呼び出し元に制御を戻す(return する)のです。

デバイスエミュレータの場合、OEMPowerOff() のソースは、
 <WINCE600>/PLATFORM/DEVICEEMULATOR/SRC/OAL/POWER/power.c
です。このソースを見ると、OEMPowerOff() の中で CPUPowerOff() という関数を呼び出しており、CPUPowerOff() が、CPU をホールト状態にする処理を行っていることが分かります。CPUPowerOff() は、
 <WINCE600>/PLATFORM/DEVICEEMULATOR/SRC/OAL/OALLIB/cpupoweroff.s
で実装されています。興味がある人は、cpupoweroff.s にある CPUPowerOff() のソースを見てみて下さい。レジュームに必要なデータを RAM へ退避した後、サスペンド状態を解除するトリガとなる割り込みに対する設定を行い、CPU に対するクロック供給を停止してホールトさせる様子が分かるでしょう。なお、CPU をホールトさせる処理自体は、cpupoweroff.s ではなく、同じディレクトリにある startup.s で実装されています。startup.s で実装されている StartUp() には、通常の呼び出しの場合に、冒頭のジャンプ命令(’b ResetHandler’)によってスキップされる箇所があるのですが、そこ(通常の呼び出しではスキップされる箇所)で、CPU をホールトさせています。

デバイスエミュレータの OEMPowerOff() の実装では、CPU をホールトさせた後(S3C2410 を SLEEP モードに設定した後)、外部割り込みによって CPU が通常モードに復帰すると、startup.s にある StartUp() のコードの、’ResetHandler’ というラベル以降を実行するようになっています。これは、ホールト状態から通常モードに復帰した後に行わなければならない、CPU 内部の再初期化処理が、パワーオンリセット時の初期化処理と共通する部分が多いため、コードを共有するための措置ではないかと思われます。StartUp() の ‘ResetHandler’ 以降のコードでは、パワーオンリセットによる呼び出しでないかどうかを要所ごとに判定しながら初期化処理を進め、レジューム用のデータが正しく退避されていると最終的に判断できたら、cpupoweroff.s の ‘Awake_address’ ラベルへジャンプするようになっています。’Awake_address’ ラベル以降のコードでは、RAM に退避していた CPU のレジスタ内容を復元した後、CPUPowerOff() の戻り先(呼び出し元)アドレスへジャンプします。これによって、CPUPowerOff() の呼び出しが完了し、OEMPowerOff() の実行が再開します。

OEMPowerOff() は、CPUPowerOff() の呼び出しから戻った後、レジューム動作を開始します。同様に、OEMPowerOff() を呼び出した NKPowerOffSystem() はカーネル本体のレジューム動作を実行し、さらに、その呼び出し元、つまり、PlatformSetSystemPowerState() や、SetSystemPowerState() ならびに GwesPowerOffSystem() において、それぞれのレジューム動作が実行され、サスペンド前の状態に復帰する、というわけです。

なお、デバイスエミュレータをサスペンドすると、デバイスエミュレータのウィンドウが閉じてしまい、以降は操作できなくなってしまいます。その状態では、デバイスエミュレータのプロセスは終了せずに残っているため、タスクマネージャを使って kill しない限り、[ターゲット] -> [デバイスの接続] メニューで Platform Builder からデバイスエミュレータを起動し直すことすら、できないのです。デバイスエミュレータをサスペンドした後、電源ボタンやキーボードによる割り込みの発生をエミュレートできれば面白いのですが、その方法は、ないようです。とりあえずの対策として、power.c を編集し、OEMPowerOff() からの CPUPowerOff() の呼び出しをコメントアウトして使ったという例が、Bruce Eitman という方の Blog エントリで紹介されています:
 http://geekswithblogs.net/BruceEitman/archive/2008/08/11/platform-builder-getting-the-emulator-to-resume.aspx
 http://geekswithblogs.net/BruceEitman/archive/2008/05/13/profile-of-bruce-eitman.aspx

サスペンドの開始からレジュームの完了に至るまでの一連の動作を、デバイスエミュレータとカーネルデバッガを使って確認するには、上の Blog エントリで紹介されているように、OEMPowerOff() からの CPUPowerOff() の呼び出しをコメントアウトするのが簡単です。
僕も、この記事を書く際に、その方法を使ってサスペンドとレジュームの動作をカーネルデバッガで追跡しました。

サスペンド動作について、もう一つだけ書いておきます。この項の冒頭で、SetSystemPowerState() によるサスペンド動作では、レジストリやファイルシステムのフラッシュ動作を発火させ、その後で、ボードや CPU 固有のサスペンド処理を実行すると書きました。レジストリやファイルシステムのフラッシュ動作を発火させるのは、メモリ上のキャッシュのみが更新されたまま電源が切られてしまい、永続記憶域上のデータに不整合が発生することを防ぐためでした。しかし、フラッシュ動作を発火させた後、サスペンドが完了するまでの間に、書き込みモードでファイルを開いているアプリケーションがファイルに対する書き込みを行ったら、どうなるのでしょうか?

心配は、不要です。ファイルシステムを管理する File System Disk Manager (fsdmgr.dll) によって、フラッシュ動作後のファイルアクセスは、ブロックされます。WriteFile() や ReadFile() などのファイルアクセス API の全ての呼び出しは、ファイルシステムの実体(ファイルシステムのドライバ)による実装が呼び出される前に、fsdmgr.dll によるチェックが行われ、そのファイルシステムが所属するボリュームが電源 OFF 状態の場合、電源が ON されて利用可能になるまでの間、ブロックされる(待たされる)ようになっています。レジストリやファイルシステムのフラッシュ動作においては、ファイルをアクセスしているスレッドが全てアクセスし終えるのを待ち、アクセスし終わった時点で、メモリ上のファイルキャッシュを全てコミットしてから、マウントされている全てのボリュームを電源 OFF 状態にします。それ以降にファイルアクセスを行おうとしたスレッドは、ファイルアクセス API 内部でブロックされますので、フラッシュ動作後に再度メモリ上のファイルキャッシュ内容が更新されてしまう、ということは起きないのです。

fsdmgr.dll による、ボリュームの電源 OFF 時の保護動作に興味がある人は、ソースをご覧になってみて下さい。fsdmgr.dll のソースは、
 <WINCE600>/PRIVATE/WINCEOS/COREOS/STORAGE/FSDMGR/
にあります。このディレクトリの、mountedvolume.{hpp,cpp} や {file,fsdmgr,volume}api.{cpp,hpp} が、関連するソースです。

■電源 OFF について
サスペンド動作の項が、だいぶ長くなってしまいました。電源 OFF については、短めにします。

実は、WinCE の Power Management では、電源 OFF 状態のサポートは、標準では提供されていません。つまり、デフォルトの実装では電源 OFF 状態がサポートされていないため、WinCE デバイスを実装する側(OEM)での対応が必要です。実際、SetSystemPowerState() に POWER_STATE_OFF を渡しても、エラーが返ります。エラーが返る直接の原因は、Power Management に関するレジストリ設定で、POWER_STATE_OFF に対応する電源状態の指定が存在しないからです。デバイスが取りうる電源状態は、D0~D4 がありますが、電源 OFF 状態を指す D4 と、POWER_STATE_OFF とを関連づける指定が存在していないため、SetSystemPowerState() に POWER_STATE_OFF を渡すと、無効な状態が指定されたものとして扱われ、エラーになるのです。

D0~D4 の電源状態の説明は、WinCE のリファレンスで “Device Power States” を見て下さい:
 http://msdn.microsoft.com/en-us/library/aa932261.aspx

Power Management に関するデフォルトのレジストリ設定は、
 <WINCE600>/PUBLIC/COMMON\OAK/FILES/common.reg
にあります。common.reg をテキストエディタで開き、’HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Power\State’ で検索してみて下さい。このレジストリキーによる、電源状態の指定は、WinCE のリファレンスの “Mapping of System Power States to Device Power States” で説明されています:
 http://msdn.microsoft.com/en-us/library/aa920052.aspx

common.reg での指定を見ると、”Flags” キーに POWER_STATE_OFF を指定した有効な設定が、存在しません。POWER_STATE_OFF と D4 を関連づけた、ShutDown という名前の指定があるのですが、コメントアウトされています。コメントアウトされた ShutDown に対しては、以下のコメントが記されています:

;;;; Entering this system power state shuts down the system. All devices are powered off,
;;;; resuming may require user intervention. The system will cold boot on resume.
;;;; Supporting this power state requires that the OEM customize the Power Manager
;;;; to recognize POWER_STATE_OFF and take platform-specific action to remove
;;;; power.

ここで、ShutDown の指定を有効にするには、Power Manager のカスタマイズが必要だと書かれています。これに対応するのが、Power Manager (pm.dll)のソースのうち、
 <WINCE600>/PUBLIC/COMMON/OAK/DRIVERS/PM/PDD/DEFAULT/platform.cpp
です。platform.cpp にある PlatformSetSystemPowerState() の本体で、POWER_STATE_RESET に対するチェックを行っている箇所に、以下のコメントがあります(358行目付近):

// Handle resets and shutdowns here, after flushing files. Since Windows CE does
// not define a standard mechanism for handling shutdown (via POWER_STATE_OFF),
// OEMs will need to fill in the appropriate code here. Similarly, if an OEM does
// not support IOCTL_HAL_REBOOT, they should not support POWER_STATE_RESET.

あなたがアプリケーションの開発を担当していて、お使いの WinCE デバイスがソフトウェアによる電源 OFF 機能をサポートしている場合には、SetSystemPowerState() に POWER_STATE_OFF を渡すことによって、アプリケーションから電源 OFF できるかも知れません。それができるかどうかは、お使いの WinCE デバイスのハードウェアが、ソフトウェアによる電源 OFF 機能を持っているか(※CPU から電源回路を OFF するための支援機能がハードウェアに組み込まれているか)と、その WinCE デバイスの OAL が、POWER_STATE_OFF に対する KernelLibIoControl() をサポートしているかにかかっています。

SetSystemPowerState() で POWER_STATE_OFF を使えるかどうかを簡単に調べる方法は、実際にテストコードを書いて動かしてみるか、または、レジストリの HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Power\State キー配下を調べ、”Flags” キーに POWER_STATE_OFF (0×00020000) を指定したエントリがあるかどうかを確認することだと思います。

POWER_STATE_OFF の指定を使えない場合は、手作業でデバイスの電源を切るしかありません。もちろん、電源を切る前には、デバイスをサスペンド状態にして下さい。サスペンド状態への遷移をアプリケーションから行う場合は、「サスペンド動作」の項で述べたように、keybd_event(VK_OFF, 0, 0, 0); を実行するのが簡単でしょう。OS デザインに Power Management と GWES を組み込んでいない場合は、WinCE のリファレンスの次のページが参考になるでしょう:

・OEM Power Off State
 http://msdn.microsoft.com/en-us/library/aa913544.aspx

このページの説明には、GWES や Power Management が組み込まれていない場合、サスペンドするには、次の呼び出しを実行すればよいと書かれています:

 FileSystemPowerFunction(FSNOTIFY_POWER_OFF);
 PowerOffSystem();

Add comment 2009/07/21 koga

圧縮フォント

日本語フォントを追加したら、OS イメージのサイズが大きくなり過ぎてしまい、ROM に収まらなくなってしまう、という場合があります。このような場合、圧縮されたフォントファイルを使うように設定することで、OS イメージのサイズを小さくできます。

WinCE では、日本語などのアジア言語圏用のフォントに対しては、非圧縮の True Type フォントファイル(拡張子 .ttc)を使うか、圧縮形式のフォントファイル(拡張子 .ac3)を使うかを、カタログ項目の選択で切り替えできます。日本語の場合、対応するカタログ項目は、次のものです:

 コア OS
  CEBASE
   インターナショナル
    ロケール特定サポート
     日本語
      Monotype Imaging AC3 フォント圧縮

このカタログ項目を選択すると、日本語フォントのフォントファイルは、非圧縮の .ttc ではなく、圧縮形式の .ac3 が OS イメージに含められます。これらのフォントファイルは、<WINCE600>/PUBLIC/COMMON/OAK/FILES/ に配置されています。

たとえば、MS Gothic(「MS ゴシック、MS P ゴシックおよび MS UI Gothic」)の場合、非圧縮のフォントファイルが msgothic.ttc、圧縮形式のフォントファイルが msgothic.ac3 です。両者のファイルサイズを比べると、msgothic.ttc は 7.89[MB]、msgothic.ac3 は 4.63[MB] で、3[MB] 以上もサイズが違います。

上記カタログ項目の「Monotype Imaging AC3 フォント圧縮」は、SYSGEN 変数 ‘SYSGEN_AGFA_FONT’ に関連づけられており、この変数が定義されると、’FONTS_AC3_VERSIONS’ という SYSGEN 変数も有効になるようです。<WINCE600>/PUBLIC/COMMON/OAK/FILES/ にある common.bib と common.reg を見ると、FONTS_AC3_VERSIONS が有効かどうかにより、フォントファイルとして .ttc を使うか .ac3 を使うかが切り替わるのが分かります。

「Monotype Imaging AC3 フォント圧縮」(”Monotype Imaging AC3 Font Compression”)についての説明は、MSDN のリファレンスの
 http://msdn.microsoft.com/en-us/library/aa912914.aspx
に説明があります。このカタログ項目は、WinCE 6.0 から名前が変わっています。上のページにも説明がありますし、
 http://msdn.microsoft.com/en-us/library/aa924053.aspx
にも書かれています(”Agfa AC3 Font Compression”)。

ちなみに、.ac3 形式のフォントファイルを展開する処理は、decomdll.dll というフォントドライバプラグインによって実装されているようです:
 http://msdn.microsoft.com/en-us/library/aa913710.aspx
この decompdll.dll および、decomdll.dll で実装されているルーチンの呼び出しを行う部分(※GDI/GWES 内部から呼び出されるようです)は、ビルド済みのバイナリのみ提供されるコンポーネントであるため、ソースを見て詳細を理解することはできません。

1 comment 2008/07/08 koga


Categories

Links

Posts by Authors

Recent Posts

Calendar

2019年8月
« 9月    
 123
45678910
11121314151617
18192021222324
25262728293031

Posts by Month

Posts by Category

Meta