第3回Device2Cloud Contest 決勝大会IME と、ソフトウェアキーボードに関する tips

WEC 7/WinCE 6.0 のブートシーケンス~init に至るまで

2012/12/21 koga

■はじめに
前々回のエントリ(「Telnet サーバと udevice.exe(2/2)」)で、User Mode Driver Framework の Reflector が、カーネルランドからユーザランドを呼び出す仕組みを紹介した際、カーネルのソースコードを少しだけ覗いてみました。今回は、WEC 7/WinCE 6.0 のブートシーケンスについて、再びカーネルのソースコードを覗いてみます。

もしあなたが、WEC 7/WinCE 6.0 のカーネル移植をこれから始めてみようと思っていらっしゃるのであれば、少しだけ参考になるかも知れません。あるいは、カーネル移植を自分で手がけることはないけれど、どのような手順で WEC 7/WinCE 6.0 がブートするのか興味がある、という方にも、楽しんでもらえると良いなと思います。Linux などの UNIX 系 OS では、カーネルが最初に init プロセスを生成・起動し、init が全てのプロセスの母となりますが、WEC 7/WinCE 6.0 で init に相当するものは何なのかも、見てみます。

■WEC 7/WinCE 6.0 のブートシーケンス(ARM の場合)
分かりやすく書く、というのは、難しいことです。今回は、簡潔に、カーネルのソースコードの具体的な個所を示して、ブートシーケンスの大枠だけを書いてみます。興味のある方は、ご自分でソースコードを追ってみて下さい。正確に理解するには、それが一番だと思います。説明の都合上、ここでは ARM プロセッサの場合のブートシーケンスについて記します。他のプロセッサ(x86, MIPS)の場合も、大枠は違わないでしょう。

まず最初に実行されるのは、OS イメージの先頭に配置されるエントリルーチンの、StartUp() です。StartUp() は、startup.s というソースファイルにアセンブラで実装されており、プロセッサの必要最小限の初期化処理を行います。具体的には、(プロセッサをスーパバイザモードに設定した後で)割り込みコントローラと MMU をディゼーブルにして、キャッシュと RAM を初期化します。また、デバッグ用の LED 点灯などのために、必要最小限の周辺機器コントローラの初期化も行います。startup.s は、WEC 7/WinCE 6.0 に付属する BSP ですと、たとえば次の場所にあります:

・Freescale i.MX27 の BSP(WEC 7)
 %_WINCEROOT%/platform/3DS_iMX27/src/OAL/oallib/startup.s

・デバイスエミュレータの BSP(WinCE 6.0)
 %_WINCEROOT%/platform/DEVICEEMULATOR/src/oal/oallib/startup.s

エントリルーチンの StartUp() を実装している startup.s は、実はカーネルとブートローダに共通のソースファイルとなっています。StartUp() は、初期化処理を行った後に KernelStart() という手続きを呼び出すのですが、カーネルとブートローダのそれぞれにおいて、KernelStart() が実装されています。つまり、カーネルとブートローダでは異なる起動処理の本体を、同じ名前の KernelStart() という手続きで実装することにより、それを呼び出すエントリルーチンを共通化している、というわけです。

カーネルの方の KernelStart() は、nkldr.lilb という静的リンクライブラリに所属する armstart.s で実装されており、armstart.s は、
 %_WINCEROOT%/private/winceos/COREOS/nk/ldr/arm/
に収録されています。ここで、nkldr.lib は oal.exe にリンクされるライブラリです。oal.exe は、
 %_WINCEROOT%/public/common/oak/files/common.bib
にある次の行の設定により、nk.exe という名前で OS イメージに格納されます。この nk.exe(oal.exe)が、カーネル移植レイヤを含む中核であり、ブートシーケンスに最初から関わるのです。


MODULES
;  Name            Path                                           Memory Type
;  --------------  ---------------------------------------------  -----------
; @CESYSGEN IF CE_MODULES_NK
IF IMGNOKITL
    nk.exe          $(_FLATRELEASEDIR)\oal.exe                  NK  SHZ
ENDIF IMGNOKITL

IF IMGNOKITL !
IF IMGNOKITLDLL
    nk.exe          $(_FLATRELEASEDIR)\oalkitl.exe              NK  SHZ
ENDIF IMGNOKITLDLL
IF IMGNOKITLDLL !
    nk.exe          $(_FLATRELEASEDIR)\oal.exe                  NK  SHZ
    kitl.dll        $(_FLATRELEASEDIR)\kitl.dll                 NK  SHZ
ENDIF IMGNOKITLDLL !
ENDIF IMGNOKITL !

