Archive for 2012/01

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

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

最初に断っておきますが、この方法は、WinCE/WEC の OS Design のコンフィグレーションでは対応できません。カーネル移植レイヤ(OAL)とブートローダのカスタマイズが必要です。

■複数の ROM イメージ(Multiple Region)
ROM イメージを複数に分割する仕組みは、もともと、WinCE/WEC のカーネルと Platform Builder 内蔵の ROM イメージ生成コマンド(makeimg.exe, romimage.exe)に組み込まれています。ただし、それを有効にするためには、OAL とブートローダが、複数に分割された ROM イメージに対応する必要があるのです。この対応は、必須ではないため、全ての BSP が対応しているわけではありません。対応していない BSP の場合は、OAL とブートローダの実装にカスタマイズを加えなければいけない、というわけです。

複数に分割された ROM イメージに対応した OAL とブートローダは、それぞれ、次の機能を持ちます。

・OAL
 ブートローダが Flash メモリなどから RAM にロードした(XIP の場合は、NOR Flash 上の)、複数に分割された ROM イメージのリストを、カーネルに伝える。この通知は、OAL の初期化時に(つまり、OEMInit() の中で)OEMRomChain を構築することで行う。

・ブートローダ
 Flash メモリなどから、複数に分割された ROM イメージを RAM へロードする(XIP の場合は、ロードしない)。また、ROM イメージをホスト(開発用 PC)から転送された時は、分割された ROM イメージを各々認識し、それらを、Flash メモリなどに書き込む。

ここで、「分割された ROM イメージ」というのは、ROM イメージファイルを単純に分割したものでは、ありません。「分割された ROM イメージ」の一つ一つは、それぞれが ROM イメージのヘッダを持っていて、個別の ROM イメージとなっています。これらを、region と呼びます。どのように region を構成するのかは、config.bib で設定します。makeimg.exe は、config.bib で複数の region が設定されていれば、それに従って、region ごとに ROM イメージファイル(.bin)を生成するのです。

複数の region を WinCE/WEC カーネルに認識させるために、OAL は、初期化時に region のリストを構築します。このリストが、上で述べた OEMRomChain です。OEMRomChain は、ROMChain_t 構造体のリストで、カーネルのスタートアップルーチンによって、スタートアップルーチンが所属する ROM イメージを指すように初期化されます。つまり、デフォルトでは、region が一つだけとなります。その後、スタートアップルーチンが OAL の OEMInit() を呼び出し、OEMInit() が、OEMRomChain に複数の region を登録する(OEMRomChain を初期化し直す)というわけです。

より正確には、OEMInit() が OEMRomChain をどう構築するかは、OAL における OEMInit() の実装次第です。カーネルのスタートアップルーチンは、OEMInit() を呼び出した後、OEMRomChain の内容をチェックして、自身が所属する region が登録されていない場合には、自身が所属する region を OEMRomChain の末尾に連結します。

ROMChain_t 構造体の説明は、WinCE 6.0/WEC 7 のリファレンスの、次のページをご覧ください:

 OEMRomChain
 http://msdn.microsoft.com/en-US/library/ee479246(v=WinEmbedded.60).aspx
 http://msdn.microsoft.com/en-US/library/ee479246.aspx

■Region ごとの部分アップデート
複数の ROM イメージ(Multiple Region)を使うと、部分アップデートする対象(アプリケーションなど)を収録した ROM イメージと、OS 本体を収録した ROM イメージを分けることができます。そして、ブートローダが対応していれば、アップデートしたい ROM イメージ(region)だけをブートローダへ転送し、書き換えることができます。これが、ROM イメージファイルを複数に分割する方式の部分アップデートです。

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

・永続記憶域上にルートファイルシステムを構築する必要がないため、より耐障害性が高い。
・OS イメージ中の全てのファイルを、アップデートすることが可能。

