Posts filed under 'OS のコンフィグレーション'

WEC 7 の USB Audio ドライバ

Windows Embedded Compact 7 (WEC 7) で追加になった機能の中に、USB Audio のクラスドライバがあります。このドライバを組み込めば、オーディオ出力のハードウェアがないデバイスでも、USB Audio アダプタを使って音声出力できます。今回は、実際に動作確認してみた結果を紹介します。

■USB Audio アダプタ
今回動作確認に使ったのは、エアリア社製の「響音4」という製品です:
 http://www.area-powers.jp/product/usb_product/product/kyo-on/u1sounds4.html

家電量販店の店頭で購入しましたが、¥2,000未満の価格で、お手頃です。購入して、Windows 7 の PC に接続してみたところ、パッケージの箱の説明どおり、付属ドライバのインストール無しで、動作しました。つまり、USB Audio の規格に合致しているので、Windows 7 の標準ドライバで動作します。Windpows 7 のデバイスマネージャでデバイス情報を見たところ、USB の Vendor ID が 0D8C ですから、台湾の C-Media Electronics 社のチップセットを使っているようです:
 http://www.cmedia.com.tw/ProductsIndex.aspx

■USB Audio クラスドライバ
WEC 7 の USB Audio クラスドライバは、リファレンスに簡単な説明が載っています。

 http://msdn.microsoft.com/en-us/library/gg158818.aspx (USB Host Driver Catalog Items and Sysgen Variables)

上のページを見ると、SYSGEN_USB_AUDIO という Sysgen 変数が定義されると組み込まれ、出力と入力をサポートしているようです。このドライバのソースコードは、WEC 7 のソースツリーで、以下の場所にあります。
 %_WINCEROOT%/public/COMMON/oak/drivers/wavedev/wavedev2/usb/

このソースを見ると、wavemain.cpp にある Waveform Audio Driver の関数実装のうち、WAV_IOControl() において、入力デバイスに対するメッセージ(WIDM_*)の応答動作も実装されており、出力と入力をどちらもサポートしていることが分かります。Waveform Audio Driver については、リファレンスをご覧ください:
 http://msdn.microsoft.com/en-us/library/ee485907.aspx (Waveform Audio Driver Reference)

■USB Audio 機能の組み込み
USB Audio クラスドライバを OS イメージに組み込むには、Platform Builder のカタログ項目ビュー(Catalog Items View)で、次のカタログ項目を選択して下さい:

 <OS Design 名>
  Core OS
   Device Drivers
    USB
     USB Host
      USB Class Drivers
★      USB Audio Class Driver

USB ポート(USB ホストのポート)を搭載した WEC 7 デバイスであれば、これにより、音声出力できるようになります。USB Audio クラスドライバのレジストリ設定ファイル(usbaudio.reg)を見ると分かりますが、Audio クラスを宣言する全ての USB デバイスに対して、このドライバを USB Host Client Driver として登録する設定になっており、USB Audio デバイスの Vendor ID や Product ID を意識する必要は、ありません。

弊社が BSP を提供している CPU ボードである、アットマークテクノ社の Armadillo-440 で試してみたところ、冒頭で紹介した USB Audio アダプタ(エアリア社の響音4)を使って、ヘッドフォンに音声出力できました。エクスプローラのディレクトリアイコンをダブルクリック(ダブルタップ)した際などに、効果音が出力されます。Armadillo-400 シリーズ用 BSP の、公開している評価版では、Armadillo-440 液晶モデルの音声出力インタフェースをサポートしていませんが、USB Audio アダプタと組み合わせれば、WEC 7 の USB Audio クラスドライバを使って音声出力できるというわけです。

さらに、DirectShow と Windows Music Player、および、MP3 と WMA のコーデックを OS イメージへ組み込むことにより、MP3 や WMA の音楽ファイルを再生できることも確認できました。

以上、WEC 7 で追加された USB Audio クラスドライバの紹介です。興味のある方は、ぜひ試してみて下さい。

Add comment 2012/02/12 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

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

タスクバーの非表示~Taskman Shell の組込み

前回 は、OS をカスタマイズせずに、レジストリ設定や API 呼び出しだけでタスクバーの非表示対応(アプリケーションのフルスクリーン表示対応)を行う方法について、説明しました。今回は、タスクバーを表示しないシェルを組み込む方法について説明します。

■Taskman Shell
WinCE 6.0(および WEC 7)には、タスクバーを表示しないカスタムシェルのソースコードが付属しており、WinCE 6.0 のリファレンスでも説明されています:
 http://msdn.microsoft.com/en-US/library/ee502244(v=WinEmbedded.60).aspx (Including the Taskman Shell)

標準シェルを使用する必要がなく、専用のフルスクリーン表示アプリケーションを搭載するデバイスを開発する場合には、標準シェルの代わりに、この Taskman Shell を OS イメージに組み込むのが、簡単で良いのではないかと思います。

ただし、上のページに書かれている、Taskman Shell を組み込む方法は、実際には使えません。説明が間違っているように思われます。そこで、Taskman Shell を組み込むやり方について、以下に一例を述べます。

■Taskman Shell の組み込み
上記の、WinCE 6.0 のリファレンスページでは、Taskman Shell を OS イメージに組み込む場合、’__SYSGEN_TASKMAN’ という環境変数の値を 1 に設定すればよいと書かれています。しかし、その設定を行っても、Taskman Shell は OS イメージに組み込まれず、それどころか、Tasklman Shell のビルドすら行われません。WinCE のソースツリー配下のファイルを ‘__SYSGEN_TASKMAN’ で検索しても、この環境変数を参照している個所は見当たりませんし、さらに、Taskman Shell のソースコードの親ディレクトリ(%_WINCEROOT%/Public/Wceshellfe/Oak/)にある dir ファイルを見ると、Taskman Shell のディレクトリ(taskman)が記載されておらず、ビルド対象から外れていることが分かります。

では、どうすれば、OS Design をビルドした時に、Taskman Shell が OS イメージへ組み込まれるようになるのでしょうか?以下に、Taskman Shell を OS イメージへ組み込む手順の一例を述べます。

1.) taskman/ ディレクトリを、ビルド対象に含める。
 VS 2005/Platform Builder の「ソリューションエクスプローラー」で、TASKMAN のアイコンを右クリックしてポップアップメニューを開き、[ビルドに含める] を選択します。TASKMAN は、ソリューションエクスプローラーで、次の階層にあります:

 
  C:/WINCE600
   PUBLIC
    wceshellfe
     OAK
      TASKMAN ★

注:確認していませんが、この(1)(「ビルドに含める」)は、必要ないかも知れません。

2.) %_WINCEROOT%/PUBLIC/CEBASE/OAK/MISC/ にある wceshellfe.bat を編集して、次の行を追加する:


    REM for Taskamn shell
    if "%__NO_SYSGEN_TASKMAN%"=="1" goto noTaskmanShell
        rem Turn off StandardShell
        set __SYSGEN_EXPLORER=
        rem add TaskmanShell to WCESHELL module
        set WCESHELLFE_MODULES=%WCESHELLFE_MODULES% taskman
    :noTaskmanShell


 上の行を追加する位置は、wceshellfe.bat の440行付近にある、以下の行の直前にして下さい。


    goto :EOF
:Not_Pass1


3.) OS Design をリビルドする。