KernelStart() が呼び出された後のブートシーケンスは、ARM プロセッサの場合、次の通りです:

1.) KernelStart() は、OEMAddressTable に記述されたカーネル仮想アドレスのマップに従って、MMU のページテーブルを設定したのち、MMU とキャッシュをイネーブルにして仮想アドレスに遷移する。また、割り込みハンドラのスタックを設定する。

2.) その後、KernelStart() は、ARMInit() を呼び出して kernel.dll のエントリルーチンのアドレスを取得する。そして、そのアドレス(kernel.dll のエントリルーチンである NKStartup() の開始アドレス)へジャンプする。
  ここで、ARMInit() は FindKernelEntry() という関数を使って kernel.dll のエントリルーチンのアドレスを取得します。ARMInit() と FindKernelEntry()、そして NKStartup() は、それぞれ次のソースファイルで実装されています。

 - ARMInit() :nk.exe に所属
  %_WINCEROOT%/private/winceos/COREOS/nk/ldr/arm/arminit.c

 - FindKernelEntry() :nk.exe に所属
  %_WINCEROOT%/private/winceos/COREOS/nk/ldr/ldrcmn.c

 - NKStartup() :kernel.dll に所属
  %_WINCEROOT%/private/winceos/COREOS/nk/kernel/arm/mdarm.c

3.) NKStartup() が、(nk.exe のではなく)kernel.dll の KernelStart() を呼び出す。
  kernel.dll の KernelStart() を実装しているソースファイルは、
   %_WINCEROOT%/private/winceos/COREOS/nk/kernel/arm/armtrap.s
  です。

4.) KernelStart() が、KernelInit() を呼び出す。KernelInit() によって、カーネルのヒープと仮想記憶管理構造、および、プロセス管理構造とスレッド管理構造が初期化される。
  ここで、スレッド管理構造を初期化する THRDInit() という関数では、SystemStartupFunc() という関数を、最初に起動するスレッドが実行する手続きとしてセットします。KernelInit() と THRDInit() は、それぞれ次のソースファイルで実装されています。

 - KernelInit()
  %_WINCEROOT%/private/winceos/COREOS/nk/kernel/nkinit.c

 - THRDInit()
  %_WINCEROOT%/private/winceos/COREOS/nk/kernel/thread.c

5.) その後、KernelStart() は Reschedule() を呼び出す。Reschedule() によって、最初のスレッドに実行が移って動き出し、(4) で設定された SystemStartupFunc() を実行する。
  Reschedule() も、armtrap.s で実装されています。THRDInit() によってマルチスレッド機構が構築されて、最初の Reschedule() の呼び出しによって、マルチスレッドモードへ遷移するというわけです。

6.) SystemStartupFunc() が、KernelInit2() を呼び出して、マルチスレッドモードでなければ行えないカーネルの初期化処理を実行する。
  SystemStartupFunc() は、さらに、ローダーとページプールの初期化、および、(それらが組込まれていれば)カーネルのロギング機構やカーネルデバッガの初期化も行います。メッセージキューやウォッチドッグタイマの初期化も、ここで行われます。SystemStartupFunc() を実装しているソースファイルは、
   %_WINCEROOT%/private/winceos/COREOS/nk/kernel/schedule.c
です。KernelInit2() のソースファイルは、nkinit.c です。

7.) SystemStartupFunc() は、各種初期化動作を行った後、ブートシーケンスの最終段階として、RunApps() という関数を実行するスレッドを起動する。
  RunApps() を実行するスレッドを起動した後、SystemStartupFunc() は、RTC のアラーム発火を待つ無限ループを実行します。

8.) RunApps() が、filesys.dll の WinMain() を実行するスレッドを起動する。
  その名前から連想する動作とは違い、RunApps() は、プロセス(アプリケーション)を起動するのではなく、スレッドを起動します。純粋マイクロカーネル構造だった WinCE 5.0 までは、filesys は DLL ではなく EXE でしたから、その名残なのかも知れません。
  filesys.dll のメインルーチン(WinMain())を実行するスレッドを起動した後、RunApps() は、CleanPagesInTheBackground() という関数を実行します。CleanPagesInTheBackground() は、
   %_WINCEROOT%/private/winceos/COREOS/nk/kernel/physmem.c
  で実装されており、自身の優先度を最低優先度値(255)に変更した後、不要になったページを破棄する無限ループを実行します。つまり、Linux でいえば swapper に相当するアイドルスレッドとなります。