ここで、「OS イメージ中の全てのファイルをアップデート可能」というのは、ROM イメージの Region 間で shadowing が作用することによるものです。つまり、二つの region に同じファイルが収録されている場合、上述した OEMRomChain のリストにおいて、より先頭に近い方の region 内のファイルが優先され、その後ろにある region 内のファイルが隠されるのです。

OEMRomChain の region 間での shadowin は、前回紹介した、ルートファイルシステム上のファイルによる ROM イメージ内のファイルの shadowing の仕組みと、本質的には同じものです。どちらも、FSD Manager と、カーネルのローダーによって実現されています。前回、カーネルのローダーが、OpenExecutable() から OpenFileFromFilesys() と OpenFileFromROM() を呼び出し、先に呼び出す OpenFileFromFilesys() で executable ファイル(.exe または .dll)が見つかれば、そちらを優先する、と説明しました。実は、OEMRomChain に複数の region が登録されている場合、OpenFileFromROM() は、OEMRomChain のリスト(つまり、region のリスト)を先頭から順に辿り、最初に見つかったファイルをオープンします。従って、よりリストの先頭に近い region 内のファイルが優先される、というわけです。

たとえば、OS 本体を収録した region(※この region に、カーネルのスタートアップルーチンが含まれます)と部分アップデートする対象を収録した region の二つがある場合について考えましょう。OEMRomChain のリストを構築する際、OS 本体を収録した region がリストの末尾要素となり、部分アップデートする対象を収録した region がリストの先頭要素となるようにすれば、両者に同じファイルが存在する場合、部分アップデート対象を収録した region 内のファイルが優先されます。このため、OS 本体を収録した region に収録したデバイスドライバなどをアップデートする際に、その region を書き換える代わりに、部分アップデート用の region の ROM イメージにアップデートしたいファイルを追加して、部分アップデート用の region だけを書き換える、という方策が可能です。

OS 本体を収録した region のサイズが、部分アップデートする対象を収録した region のサイズよりも、ずっと大きければ、小さい方の region(つまり、部分アップデートする対象を収録した region)の方を書き換える方が、より小さなコストで済みます。

部分アップデート用に、複数の ROM イメージ、つまり region の分割を行う方策としては、region を二つに分ける以外に、次のように、四つの region を設定する方策も考えられます(どちらかといえば、極端な例ですが):

 - NK: OS 本体を収録
 - EXT: デバイス固有(BSP 固有)のデバイスドライバやアプリケーションなどを収録
 - UNK: NK region 内のファイルの更新版を収録する(※出荷時は空)
 - UEXT: EXT region 内のファイルの更新版を収録する(※出荷時は空)

このように region を設定すれば、OS 本体(WinCE/WEC のカーネル及び、標準のデバイスドライバやアプリケーション、API の DLL など)と、デバイス(あなたが開発する製品)固有のドライバやアプリケーションを、それぞれ独立してアップデートできます。OS 本体用の ROM イメージと、デバイス固有の ROM イメージは、全体的なアップデートが必要となれば、各々の region(NK, EXT)を書き換えればよいですし、特定のファイルだけをアップデートしたい場合は、各々の部分アップデート用の region(UNK, UEXT)を書き換えればよい、というわけです。

WinCE/WEC は、月例アップデートが公開されます。もし、出荷後に重要な修正がリリースされた場合は、その修正を、出荷済みの製品の OS に反映しなければならないこともあるでしょう。そのような場合に、上の例のような region 分割を設定しておくと、OS 本体の ROM イメージを全て書き換えなくても、修正が加わった .dll や .exe を収録した、部分アップデートの ROM イメージを作り(そのイメージサイズは、OS 本体の ROM イメージに比べて、非常に小さいでしょう)、対応する region を書き換えることでアップデートできます。なお、部分アップデートを繰り返す場合、部分アップデートの ROM イメージには、最新のアップデート対象の .exe や .dll に加えて、以前の部分アップデートの際に収録した .exe や .dll も収録しなければならないことに注意して下さい。そうしなければ、部分アップデートした際に、以前の部分アップデートの内容が消えてしまうからです。