上記の手順により、OS イメージ(nk.bin)に taskman.exe が追加され、さらに、レジストリの [HKEY_LOCAL_MACHINE\init] キーの Launch50 の値が "taskman.exe" になります。標準シェル(explorer.exe)は OS イメージには収録されず、標準シェルを Taskman Shell が置き換えられます。ここで、標準シェルが OS イメージに収録されないのは、上の (2) で wceshellfe.bat に追加した行の中の


        set __SYSGEN_EXPLORER=


という行の働きによるものです。一方、Taskman Shell が OS イメージに収録されるのは、


        set WCESHELLFE_MODULES=%WCESHELLFE_MODULES% taskman


という行の働きによるものです。この行の意味については、2011/08/14 に書いた「CESYSGEN 条件文での ‘Component’ 名」を併せ読んでみて下さい。この行の働きにより、taskman がビルドターゲットの依存ファイル/ターゲットの一部となり、ビルドされるようになるというわけです。さらに、 %_WINCEROOT%/Public/Wceshellfe/Oak/FILES/ にある wceshellfe.bib と wceshellfe.reg の中の taskman.exe に関する記述が、上の行の設定により有効になる、というわけです。以下に、wceshellfe.bib と wceshellfe.reg の当該個所を引用します:

wceshellfe.bib


; @CESYSGEN IF WCESHELLFE_MODULES_TASKMAN
	taskman.exe     $(_FLATRELEASEDIR)\taskman.exe             		NK  SH
; @CESYSGEN ENDIF


wceshellfe.reg


; Choose only *one* of the taskman or explorer components in your cesysgen.bat
; @CESYSGEN IF WCESHELLFE_MODULES_TASKMAN
[HKEY_LOCAL_MACHINE\init]
	"Launch50"="taskman.exe"
	"Depend50"=hex:14,00, 1e,00
; @CESYSGEN ENDIF

Taskman Shell を OS イメージに収録せず、標準シェルに戻す場合は、%_WINCEROOT%/PUBLIC/CEBASE/OAK/MISC/wceshellfe.bat に追加した行を削って元に戻すか、または、'__NO_SYSGEN_TASKMAN' という環境変数の値を 1 に設定して OS Design をビルドして下さい(*)。

* 上述した例では、'__NO_SYSGEN_TASKMAN' という環境変数を設定しない場合、Taskman Shell が組み込まれます。つまり、デフォルトで Taskman Shell が組み込まれる設定になってしまいます。どちらかといえば、デフォルトでは標準シェルが組み込まれるように設定できるのが良いと思います。つまり、冒頭で述べた WinCE 6.0 のリファレンスページに書かれているように、'__SYSGEN_TASKMAN' という環境変数を 1 に設定した時にだけ、Taskman Shell が組み込まれるようにできる方が良いでしょう。しかし、試してみたところ、wceshellfe.bat に追加する行の中で '__SYSGEN_TASKMAN' という環境変数を参照して条件分岐するようにしても、期待通りに動作しませんでした。簡単に動きを追ってみたところ、Platform Builder の環境変数設定で __SYSGEN_TASKMAN の値を設定しても、wceshellfe.bat の当該個所が実行される時点で、__SYSGEN_TASKMAN の値が空になってしまっていることが原因でした。詳細は不明ですが、WinCE のビルドシステムにおいて、'__SYSGEN' という接頭辞の環境変数に対しては、ビルド処理を開始する前に、その値を空にする内部処理を実行しているのかも知れません。
 おそらく、今回述べた手順例よりも、もっと良いやり方があるのではないかと思います。WinCE のビルドシステムに対し、ビルド内容をカスタマイズする「フック」の仕組みが提供されていますから、その仕組みを使うことで、もっとエレガントな対応ができるのかも知れません。興味のある方は、ご自分で調べて、追ってみて下さい。

2 comments 2011/08/18 koga

CESYSGEN 条件文での ‘Component’ 名

WinCE のビルドシステムでは、.bib ファイルや .reg ファイルなどで、@CESYSGEN IF … @CESYSGEN ENDIF という構文を使って、条件指定を記述できます。たとえば、WinCE のカタログ項目で「SD バス ドライバー」が選択されたら、BSP 固有の SD ホストコントローラのドライバが OS Design へ組み込まれるようにするには、BSP の platform.bib において、次のように記述します:

; @CESYSGEN IF CE_MODULES_SDBUS2
    <SD ホストドライバ名>.dll       $(_FLATRELEASEDIR)\<SD ホストドライバ名>.dll            NK  SHK
; @CESYSGEN ENDIF CE_MODULES_SDBUS2


ここで、条件指定に使っている ‘CE_MODULES_SDBUS2′ という変数名は、どうやって決まるのでしょうか?カタログ項目「SD バス ドライバー」の Sysgen 変数(システム生成変数)は ‘SYSGEN_SDBUS’ ですから、Sysgen 変数の名前を使っているわけでは、ありません。今回は、この条件指定に使う変数名について調べてみて分かったことを記します。

まず、WinCE のリファレンスの CESYSG 条件文に対する説明は、次のページです:

 http://msdn.microsoft.com/en-US/library/ee478869(v=WinEmbedded.60).aspx (Preprocessing Using Cesysgen Conditionals)

このページでは、CESYSGEN 条件文で指定する変数名(’Component’ 名)について、次のように説明しています。

・対応するものが module の場合は、<OS tree name>_MODULES_<module_name>。

・対応するものが component の場合は、<module_name>_<component_name>。

ただし、この説明では、’module’ と ‘component’ について具体的な説明がなく、また、<OS tree name>, <module_name>, <component_name> について、説明されていません。これらについては、%_WINCEROOT%/PUBLIC/ 配下の .bat ファイルに記述された、ビルド用の変数設定を見るのが確実だと思います。

たとえば、カタログ項目「SD バス ドライバー」の例でいえば、プロパティの「モジュール」が sdbus2.dll ですが、’sdbus2′ をキーにして %_WINCEROOT%/PUBLIC/ 配下の .bat ファイルを検索すると、%_WINCEROOT%/PUBLIC/CEBASE/OAK/MISC/winceos.bat がヒットします。ヒットするのは、以下の行です:

        set CE_MODULES=%CE_MODULES% sdbus2


ここで、CE_MODULES というビルド変数は、%_WINCEROOT%/PUBLIC/common/CESYSGEN/makefile で参照されており、ターゲット ‘preproc’ の依存ファイルリストの一部として記述されています。つまり、ビルドの preproc ステージで、CE_MODULES にセットされたターゲット群が、依存ファイルとしてビルドされるというわけです。

さて、上で述べた、CESYSGEN 条件文で指定する Component 名の ‘module’/'component’ は、このビルド変数の名前の末尾が ‘_MODULES’ と ‘_COMPONENTS’ のどちらなのか、に対応するようです。sdbus2 の場合は、CE_MODULES というビルド変数の値の一部としてセットされますので、’module’ です。そして、Component 名は、ビルド変数の名前に ‘_’ と、.dll/.exe の名前(つまり、当該 module/component のビルドターゲット名)を大文字にしたものを連結した形式となるようです。sdbus2 の場合は、CE_MODULES_SDBUS2 ですね。

Component 名が ‘component’ の場合の例は、pmif です。pmif は、%_WINCEROOT%/PUBLIC/CEBASE/OAK/MISC/winceos.bat の中で、次のように DEVICE_COMPONENTS というビルド変数の値の一部としてセットされます。

          set DEVICE_COMPONENTS=%DEVICE_COMPONENTS% pmif