以上が、WCE/WinCE カーネルのブートシーケンスの大枠です。ブートの最終段階として、filesys.dll のメインルーチンが実行されることが分かりました。
filesys.dll は、起動後(つまり、メインルーチンの実行開始後)、レジストリの初期化とファイルシステム API の初期化を行い、続いて、Storage Manager と Device Manager (device.dll) を起動します。device.dll によってデバイスドライバがロードされ、Storage Manager によって、ファイルシステム上のファイルを読み書きできるようになります。その後、filesys.dll は、レジストリの HKEY_LOCAL_MACHINE\Init キー配下に記述された設定内容に従って、プロセスを起動します。標準シェル(explorer.exe)が起動されるのは、このタイミングです。

filesys.dll のブート時動作については、WinCE 6.0 のリファレンスで説明されています:

 File System Boot Process (Windows Embedded CE 6.0)
 http://msdn.microsoft.com/en-us/library/ee490357(v=winembedded.60).aspx

この Blog の以前のエントリでも、Storage Manager と device.dll の起動順序について書きました。興味のある方は、こちらもどうぞ:

 レジストリ変更内容の永続化(2/2)
 http://www.stprec.co.jp/ceblog/2011/02/21/

さて、filesys.dll が、ブートの完了時に、レジストリの HKEY_LOCAL_MACHINE\Init キー配下の記述に従ってプロセスを起動すると書きました。ということは、filesys.dll が全てのプロセスの親となるわけです。つまり、UNIX 系 OS の init プロセスに相当します。init プロセスとは異なり、filesys.dll は、カーネルにロードされる DLL ですが、WinCE 5.0 までは、filesys もプロセスでした。こうやって見てみると、ブートシーケンスの大枠は、UNIX 系 OS と *大きくは* 違わないことが分かります。

■KernelRelocate() について
WEC/WinCE のブートシーケンスについて、もう少しだけ補足します。上に書いたブートシーケンスの (2) に登場した、ARMInit() です。

ARMInit() は、FindKernelEntry() を呼び出す前に、KernelRelocate() という関数を呼び出します。この KernelRelocate() を呼び出すことによって、初めて大域変数へアクセスできるようになるのです。大域変数は、実行形式ファイル(.exe と .dll)の data セクションに配置されますが、OS イメージは ROM 領域にあるので、それを RAM 領域へ再配置(relocate)する必要があります。この再配置は、LoadLibrary() や CreateProcess() によってロードされる場合は、カーネルのローダーによって行われます。しかし、上で述べたブートシーケンスにおける、nk.exe(oal.exe)と kernel.dll の呼び出しの流れでは、(6) で SystemStartupFunc() がローダーを初期化する前に行われます。従って、ローダーは介在できず、自分自身で再配置処理を行われなければいけません。そのための関数が KernelRelocate() なのです。

KernelRelocate() による再配置処理の詳細は、MSDN の Blog サイトにある、WinCE の開発チームの人による Blog エントリに書かれています:

 How does Windows Embedded CE 6.0 Start?
 http://blogs.msdn.com/b/ce_base/archive/2007/11/26/how-does-windows-embedded-ce-6.0-start_3f00_.aspx

上のページで ‘KernelRelocate’ を検索してみて下さい。なお、ここの説明には、そこまでは書かれていないのですが、おそらく、romimage.exe が OS イメージファイル(nk.bin)を生成する際、”copy entries” として TOC に記述されるのは(※この copy entries を KernelRelocate() が参照します)、oal.exe と kernel.dll の data セクションだけではないかと思います。それら以外の EXE や DLL の data セクションは、ローダーによってロード時に再配置が行われるはずですし、必ずロードされるとは限らないものを、ブート時に全て RAM に配置するのは無駄があるからです。

ローダーによる、ROM 領域の実行形式ファイルに対する再配置処理は、
 %_WINCEROOT%/private/winceos/COREOS/nk/kernel/loader.c
で実装されている PageInOnePage() の中で行われるように見えます。もし興味のある方は、ご自分で追ってみて下さい。WEC/WinCE の実行形式ファイルフォーマットについては、PE (Portable Executable) のフォーマットを解説したページが参考になります:

 EXEファイルの内部構造(PEヘッダ)
 http://codezine.jp/article/detail/412?p=2

 Peering Inside the PE: A Tour of the Win32 Portable Executable File Format
 http://msdn.microsoft.com/en-us/library/ms809762.aspx

Entry Filed under: BSP, OS の内部動作

Leave a Comment

Required

Required, hidden

Some HTML allowed:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Trackback this post  |  Subscribe to the comments via RSS Feed


Categories

Links

Posts by Authors

Recent Posts

Calendar

2012年12月
« 8月   1月 »
 1
2345678
9101112131415
16171819202122
23242526272829
3031  

Posts by Month

Posts by Category

Meta