ところで、もし、部分アップデートを繰り返した結果、アップデート対象のファイルが累積して、部分アップデートの ROM イメージが大きくなった場合は、古い部分アップデート内容を破棄して、全体をアップデートする必要が生じる場合も、あると思います。つまり、上の例で言うと、UNK または UEXT region に収録するファイルが増えた場合は、それらの region を空にして、代わりに、NK または EXT region を作り直し、そちらを書き換える必要が生じるでしょう。もちろん、どのように region を分割し、部分アップデートを行うかということは、ケースバイケースですよね。

■Region 分割の手順
ここまでで、ROM イメージを複数に分割する方式の部分アップデートについて、その仕組みと、部分アップデートの方策について説明しました。残りは、実際の手順です。

複数の region に分割した ROM イメージを作成する手順の例は、WinCE 6.0 のリファレンスに載っています。現時点では、WEC 7 のリファレンスには対応するページが見当たりません。

 How to Create a Run-Time Image with Multiple XIP Regions
 http://msdn.microsoft.com/en-US/library/ee482739.aspx

このページでは、CEPC の場合の手順が載っていますが、他のボード(デバイス)の場合も、基本的には同じです。必要な作業を簡単にまとめると、次の通りです:

・config.bib ファイルの MEMORY セクションの記述を書き換える。その際、環境変数 IMGMULTIBIN に対する条件分岐を書き加えて、複数 region 対応するかどうかを、環境変数(IMGMULTIBIN)の設定有無で切り換えできるようにすると便利。
 複数 region 対応用の MEMORY セクションには、NK の他に定義する region を、RAMIMAGE 型の memory region として設定する。また、それらの region の連結内容を記録するための CHAIN region を、RESERVED の memory region として設定する。
 http://msdn.microsoft.com/en-US/library/ee482800.aspx (Modifying the Binary Image Builder Files)

・platform.bib ファイルの MODULES または FILES セクションに、NK 以外の region に収録するファイルを記述する。
 BSP 固有のデバイスドライバやアプリケーションなどを、NK 以外の region に収録する場合は、環境変数 IMGMULTIBIN に対する条件分岐を書き加えて、複数 region 対応の場合のみ、当該 region(「Region ごとの部分アップデート」で示した例で言えば、EXT region)を指定する。
 http://msdn.microsoft.com/en-US/library/ee482800.aspx (Modifying the Binary Image Builder Files)

・OS イメージをビルドする。

・ビルドしてできた .bin ファイルのうち、chain.bib を、Platform Builder から WinCE/WEC デバイスへ転送するファイルとして設定する。
 デバイスへ転送するファイルは、Platform Builder のプロパティダイアログで設定できる。Platform Builder の「プロジェクト」 > 「プロパティ」メニューで、プロジェクトのプロパティダイアログを開き、左側にあるツリービューから、「構成プロパティ」 > 「全般」を選択して表示される画面の、「デバッガ用のターゲットファイル名」というラベルのドロップダウンリストで、転送したい .bin ファイルを選択する。
 http://msdn.microsoft.com/en-US/library/ee482967.aspx (Building the Run-Time Image and Opening a Workspace with Multiple XIP Regions)

以上が、複数の region に分割した ROM イメージを作成して、WinCE/WEC デバイスのブートローダへ転送するために、Platform Builder で行う手順です。なお、上の最後のページ(“Building the Run-Time Image and Opening a Workspace with Multiple XIP Regions”)の説明では、複数に分割した ROM イメージを全て転送する場合、xip.bin を指定すると書かれていますが、これは、うまくいきません。xip.bin ではなく、CAHIN region の内容を収録した chain.bin を指定して下さい。