pmif は、対応するカタログ項目が選択された場合に、DeviceManager (devmgr.dll) にリンクされるライブラリ(pmif.lib)に対するビルドターゲットです。CESYSGEN 条件文の変数名は、Component 名が ‘component’ の場合、ビルド変数の名前から末尾の ‘_COMPONENTS’ を削って、’_’ とビルドターゲット名を大文字にしたものを連結した形式となるようです。つまり、pmif に対しては、DEVICE_PMIF となります。DEVICE_PMIF は、たとえば %_WINCEROOT%/PUBLIC/common/OAK/FILES/common.bib で参照されており、common.bib には次の行があります:

; @CESYSGEN IF DEVICE_PMIF
   pm.dll       $(_FLATRELEASEDIR)\pm.dll                      NK  SHMK
; @CESYSGEN ENDIF


ちなみに、pmif に対応するカタログ項目は、「電源管理 (完全)」(Sysgen 変数:SYSGEN_PM)と「電源管理 (最小)」(Sysgen 変数:SYSGEN_PMSTUBS)です。これらのカタログ項目が、どちらも選択されていない場合は、pmif.lib が devmgr.dll にリンクされません。pmif が DEVICE_COMPONENTS の値の一部にセットされることにより、devmgr.dll のリンクライブラリに pmif.lib が追加される様子は、%_WINCEROOT%/PUBLIC/common/CESYSGEN/makefile をご覧ください。

さらに補足すると、pmif.lib のソースは、WinCE のカーネルソースの一部となっており、%_WINCEROOT%/PRIVATE/winceos/COREOS/device/pmif/ に配置されています。

さて、CESYSGEN 条件文で指定する Component 名の命名規約の説明における、’module’ と ‘component’ の違いは、上で述べた実例を振り返ると、おそらく、次のような使い分けなのだと思います:

・module
 ビルドターゲットが、.dll または .exe、つまり、executable であるもの。

・component
 ビルドターゲットが、.lib などの、ビルドの中間生成ファイルであるもの。

また、module については、Platform Builder のカタログ項目ビューで対応する項目のプロパティを見ると、「モジュール」という項目があり、component については、そうではない、ということも言えそうです。

module と component の概念については、WinCE のリファレンスの、次のページにも記述があります:

 http://msdn.microsoft.com/en-US/library/ee482113(v=WinEmbedded.60).aspx (Windows Embedded CE Modules and Components)

このページの説明には、component は、module の構成要素であり、module によっては、(コンフィグレーションにより組込むかどうかを切り替えできる)オプションの component を含む、と書かれています。上で述べた解釈は、この説明にも合っていると思います。

蛇足ですが、.bat ファイルにおける、OS Design に module を含める記述の例(ビルド変数 XXX_MODULES の設定例)が、WinCE のリファレンスの次のページに載っています:

 http://msdn.microsoft.com/en-US/library/ee478860(v=WinEmbedded.60).aspx (Cesysgen Batch File)

Add comment 2011/08/14 koga

RAM-Based レジストリの永続化に関する補足

先日書いた「レジストリの永続化~RAM-Based の場合」では、RAM-Based のレジストリを永続化した場合の注意点として、次のことを書きました:

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

これに加えて、もう一つ注意すべきことがあります。それは、OS イメージを更新する場合です。

OS イメージを更新して、NOR Flash などに新しいイメージを書き込む際、新しいイメージに収録されたレジストリの内容が、置き換え前のイメージに収録されたものと異なっていた場合は、Hive-Based のレジストリと RAM-Based のレジストリで振る舞いが異なります。Hive-Based のレジストリでは、新しいイメージに収録されたレジストリ内容が優先されるのに対し、RAM-Based のレジストリでは、永続記憶域に保存されているレジストリ内容が優先されるのです。

■永続化されたレジストリ内容の扱い方の違い
以下に、OS イメージが置き換えられた際に、永続記憶域に保存されているレジストリ内容がどう扱われるのか、Hive-Based の場合と RAM-Based の場合のそれぞれについて、順に述べます。

(1)Hive-Based の場合
Hive-Based レジストリの場合に、OS イメージが置き換えられた直後のブートにおいて、OS イメージに収録されたレジストリの内容が優先される様子は、リファレンスの次のページの説明を見て下さい:

 http://msdn.microsoft.com/en-US/library/ee489764(v=WinEmbedded.60).aspx (Hive-based Registry Initialization)

このページの、Filesys.dll による Hive-Based レジストリの初期化手順の 15 には、次の記述があります:

 15. If a clean registry is not required, the system registry hive file (System.hv) is loaded from the file system that contains it. A signature in the system hive file is checked against a signature in the ROM portion of the system registry. If the signatures do not match, a clean registry is required, even if this is not indicated by IOCTL_HAL_GET_HIVE_CLEAN_FLAG.

つまり、永続記憶域に保存されているレジストリファイル(System.hv)の signature と、OS イメージに収録されている System.hv の signature を照合し、signature が等しくない場合は、永続記憶域に保存されているレジストリファイルを捨てて初期化する、というわけです。

(2)RAM-Based の場合
RAM-Based レジストリを永続化した場合に、OS イメージが置き換えられても、永続記憶域に保存されているレジストリ内容が優先される様子は、リファレンスの次のページの説明を見て下さい:

 http://msdn.microsoft.com/en-US/library/ee490800(v=WinEmbedded.60).aspx (OAL Initialization of RAM-based Registry)

このページの、OAL での対応により RAM-Based のレジストリを永続化した場合の、Filesys.dll によるレジストリの初期化手順の 2 には、次の記述があります:

 2. If the boot process is a cold boot, Filesys.dll performs the following steps:
  a. Filesys.dll loads the registry data stored in the Default.fdf file from ROM.
  b. If pReadRegistryFromOEM is implemented in the OAL and registry data is available, Filesys.dll deletes the registry data that was restored from Default.fdf. Filesys.dll calls pReadRegistryFromOEM until all of the data is returned. If the data retrieval fails, Filesys.dll cleans the registry and restores Default.fdf.

つまり、OAL が ReadRegistryFromOEM() を実装しており、それを呼び出してレジストリ内容を永続記憶域からロードできた場合は、OS イメージから取り出したレジストリ内容を捨てて、ReadRegistryFromOEM() でロードした内容を使う、ということです。

■RAM-Based レジストリの場合の注意
上で述べたように、RAM-Based のレジストリを永続化した場合は、OS イメージを置き換えても、永続記憶域に保存されたレジストリ内容が優先され、新しい OS イメージに収録されたレジストリ内容は、参照されません。このことは、あるレジストリ設定を変更したくて OS イメージを作りなおした場合に、OS イメージを置き換えても、意図したレジストリ設定の変更が行われない、ということを意味します。作り直した OS イメージに収録したレジストリ内容を有効にするには、永続記憶域に保存されたレジストリを消去して、OS イメージに収録されたレジストリ内容が永続記憶域に保存されるようにしなければいけません。

このことは、OS のバージョンアップを行う際、つまり、WinCE 本体に対する QFE の適用や、あるいは、OS イメージに含める独自デバイスドライバの修正などを行う際に、レジストリ内容の更新が必要な場合に問題となります。そのような場合を考慮して、RAM-Basded レジストリを永続化する対応を行う時は、永続記憶域に保存されたレジストリを消去する機能も実装しておく必要があります。

