アプリケーションからリブート(または電源 OFF)~その1(1/2)8/25(火)”Windows Embedded Conference in Yokohama 2009″

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

2009/07/21 koga

前回の続きです。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();

Entry Filed under: OS のコンフィグレーション, 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

2009年7月
« 12月   8月 »
 1234
567891011
12131415161718
19202122232425
262728293031  

Posts by Month

Posts by Category

Meta