xip.bin は、全ての region の内容を一つに連結したファイルなのですが、WinCE 6.0 付属のブートローダのライブラリ(blcommon)の実装は、この構造に対応していないように思われます。xip.bin が、残りの .bin ファイルの内容を単純に連結した内容になっており、その結果、blcommon ライブラリでは、先頭の region しか認識されないのです。blcommon ライブラリのソースを読むと、ROM イメージのヘッダに記すマジックナンバーの値として、xip.bin 専用の値が定義されていたような形跡があるのですが、その値(X000FF)が設定されている場合、blcommon ライブラリは、エラーメッセージを出力して停止するように実装されています。

■OAL とブートローダのカスタマイズ
さて、今回の最初の方で、複数に分割した ROM イメージに対応するには、OAL とブートローダのカスタマイズが必要だと述べました。複数に分割した ROM イメージに対応している OAL とブートローダが、それぞれ、どのような働きをするのかについても、ごく簡単に説明しました。OAL とブートローダに対するカスタマイズの内容について、最後に、もう少しだけ補足します。

・OAL のカスタマイズ
 OEMInit() 中で、OEMRomChain を構築しなければならないことは、既に述べました。このことは、次のページに書かれています。

 Booting an Image with Multiple XIP Regions
 http://msdn.microsoft.com/en-US/library/ee483010.aspx

 しかし、このページの説明だけでは、あっさりし過ぎていて、具体的にどうすればよいのか、分かりません。OEMRomChain を構築する例は、x86 用の BSP 共通ソースをご覧になるのが良いでしょう。このソースは、次の場所にあります:

 %_WINCEROOT%/PLATFORM/COMMON/src/x86/COMMON/startup/oeminit.c

 oeminit.c の中にある、x86InitRomChain() が、OEMRomChain を構築する関数です。なお、x86InitRomChain() では、OEMRomChain という名前の変数の値を設定していますが、実は、OEMRomChain はマクロで、その実体は、OEMGLOBAL 構造体のメンバ pROMChain なのです。

 OEMGLOBAL
 http://msdn.microsoft.com/en-us/library/ee478176(v=WinEmbedded.60).aspx  
 http://msdn.microsoft.com/en-us/library/ee478176.aspx

 OEMRomChain を定義したヘッダファイルは、次の場所にあります:

 %_WINCEROOT%/PUBLIC/COMMON/OAK/INC/bcoemglobal.h

 OEMRomChain を構築する処理では、region の連結内容を記録した CHAIN region と、各 regionの ROM イメージヘッダ内容をアクセスする必要がありますが、それらは、以下のヘッダファイルで定義されています:

 %_WINCEROOT%/PUBLIC/COMMON/OAK/INC/romldr.h
 %_WINCEROOT%/PUBLIC/COMMON/OAK/INC/pehdr.h

 CHAIN region の構造については、次のページでも説明されています:

 XIP Chain
 http://msdn.microsoft.com/en-us/library/ee482877.aspx

・ブートローダのカスタマイズ
 ブートローダのカスタマイズについては、次のページをご覧ください。

 Adding Support for Multiple-BIN Image Notification
 http://msdn.microsoft.com/en-US/library/ee479207.aspx

 OEMMultiBINNotify
 http://msdn.microsoft.com/en-US/library/ee478943(v=WinEmbedded.60).aspx
 http://msdn.microsoft.com/en-US/library/ee478943.aspx

 OEMMultiBINNotify() を実装すると共に、CHAIN region の内容および他の region をホストから受け取って Flash メモリなどに書き込む動作、および、region が一つだけ転送された場合(部分アップデートの場合)に、それを正しい場所へ書き込む機能を実装しなければいけません。さらに、ブート動作では、Flash メモリなどに書き込んだ、CHAIN region と他の全ての region を RAM へロードする処理(XIP の場合は、ロードしない)を実装する必要があります。

Add comment 2012/01/13 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


Categories

Links

Posts by Authors

Recent Posts

Calendar

2012年1月
« 12月   2月 »
1234567
891011121314
15161718192021
22232425262728
293031  

Posts by Month

Posts by Category

Meta