たとえば、OS イメージの置き換えを行う際にレジストリを消去できるように、イメージ置き換え機能を実装する、という方策が考えられます。あるいは、ブートローダにレジストリ初期化機能を追加し、WinCE カーネルを起動する前にレジストリを消去できるようにする、という方策も考えられます。

Add comment 2011/08/13 koga

レジストリ変更内容の永続化(2/2)

今回は、前回提示したレジストリの設定内容を、レジストリが永続化される仕組みとともに説明します。弊社が提供している WinCE 6.0 の BSP(Lilas-am440-6; Armadillo-440 用の BSP)以外の BSP をお使いの方は、今回の説明を参考にして下さい。

・基本原理
レジストリを永続化する仕組みの基本原理は、次の通りです:

 ブートの初期フェーズにおいて、レジストリが初期化される前に、レジストリのファイル(hive)を配置する永続記憶域用のデバイスドライバとファイルシステムをロードして初期化する。その後、レジストリのファイル(hive)が永続記憶域からロードされて、レジストリの初期化が完了する。

これは、前回示したリファレンスのページにも書かれています:
 http://msdn.microsoft.com/en-us/library/ee490032.aspx (Implementing the Hive-Based Registry)

以下では、この仕組みを、詳しく見ていきます。

・三つのレジストリファイル
WinCE は、レジストリの配置形式として、RAM-Based と Hive-Based の二種類をサポートしています:
 http://msdn.microsoft.com/en-us/library/ee490801.aspx (Registry Types)

前回示した手順では、OS Design で Hive-Based のレジストリを指定しました(カタログ項目の「Hive ベースのレジストリ」を選択)。上のページに書かれているように、WinCE 6.0 では、Hive-Based のレジストリがデフォルトです。上のページの末尾の方にある表で示されていますが、Hive-Based のレジストリでは、レジストリは、次の三つのファイルで構成されます:

 - Boot.hv
  このファイルは、必ず OS イメージの中にのみ配置され、永続記憶域に配置することは、できません。

 - System.hv
 - User.hv
  これら二つのファイルは、Boot.hv 内での設定により、指定した永続記憶域、つまり OS イメージの外に配置できます。

Boot.hv の内容は、ブートの初期フェーズにのみ参照されます。bootable なファイルシステムがマウントされて、そのファイルシステム上に System.hv (および User.hv)が構築された時点で、破棄されます。その際、Boot.hv の内容に変更が加わっていれば、その変更は、System.hv に引き継がれます。Boot.hv は、ブートの初期フェーズにのみ参照されますので、初期フェーズで必要となる内容だけが収録されます。

Boot.hv の内容と、他の二つのファイルの初期状態の内容は、OS イメージのファイルを Platform Builder で開いてみれば分かります。Platform Builder の [ファイル] → [開く] → [ファイル...] メニューでオープンダイアログを開き、OS イメージのファイル(.bin ファイル)を指定してオープンしてみて下さい。OS イメージの内容を示すツリービューが表示されて、次の階層に、レジストリ内容が表示されます:

 <OS イメージのファイル名>(※デフォルトは NK.bin)
  NK
*  ブートレジストリ
*  レジストリ
   (すべてのファイル)

「ブートレジストリ」配下が Boot.hv の内容、そして、「レジストリ」配下が System.hv と User.hv の内容です。注意深くみると、「ブートレジストリ」配下の内容と同じ項目が、「レジストリ」配下にもあることに気づくでしょう。これは、OS イメージがビルドされる際に、Boot.hv の内容が System.hv にマージされることによるものです。つまり、Boot.hv の内容は、System.hv の内容のうち、ブートの初期フェーズにだけ必要なサブセットになっているのです。

前回示したレジストリの設定内容は、先頭行が ‘; HIVE BOOT SECTION’ で、末尾行が ‘; END HIVE BOOT SECTION’ でした。.reg ファイルに、この二つの行で囲った記述があると、囲われた部分の内容は、OS イメージがビルドされる際に、Boot.hv に収録されます(結果として、System.hv にも収録されます)。WinCE は、ブートの初期フェーズにおいて Boot.hv を参照し、bootable なファイルシステムをマウントした後、そのファイルシステム上に System.hv を構築します。この時、ファイルシステム上に System.hv が存在していない場合(または、存在していても、その内容が有効でないと判定された場合)は、OS イメージ中の System.hv を、ファイルシステム上の指定されたディレクトリへコピーします。

デフォルトの設定では、RAMFS (Object Store) が bootable なファイルシステムとしてマウントされます。初期状態の RAMFS には、System.hv と User.hv は存在しませんので、コールドブートのたびに、OS イメージ内のものが RAMFS に展開される、というわけです。これが、前回の冒頭で述べた動作です。

参考:Platform Builder の、OS イメージの内容を閲覧する機能は、リファレンスの次のページで説明されています:
 http://msdn.microsoft.com/en-us/library/ee482393.aspx (Run-Time Image Viewer)

注意:この節の冒頭で、WinCE は RAM-Based と Hive-Based の二種類のレジストリ配置形式をサポートしていると述べました。RAM-Based のレジストリに関するリファレンスを読むと分かりますが、実は、RAM-Based のレジストリも、永続化することが可能です。ただし、そのためには、BSP のカーネル移植レイヤ(OAL)での対応が必要となります:
 http://msdn.microsoft.com/en-us/library/ee489958.aspx (Persisting Data with the RAM-Based Registry)
 http://msdn.microsoft.com/en-us/library/ee490769.aspx (Data Persistence with the RAM-based Registry Using the OAL)
弊社が提供している WinCE 6.0 の BSP (Lilas-am440-6) の無償評価版では、現状、RAM-Based のレジストリの永続化には対応していません。

・Storage Manager と Device Manager の起動タイミング
これまでの説明で、「ブートの初期フェーズ」という表現を使いましたが、より正確には、「ブートの第1フェーズ」です。ブートのフェーズには、0, 1, 2 の三つがあります:
 http://msdn.microsoft.com/en-us/library/ee490010.aspx (Boot Phases)

フェーズ0は、Boot.hv の内容がロードされる前の状態、フェーズ1は、Boot.hv の内容がロードされて、System.hv と User.hv が構築される前の状態です。System.hv と User.hv が構築されて、レジストリの初期化が完了した後、ブートが完了するまでの間がフェーズ2となります。

さて、前回示したレジストリの設定内容では、先頭の ‘Boot Phases’ 行の直後に、以下の行がありました:

[HKEY_LOCAL_MACHINE\init\BootVars]
    "SystemHive"="REG\\system.hv"
    "ProfileDir"="REG"
    "Flags"=dword:3
    "RegistryFlags"=dword:1


この BootVars キーの Flags の値の意味は、前回示したリファレンスのページで説明されています:
 http://msdn.microsoft.com/en-us/library/ee490554.aspx (Hive-based Registry Setup)

Flags の値が3というのは、このページの後半にある表で、ビット 0×00000001 と 0×00000002 を共にセットした状態、つまり、Storage Manager と Device Manager を共に、ブートのフェーズ1で起動するという指定です。

BootVars キーの他のレジストリ値についても、上のページで説明されています。SystemHive は、System.hv のファイルパスであり(※この値で、System.hv 以外の名前に設定することが可能です)、ProfileDir は、User.hv を配置するディレクトリです。
(これを書いている時点では、上のページの ProfileDir の説明は、Systgem.hv の配置先と書かれており、正しくないように思われます。)

