杉本 拓也 松井 俊訓 杉本 拓也(すぎもと たくや/左)
松井 俊訓(まつい としのり/右)
株式会社富士通ソフトウェアテクノロジーズ


 Windows Embedded CE 6.0はWindows Vistaなどと同じくプリエンプティブなマルチタスクをサポートするOSであることは、前回の「第6回 マルチタスクの実行順はどうやって決めるのか」で説明しました。

 マルチタスクでは、複数のスレッドを並行して処理します。そのため、複数のスレッドが1つのリソースを共有する場合には、競合しないように排他処理をしなければなりません。また、スレッドの処理の順序に依存関係があるときは、そのタイミングを制御するために同期を取る必要もあります。これらの処理を行うために用意されているのが、「同期オブジェクト」です。

 Windows Embedded CE 6.0はデスクトップWindows OSと同様に「クリティカルセクション」「セマフォ」「ミューテックス」「イベント」などの同期オブジェクトをサポートしています。利用するAPIも、デスクトップWindows OSで利用しているものと変わりません。

プロセス内の排他制御はクリティカルセクション

 スレッドの実行順をスケジューリング(CPU実行権限の割り振り)するのは、カーネルのスケジューラです。他のスレッドに実行権限が渡されると、実行中のスレッドの処理は中断されます。そのタイミングは、他の優先順位の高いスレッドの状態やクァンタムの値に従ってスケジューラが制御します。

 しかし、他のスレッドに処理を中断されるのを防ぎたいケースもあります。たとえば、時間に制限のあるハードウェアアクセスや共有メモリへのアクセスなどの場合です。こうした中断を防ぎたい場合に利用する同期オブジェクトの1つとして、クリティカルセクションがあります。

 クリティカルセクションを利用したコード例と図は、次のようになります。

 CRITICAL_SECTION MyCritSec;
 InitializeCriticalSection(&MyCritSec);
 EnterCriticalSection(&MyCritSec);

 …中断されるのを防ぎたい処理

 LeaveCriticalSection(&MyCritSec);
 DeleteCriticalSection(&MyCritSec);

図1●クリティカルセクションによるスレッドの切り替え
図1●クリティカルセクションによるスレッドの切り替え
[画像のクリックで拡大表示]

 EnterCriticalSectionとLeaveCriticalSectionで囲まれた部分がクリティカルセクションです。ここが中断を防ぎたい部分になります。図1では、Thread2のt3~t6がこれに相当します。EnterCriticalSectionでクリティカルセクションに入り、LeaveCriticalSectionでクリティカルセクションを解放しますが、その間は1つのスレッド(ここではThread2)だけがCPUの使用権を持ち、他のスレッドは実行できません。つまり排他処理が行われます。クリティカルセクションで排他制御を行うのは、1つのプロセス内のスレッド間です。

より複雑な排他制御にはミューテックスやセマフォ

 ミューテックスも、共有リソースの排他制御に利用できる同期オブジェクトです。ミューテックスはクリティカルセクションと似ていますが、任意の名前を設定できてカーネルオブジェクトとして管理されるため、OS上のすべてのスレッドに対して排他制御を行うことができます。

 以下に示したサンプルコードのように、まずCreateMutexというAPIを使ってミューテックスオブジェクトのハンドルhMutexを生成します。このハンドルを使い、WaitForSingleObjectまたはWaitForMultipleObjectを利用して排他制御を行います。

 このコードと図2で示したのは、Thread1とThread2がミューテックスを利用して排他制御を行っている様子です。たとえばThread1がWaitForSingleObjectから復帰した場合、Thread1がこのミューテックス(hMutex)のオーナーとなります。Thread2は、Thread1がミューテックスのオーナー権を解放(ReleaseMutex)するまでWaitForSingleObjectから復帰できません。Thread1がReleaseMutexを呼び出してオーナー権を解放すると、Thread2がWaitForSingleObjectから復帰して、今度はThread2がミューテックのオーナーとなります。

 // 初期化処理
 HANDLE hMutex;
 hMutex = CreateMutex(NULL, FALSE, NULL);

 //Thread 1
 WaitForSingleObject(hMutex, INFINITE);

 …排他対象の処理

 ReleaseMutex(hMutex);

 //Thread 2
 WaitForSingleObject(hMutex, INFINITE);

 …排他対象の処理

 ReleaseMutex(hMutex);

図2●ミューテックスによるスレッドの切り替え
図2●ミューテックスによるスレッドの切り替え
[画像のクリックで拡大表示]

 また、セマフォも排他制御に利用する同期オブジェクトです。実装はミューテックスに似ていますが、ミューテックスとは異なり複数のスレッドがオブジェクトにアクセスできます。したがって、セマフォは排他対象に対する同時アクセス数の上限を管理する使い方ができます。カウンタの付いたミューテックスと言えばわかりやすいでしょう。

 ミューテックスもセマフォも耳慣れない名前ですが、ミューテックスとはMutual Exclusion(相互排除)を略してMutexとしたものです。またセマフォは手旗信号という意味で、処理の要求と処理の解放を手旗信号のように相互に知らせ合いながら排他制御を行うことに由来しています。

割り込みの通知はイベント

 これに対して、スレッド間でタイミングを通知するときに利用する同期オブジェクトがイベントです。イベントを利用すると、ある事象が発生したことを他のスレッドに伝えることができます。

 あるスレッドが事象の発生を伝えたい場合は、SetEventを呼び出します。このAPIを呼び出すと、指定したイベントオブジェクトがシグナル状態になります。一方、事象の発生を待つ側のスレッドは、WaitForSingleObjectまたはWaitForMultipleObjectを呼び出します。これらのAPIを呼び出したスレッドは、イベントオブジェクトがシグナル状態になるまでブロック状態のままです。

 ひとたびイベントオブジェクトがシグナル状態になると、WaitForSingleObjectまたはWaitForMultipleObjectを呼び出したスレッドのブロック状態が解除され、実行可能状態になります。この様子を示したのが図3です。Tread1は事象の発生を待つ側で、WaitForSingleObjectを呼び出しています。事象の発生を伝える側のThread2でSetEventを呼び出すと、Tread1は実行可能状態になります。

 組み込み機器では、ハードウェアからの割り込みをデバイスドライバにすばやく通知すしなければならない場面がよくあります。そのような時にイベントを利用します。

図3●イベントによるスレッドの切り替え
図3●イベントによるスレッドの切り替え
[画像のクリックで拡大表示]

 組み込み機器の複雑なマルチタスク処理の裏では、以上のようなオブジェクトがスレッドの同期をコントロールしながらリアルタイム性を実現しています。