RegistryFlags については、上のページでは説明されていませんが、この値を1にすると、RegCloseKey() が呼ばれるたびに、レジストリの更新内容が保存されます:
 http://msdn.microsoft.com/en-us/library/ee489941.aspx (Flush-On-Close Registry Flushing)

もし、パフォーマンスが問題となる場合には、RegistryFlags を1に設定せず(※デフォルトでは0になります)、必要な時点で、アプリケーションから RegFlushKey() を呼ぶようにすればよいでしょう。

・ストレージ用のデバイスドライバ
冒頭の「基本原理」の節で、”ブートの初期フェーズにおいて、レジストリが初期化される前に、レジストリのファイル(hive)を配置する永続記憶域用のデバイスドライバとファイルシステムをロードして初期化する。”と述べました。前回提示したレジストリ設定内容で、これに対応するのが、’[HKEY_LOCAL_MACHINE\Drivers\BuiltIn\SDBusDriver]‘ 以降の部分です。そこでは、以下の設定を行っています:

 - レジストリのファイルを配置する永続記憶域用のデバイスドライバのレジストリ設定を Boot.hv に収録し、かつ、ブートの第1フェーズでロードするよう指定。
 - レジストリのファイルを配置するファイルシステムのレジストリ設定を Boot.hv に収録し、かつ、それを bootable に指定。

レジストリ設定を Boot.hv に収録する指定は、設定内容を ‘; HIVE BOOT SECTION’ と ‘; END HIVE BOOT SECTION’ で囲むことによって達成されます。通常、デバイスドライバのレジストリ設定は、System.hv のみに収録されますが、同じ設定内容を ‘; HIVE BOOT SECTION’ と ‘; END HIVE BOOT SECTION’ で囲って追記することにより、Boot.hv にも収録できます。

永続記憶域用のデバイスドライバを、ブートの第1フェーズでロードするのは、各デバイスドライバに対するレジストリの Flags の値で指定しています。具体的には、次の二つのキーの Flags の値に、dword:1000 を指定しています。

[HKEY_LOCAL_MACHINE\Drivers\BuiltIn\SDBusDriver]
    ...
    "Flags"=dword:1000
    ...
[HKEY_LOCAL_MACHINE\Drivers\BuiltIn\ESDHC1_MX25]
    ...
    "Flags"=dword:1000
    ...

これらは、それぞれ WinCE の SD バスドライバと、Armadillo-440 の SD ホストコントローラのドライバに対するキーです。Flags の値の意味は、リファレンスの次のページで説明されています:
 http://msdn.microsoft.com/en-us/library/ee484469.aspx (ActivateDeviceEx)

dword:1000 は、DEVFLAGS_BOOTPHASE_1 であり、そのドライバをブートの第1フェーズでロードするという指定です。デフォルト(DEVFLAGS_BOOTPHASE_1 が指定されていない場合)では、たとえそのレジストリキーが Boot.hv に収録されていても、デバイスドライバは、ブートの第2フェーズ、つまり System.hv の構築が完了した後でロードされます。その逆に、Flags に DEVFLAGS_BOOTPHASE_1 が指定されていても、その設定内容が Boot.hv に収録されていなければ、ブートの第2フェーズでロードされます。

さて、前回示したレジストリ設定内容は、Armadillo-440 に装着した microSD カードを永続記憶域として使う設定でしたが、microSD のメモリカード用のドライバは、SD バスドライバと SD ホストコントローラのドライバに加え、SD メモリカードのドライバがあります。このドライバのキーは、
[HKEY_LOCAL_MACHINE\Drivers\SDCARD\ClientDrivers\Class\SDMemory_Class]
ですが、このキーには、Flags が指定されていません。指定してはいけないのです。SD メモリカードのドライバに対して、”Flags”=dword:1000 を指定すると、指定どおり、ブートの第1フェーズで順にロードされますが、それだとエラーが発生し、microSD のメモリカードを認識できなくなります。SD メモリカードのドライバは、SD カードのクライアントドライバ(クラスドライバ)であり、SD ホストコントローラを通じて microSD のメモリカードが認識された後、SD バスドライバによってロードされます。その際、SD メモリカードのドライバに対して “Flags”=dword:1000 が指定されていると、SD バスドライバがロードする時点で、既に Device Manager によって、SD メモリカードのドライバがロードされてしまっているため、ロードに失敗してしまうのです。バスドライバとクライアントドライバの関係は、他のバス、たとえば USB メモリの場合も、同様です。

Device Manager による、ブートの第1フェーズと第2フェーズでのドライバのロードの詳細に興味のある方は、WinCE のカーネルのソースツリーにある、Device Manager のソースをご覧になってみて下さい。以下が、関連するソースファイルです。

 - <WINCE600_Root>/PRIVATE/WINCEOS/COREOS/DEVICE/DEVCORE/devcore.c
 - <WINCE600_Root>/PRIVATE/WINCEOS/COREOS/DEVICE/DEVCORE/devload.c

devcore.c にある StartDeviceManager() で、ブートの第1フェーズと第2フェーズの各々での、ドライバのロード処理を行っています。devload.c には、第1フェーズでのロードを行う DevloadInit() と、第2フェーズでのロードに使われる InitDevices() の実装があります。また、ActivateDeviceEx() の実体である I_ActivateDeviceEx() も、devload.c で実装されています。

永続記憶域用のデバイスドライバを、ブートの第1フェーズでロードするよう指定する設定についての説明は、以上です。次に、ファイルシステムに対する設定です。ファイルシステムに対する設定は、Storage Manager に対する設定であり、前回示したレジストリ設定内容では、次の二つのキーです。

[HKEY_LOCAL_MACHINE\SYSTEM\StorageManager\Profiles\SDMemory]
    "Folder"="SDMemory"
    "Name"="SD Memory Card"
    "AutoMount"=dword:1
    "DefaultFileSystem"="FATFS"
    "PartitionDriver"="mspart.dll"
[HKEY_LOCAL_MACHINE\System\StorageManager\Profiles\SDMemory\FATFS]
    "MountPermanent"=dword:1
    "MountAsBootable"=dword:1

このうち、一番目の SDMemory キーの内容は、もともと System.hv だけに収録される設定になっている内容を、コピーしたものです。Platform Builder で OS イメージのファイルを開き、「レジストリ」の配下からコピーしたいキーのパスを辿り、そのキーを右クリックして、ポップアップメニューの [テキストで表示] を選択すると、テキストエディタ(メモ帳)が開いて、キーの内容が表示されます。その機能を使ってコピーするのが便利でしょう。

二番目の SDMemory\FATFS キーの内容は、ここでは、必要となる追加の設定のみ行っています。これらのキーに対する説明は、リファレンスの次のページをご覧ください:
 http://msdn.microsoft.com/en-us/library/ee490773.aspx (Mount Settings)

MountPermanent は、指定しなくても動作します。これは、マウントする際に、リムーバブルかどうかを指定するフラグ値ですが、レジストリファイルを配置しますので、リムーバブルではないことを示す1を指定しています。MountAsBootable は、bootable であることを示す1を指定しています。MountAsBootable については、次の節で、もう少し詳しく述べます。

・MountAsBootable
このフラグ値は、当該ファイルシステムが bootable かどうかを示す値ですが、レジストリファイルを配置するファイルシステム(今回の設定例では、FATFS)に対して、このフラグ値に1を指定するだけでは、期待通りに動作しません。その理由は、デフォルトでは、bootable と指定されたファイルシステムが他にあり、そちらが先にマウントされるためです。レジストリファイルが配置されるファイルシステムは、bootable と指定されたもののうち、最初にマウントされたものになります。

さて、注意深い方は、ここまでの説明の中で、前回示したレジストリ設定のうち、まだ説明されていない行があることにお気づきだと思います。設定内容のうち、二番目に書かれている次のキーです。

[HKEY_LOCAL_MACHINE\System\StorageManager\AutoLoad\ObjectStore]
    "MountAsBootable"=dword:0


このキーが、レジストリファイルを配置するファイルシステムに対するキーに加えて、追加で必要な設定です。「三つのレジストリファイル」の節の末尾の方で、”デフォルトの設定では、RAMFS (Object Store) が bootable なファイルシステムとしてマウントされます。”と述べました。その設定を無効にするのが、上の ObjectStore キーに対する設定なのです。この設定により、RAMFS (Object Store) が bootable でないファイルシステムとしてマウントされますので、レジストリファイルを配置したいファイルシステム(今回の設定例では、FATFS)が、bootable なファイルシステムとしてマウントされる最初のものとなり、無事に、レジストリファイルの配置先として選択される、というわけです。

前節で述べた、Device Manager によるブートの第1フェーズでのドライバのロード処理の後、第2フェーズへ遷移する前に、bootable なファイルシステムがマウントされて、レジストリの初期化が完了するのを、Event オブジェクトを用いて待ち合わせる仕組みになっています。このため、第1フェーズから第2フェーズへの遷移は、次のようになります:

 0.) Boot.hv での設定により(BootVars キー)、Device Manager と Storage Manager がブートの第1フェーズで(つまり、Boot.hv の内容のロード後に)起動される。

 1.) Device Manager が、ブートの第2フェーズへ遷移する前に、Boot.hv で Flags の値の DEVFLAGS_BOOTPHASE_1 ビットが設定されているドライバをロードする。
  →今回の設定例では、SD バスドライバと SD ホストコントローラが、ロードされる。

 2.) Device Manager は、ブートの第1フェースでのロードを指定されたドライバ群をロードし終えた後、レジストリの初期化が完了するのを待つ。

 3.) SD ホストコントローラが microSD カードを検出し、SD バスドライバへ通知することにより、SD バスドライバが(Boot.hv の設定内容により)SD メモリカードのドライバをロードする。
  (※もし、デフォルト設定のまま、ObjectStore が bootable に指定されていると、microSD カードが検出される前に、RAMFS (Object Store) がマウントされるので、RAMFS 上に System.hv と User.hv が構築されて、以降の動作を待たずに、Device Manager が第2フェーズへ遷移してしまう。)

 4.) SD メモリカードのドライバが、microSD メモリカードのパーティションを検出して、Storage Manager へ通知することにより、microSD メモリカードのファイルシステムがマウントされる。

 5.) Storage Manager が(Boot.hv の設定内容により)microSD カードのファイルシステムを bootable としてマウントし、それがレジストリ機能に通知される結果、microSD カード上に System.hv と Use.hv が構築されて、レジストリの初期化が完了する。

 6.) レジストリの初期化完了が、Event オブジェクトを通じて、(2) で待ち動作を行っている DeviceManager に通知されて、待ち動作が解除される。

 7.) Device Manager は、ブートの第2フェーズへ遷移して、System.hv にレジストリ設定内容が収録されている、残りのデバイスドライバ(※起動時の自動ロードが指定されているもの)をロードする。

bootable なファイルシステムのうち、最初にマウントされたものだけが bootable として記録される様子は、WinCE のカーネルのソースツリーにある、File System Disk Manager (FSDMGR) のソースを見ると分かります。興味のある方は、次のソースをご覧になってみて下さい。
 <WINCE600_Root>/PRIVATE/WINCEOS/COREOS/STORAGE/FSDMGR/mounttable.cpp

mounttable.cpp にある MountTable_t::RegisterVolume() で、MountAsBootable の指定により bootable と設定されたファイルシステムのうち、最初にマウントされたものだけを bootable なボリュームとして記録するようになっています。

・まとめ
以上で、Hive ベースのレジストリを永続化する手順の説明は終わりです。まとめとして、以下に、手順をおさらいしてみます。

 1.) OS Design に、「Hive ベースのレジストリ」を追加する。
   デフォルトでは「Hive ベースのレジストリ」が選択されますが、念のために指定しておくのがよいでしょう。

 2.) <WINCE600_Root>/PLATFORM/AM440/FILES/platform.reg の末尾に、’; HIVE BOOT SECTION’ と ‘; END HIVE BOOT SECTION’ の2行を追加して、追加した2行の間に、以下で述べる内容を挿入する。

 3.) [HKEY_LOCAL_MACHINE\init\BootVars] キーの設定。
   レジストリファイルのパスと、Flags、および、RegistryFlags の設定を追加する。

 4.) [HKEY_LOCAL_MACHINE\System\StorageManager\AutoLoad\ObjectStore] キーの設定。
   RAMFS (Object Store) が bootable としてマウントされないように、MountAsBootable に0を指定する。

 5.) レジストリを配置する永続記憶域のデバイスドライバ(バスドライバとホストコントローラのドライバ、およびクラスドライバ)の設定。
   各々、System.hv に収録されている内容をコピーする。バスドライバとホストコントローラのドライバに対しては、ブートの第1フェーズでロードされるように、Flags の値に 0×1000 (DEVFLAGS_BOOTPHASE_1) を指定する。

 6.) レジストリを配置する永続記憶域のデバイスに対する StorageManager の Profiles 配下のキーを設定する。
   <プロファイル名> キーの内容は、System.hv に収録されている内容をコピーする。<プロファイル名>\<ファイルシステム名> キー(または、<プロファイル名>\<ファイルシステム名>\<パーティション名> キー)に対しては、MountAsBootable を1に設定する(他は省略可能)。

前回の設定例で対象にした、弊社提供の WinCE 6.0 BSP 以外の BSP をお使いの場合や、また、弊社提供の WinCE 6.0 BSP で、microSD カード以外の永続記憶域(USB メモリ)をお使いの場合には、上のまとめを参考にして下さい。永続記憶域のデバイスドライバに対するレジストリキーが分からない場合は、BSP の提供元に問い合わせしてみるのがよいでしょう。

・おまけ
今回の説明で、「ブートの初期フェーズ」という表現を使いましたが、それは主に、ブートのフェーズ1でした。フェーズ0、つまり、Boot.hv がロードされる前の処理、特にカーネル本体の初期化が完了するまでのブートシーケンスについては、全く触れていません。カーネル本体の初期化が完了するまでのシーケンスに興味のある方は、リファレンスの次のページが、取っ掛かりとして参考になるでしょう:
 http://msdn.microsoft.com/en-us/library/ee482950.aspx (Nk.exe Boot Process)

ところで、Windows CE 5.0 までは、WinCE はマイクロカーネル構造を採用しており、マイクロカーネル本体と、Device Manager や Storage Manager は、それぞれ異なるプロセスとして動作する仕組みでした(Device Manager は Device.exe のプロセスに所属、Storage Manager は Filesys.exe のプロセスに所属)。Windows Embedded CE 6.0 において、それらは、カーネルにロードされる DLL となりました(Device Manager は Device.dll に所属、Storage Manager は Filesys.dll に所属)。つまり、純粋なマイクロカーネル構造から、モノリシックカーネルに近い構造に変わったのですが、モジュール分割の構造は、大きくは変わっていません。上のページで説明されているのは、Windows Embedded CE 6.0 のカーネル本体の初期化シーケンスですが、これは、Windows CE 5.0 までのマイクロカーネル本体のブートシーケンスに該当するものとなります。

Windows CE 5.0 と Windows Embedded CE 6.0 の違いは、@IT MONOist の次の記事が参考になるでしょう:
 http://monoist.atmarkit.co.jp/fembedded/winembedded/ce6_01/ce02.html (「Windows CE 6で刷新されたアーキテクチャ」)

1 comment 2011/02/21 koga

レジストリ変更内容の永続化(1/2)

・はじめに
IP アドレスの設定など、各種設定項目は、WinCE の OS イメージを生成する際に、レジストリに格納されます。デフォルトでは、レジストリは OS イメージ内に配置され、起動時に RAMFS (ObjectStore) に展開されます。このため、コントロールパネルで IP アドレスなどの設定を変更しても、その変更内容は、電源を切った時点で失われてしまいます。つまり、コールドブートに対して、レジストリの変更内容は永続的(persistent)ではありません。なお、WinCE の RAMFS (ObjectStore) について興味のある方は、リファレンスページをご覧ください:
 http://msdn.microsoft.com/en-us/library/ee490183.aspx (RAM (Object Store) File System)

さて、レジストリの変更内容を永続的にすることは可能です。レジストリを永続化するには、ハードディスクなどの永続記憶域にファイルとして配置すればよいのです。この手順については、以下のリファレンスページが参考になるでしょう。ただし、以下のページに書かれていることだけでは詳細な手順が分からないため、この後で、具体的な手順を述べます。
 http://msdn.microsoft.com/en-us/library/ee490554.aspx (Hive-based Registry Setup)
 http://msdn.microsoft.com/en-us/library/ee490032.aspx (Implementing the Hive-Based Registry)

・具体的な手順
レジストリを永続化するための手順の例として、弊社が提供している WinCE 6.0 の BSP(Lilas-am440-6; Armadillo-440 用の BSP)の場合の手順を述べます。BSP に固有な点は明記しますので、他の BSP をお使いの場合でも、参考にして頂けるのではないかと思います。以下が、その手順です。ここでは、永続記憶域として、Armadillo-440 に microSD カードを装着して使います。

1.) OS Design に、「Hive ベースのレジストリ」を追加する。
  PlatformBuilder のカタログ項目ビューで、次のカタログ項目を選択して下さい
  
   コア OS
    CEBASE
     ファイルシステムおよびデータストア
      レジストリ記憶域(1つ選択)
*       Hive ベースのレジストリ

2.) <WINCE600_Root>/PLATFORM/AM440/FILES/platform.reg の末尾に、次の行を追加する。
  以下の内容は、最初の二つのレジストリキーが共通な設定で、残りは、BSP に固有な設定です。BSP に固有な設定は、レジストリのファイル(hive)を配置する永続記憶域として何を使うか、および、使用する永続記憶域のデバイスドライバのレジストリ設定によって変わります。

; HIVE BOOT SECTION
[HKEY_LOCAL_MACHINE\init\BootVars]
    "SystemHive"="REG\\system.hv"
    "ProfileDir"="REG"
    "Flags"=dword:3
    "RegistryFlags"=dword:1

[HKEY_LOCAL_MACHINE\System\StorageManager\AutoLoad\ObjectStore]
    "MountAsBootable"=dword:0

[HKEY_LOCAL_MACHINE\Drivers\BuiltIn\SDBusDriver]
    "Dll"="SDBus.dll"
    "Flags"=dword:1000
    "IClass"=multi_sz:"{20FA98A8-B298-4b32-8D72-C716AEE2FA84}=%b","{6F40791D-300E-44E4-BC38-E0E63CA8375C}=%b"
    "Order"=dword:15
    "Prefix"="SDC"
    "RequestListDepth"=dword:30
    "ThreadPriority"=dword:64
[HKEY_LOCAL_MACHINE\Drivers\BuiltIn\ESDHC1_MX25]
    "DisableDMA"=dword:0
    "Dll"="esdhc.dll"
    "Flags"=dword:1000
    "Index"=dword:1
    "MaximumClockFrequency"=dword:3197500
    "Order"=dword:21
    "Prefix"="SHC"
[HKEY_LOCAL_MACHINE\Drivers\SDCARD\ClientDrivers\Class\SDMemory_Class]
    "BlockTransferSize"=dword:40
    "Dll"="SDMemory.dll"
    "IClass"=multi_sz:"{A4E7EDDA-E575-4252-9D6B-4195D48BB865}","{8DD679CE-8AB4-43c8-A14A-EA4963FAA715}"
    "Prefix"="DSK"
    "Profile"="SDMemory"

[HKEY_LOCAL_MACHINE\SYSTEM\StorageManager\Profiles\SDMemory]
    "Folder"="SDMemory"
    "Name"="SD Memory Card"
    "AutoMount"=dword:1
    "DefaultFileSystem"="FATFS"
    "PartitionDriver"="mspart.dll"
[HKEY_LOCAL_MACHINE\System\StorageManager\Profiles\SDMemory\FATFS]
    "MountPermanent"=dword:1
    "MountAsBootable"=dword:1
; END HIVE BOOT SECTION

3.) OS イメージを作りなおす。
  OS Design のソリューションを、再ビルドして下さい。

以上で、レジストリを永続化した OS イメージが出来上がります。実際に動かしてみて、コントロールパネルで IP アドレスの設定を変更してみて下さい。変更した後、電源を入れ直してコールドブートしても、設定内容が失われず、永続化されているはずです。

上で示した、platform.reg で設定する内容については、次回、レジストリが永続化される仕組みとともに説明します。

Add comment 2011/02/15 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

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

「WinCE では、Linux のように、リブートやシャットダウンを行うコマンドは無いのですか?」
先日、こんな質問を某所でもらいました。「シェルのコマンドとしては、存在していないはずですが、リブートしたりホールト(halt)するコマンドプログラムは、作れると思います。必要な API などを調べてみますので、少し時間を下さい。」そう回答して帰ってきたので、調べてみました。

なお、回答した際には、「ただし、PC Linux のように自動で電源 OFF する機能は、ハードウェアの支援が必要なので、デバイスによっては、シャットダウンしても電源は切れず、電源を切れる状態になるだけです。」と言い添えてあります。

調べた結果、OS デザインに Power Management が組み込まれている場合、リブートするには、SetSystemPowerState() を使うのが最も簡単だと分かりました。SetSystemPowerState() については、WinCE のリファレンスをご覧ください:
 http://msdn.microsoft.com/en-us/library/aa929708.aspx
SetSystemPowerState() の第二引数に POWER_STATE_RESET を渡せば、WinCE がリブートします。また、POWER_STATE_RESET ではなく、POWER_STATE_SUSPEND を渡すと、サスペンド状態となりますので、安全に電源を切ることができます。

では、OS デザインに Power Management を組み込んでいない場合には、どうすればよいのでしょうか?以下に、リブート動作とサスペンド動作について、もう少し詳しく書きます。ただし、以下で述べるのは、Windows Embedded CE 6.0(WinCE 6.0)に対するものです。WinCE 5.0 以前のバージョンについては、確認していませんが、以下で述べることがそのまま当てはまるわけではないと考えて下さい。
(※サスペンド動作については、次回に書くことにします。今回は、少々長くなるので、二回に分けて書くことにしました。)

■リブート動作
リブート動作の詳細は、ボードや CPU ごとに異なります。従って、その部分は、OAL で実装されます。具体的には、OALIoCtlHalReboot() が、リブート動作の実装です:
 http://msdn.microsoft.com/en-us/library/aa910452.aspx
デバイスエミュレータの場合、OALIoCtlHalReboot() のソースは、
 <WINCE600>/PLATFORM/DEVICEEMULATOR/SRC/OAL/OALLIB/reboot.c
です。この reboot.c を見ると、デバイスエミュレータの CPU(S3C2410) のウォッチドッグ・タイマの発火時間を短い値にセットして、CPU にリセットがかかるようにしているのが分かります。

さて、単純に CPU にリセットをかけ、OS をリブートした場合、レジストリやファイルシステムに不整合が生じる可能性があります。レジストリやファイルシステムに対する更新が、メモリ上のキャッシュに対してのみ行われ、永続記憶域に反映されていない状態でリセットがかかれば、更新内容が失われてしまうからです。従って、OALIoCtlHalReboot() の呼び出しを行う前に、レジストリやファイルシステムのフラッシュ動作を行わなければいけません。

また、OALIoCtlHalReboot() を呼び出せるのは、カーネルだけです。つまり、アプリケーションから呼び出すことは、できません。アプリケーションからデバイスをリブートする場合は、カーネルに対して OALIoCtlHalReboot() の呼び出しを要求し、さらに、OALIoCtlHalReboot() が呼び出される前に、レジストリやファイルシステムのフラッシュ動作を発火させなければいけません。これらの処理は、SetSystemPowerState() を使えるのであれば、意識する必要は、ありません。Power Management 機能が、必要な処理を行ってくれるからです。

では、Power Management を OS デザインに組み込んでおらず、使えない場合は、どうすればよいのでしょうか。その場合は、カーネルに対して、リブート動作を要求します。制御コードに IOCTL_HAL_REBOOT を指定して KernelIoControl() を呼び出すことにより、カーネルにリブート動作を行わせることが出来ます。

ただし、KernelIoControl() で IOCTL_HAL_REBOOT を指定することは、アプリケーションからは出来ません。OAL 側でサポートを追加しない限り、アプリケーションによる KernelIoControl() の呼び出しでは、IOCTL_HAL_REBOOT は未サポートの制御コードとして扱われます。

アプリケーションから 、つまり、ユーザモードから KernelIoControl() を呼び出した場合、その応答処理は、
 <WINCE600>/PUBLIC/COMMON/OAK/OALIOCTL/oalioctl.cpp
で実装されている IOControl() で処理されます。ソース(oalioctl.cpp)を見れば分かるように、IOControl() では、以下の制御コードのみに対して応答動作を行い、それ以外の制御コードに対しては、ERROR_NOT_SUPPORTED エラーとして処理するのです。

IOCTL_HAL_GET_CACHE_INFO
IOCTL_HAL_GET_DEVICE_INFO
IOCTL_HAL_GET_DEVICEID
IOCTL_HAL_GET_UUID
IOCTL_PROCESSOR_INFORMATION

なお、oalioctl.cpp のコメントに書かれていますが、OEM、つまり WinCE デバイスを実装する側では、oalioctl.cpp を編集して、IOControl() が応答する制御コードを追加/削除することができます。上で、「OAL 側でサポートを追加しない限り」と書いたのは、IOControl() が応答する制御コードとして IOCTL_HAL_REBOOT が追加されていれば、アプリケーションから KernelIoControl() を呼び出してリブートできる、という意味なのです。

OAL 側でサポートを追加していない場合は、アプリケーションから KernelIoControl() を呼び出して IOCTL_HAL_REBOOT を指定しても、エラーになってしまうので、リブートできません。その場合は、専用のデバイスドライバを作り、デバイスドライバから KernelIoControl() を呼び出せば、IOCTL_HAL_REBOOT を指定できます。上記の oalioctl.cpp が関与するのは、ユーザモードから KernelIoControl() が呼び出された場合であり、カーネルモードからの呼び出しでは、全ての制御コードが処理されます。従って、カーネルモードのデバイスドライバから KernelIoControl() を呼び出せばよい、というわけです。

ところで、リブートする際に、KernelIoControl() を呼び出すだけでよいのでしょうか?IOCTL_HAL_REBOOT を渡して KernelIoControl() を呼び出せば、カーネルによって OALIoCtlHalReboot() が呼び出されます。しかし、リブートの際には、OALIoCtlHalReboot() の呼び出しを行う前に、レジストリやファイルシステムのフラッシュ動作を行わなければいけないと書きました。

実は、デバイスドライバから呼び出す場合は、KernelIoControl() を呼び出すだけで、必要な処理が行われます。ただし、ここで述べるのはドキュメント化された情報ではなく、WinCE 6.0 のカーネルのソースを追跡して判断したことですので、ご注意下さい。念のため、アプリケーションから呼び出す場合のように(※これについては、後で述べます)、KernelIoControl() の前に FileSystemPowerFunction() を呼び出す方が良いでしょう。

カーネルモードから KernelIoControl() を呼び出した場合ですが、k.coredll.dll を経由して、カーネル内部の同じ名前の関数が呼び出されます。カーネル内部の KernelIoControl() は、
 <WINCE600>/PRIVATE/WINCEOS/COREOS/NK/KERNEL/kwin32.c
で実装されています。このソースを見れば、IOCTL_HAL_REBOOT が渡された場合、KernelIoControl() から CallOEMIoControl() を経由して、NKReboot() が呼び出されることが分かります。NKReboot() は、IOCTL_HAL_REBOOT を渡して OEMIoControl() を呼び出すことにより、OALIoCtlHalReboot() の呼び出しを行うのですが、OEMIoControl() の前に、FSNotifyMountedFS() というカーネル内部関数を呼び出します。この FSNotifyMountedFS() によって、レジストリやファイルシステムのフラッシュ動作が実行されるのです。

一方、OAL 側でサポートが追加されていて、アプリケーションから KernelIoControl() に IOCTL_HAL_REBOOT を渡した場合、上述した oalioctl.cpp の IOControl() では、そのまま OEMIoControl() を呼び出します。そのため、レジストリやファイルシステムのキャッシュ動作は、行われません。従って、アプリケーション自身が、レジストリやファイルシステムのキャッシュ動作を発火させる必要があります。具体的には、FileSystemPowerFunction() の引数に FSNOTIFY_POWER_OFF を渡して呼び出します。FileSystemPowerFunction() のリファレンスは、
 http://msdn.microsoft.com/en-us/library/aa914779.aspx
です。

次は、「サスペンド動作」と、「電源 OFF について」という項を書くつもりでしたが、少々長くなってしまいました。この二つについては、次回に書くことにします。

1 comment 2009/07/20 koga

Next Posts Previous Posts


Categories

Links

Posts by Authors

Recent Posts

Calendar

2018年10月
« 9月    
 123456
78910111213
14151617181920
21222324252627
28293031  

Posts by Month

Posts by Category

Meta