タスク間の同期および排他制御に用いられるオブジェクト「セマフォ」について、課題演習を基に詳しく解説する
前回の最後にお届けした課題演習―【問題2】は、「セマフォ」に関する問題でした。
セマフォは、メモリやポートなどの共有資源について獲得と返却の操作を行うことで、タスク間の同期および「排他制御」を可能にするオブジェクトです。
今回は、問題の解答に続いて、「セマフォと排他制御」について解説します。この機会にしっかりと理解しておきましょう。
それでは、解答を発表します!
共有資源のセマフォを獲得する操作を「P操作(オランダ語で“通行する”を意味するPasserenの略)」、セマフォを解放する操作を「V操作(オランダ語で“腕木を上げる”を意味するVerhoogの略)」といいます。P操作を行ったときに、セマフォのカウンタが「1」であれば、「1」が減算され、セマフォを獲得できます。セマフォを獲得した状態でV操作を行うと、セマフォのカウンタに「1」が加算され、セマフォが解放されます。
前回説明したように、セマフォはタスクの同期に用いるオブジェクトの1つですが、排他制御にもよく利用します。
排他制御とは、メモリや入出力ポートのような共有資源に対し、複数のタスクが同時にアクセスしないように制御することです。例えば、タスクAがメモリ上のあるデータを更新しようとしているとき、更新が完了する前にタスクBがそのデータを取得すると、データの整合性が失われてしまいます。このようなことがないように、あるタスクが共有資源にアクセスしている間は、ほかのタスクがアクセスしないようにしなければなりません。
セマフォという名称は、鉄道の腕木式信号機に由来します。この信号機は、線路に進入可能な場合には腕木を水平な状態に設定します。一方、腕木を下向きにした場合は線路には進入できないという意味です。
セマフォによる排他制御では、共有資源にアクセスしたいタスクは、その共有資源のセマフォを獲得する必要があります。セマフォの獲得をP操作といいます。P操作を行ったときにセマフォのカウンタが「1」であればカウンタから「1」が減算され、セマフォを獲得できます。共有資源を利用し終わったら、セマフォを解放します。セマフォの解放をV操作といい、V操作を行うとカウンタに「1」が加算され、ほかのタスクがセマフォを獲得できる状態になります。つまり、セマフォのカウンタが腕木の役割を果たしており、「1」は“進入可(共有資源にアクセス可)”、「0」は“進入不可(ほかのタスクが共有資源にアクセス中)”ということです。
タスク内でセマフォを獲得してから解放するまでの間、つまり排他制御の対象となる処理の区間を「クリティカルセクション」と呼びます。クリティカルセクションでは、データの更新や装置への出力など、途中でほかのタスクに切り替わると不都合が生じる処理を記述します。
排他制御を行う際には、「デッドロック」に注意しなければなりません。
例えば、タスクAが共有資源1のセマフォを、タスクBが共有資源2のセマフォを獲得しているとしましょう。実行状態になったタスクBが共有資源2のセマフォを解放する前に、共有資源1のセマフォを獲得しようとすると、獲得できずに待ち状態に遷移します。なぜなら、共有資源1のセマフォはタスクAが獲得しているからです。代わって実行状態に遷移したタスクAが、共有資源1のセマフォを解放せずに共有資源2のセマフォを獲得しようとすると、こちらも獲得できずに待ち状態に遷移します。タスクAとタスクBがお互いにセマフォの解放を待って膠(こう)着状態に陥ることをデッドロックといいます。
デッドロックを回避するためには、1つのタスクが、セマフォを解放しないまま連続してセマフォを獲得しないようにすることが重要です。どうしても1つのタスクで複数の共有資源に連続してアクセスする必要がある場合には、その順番を決めておきます。
前述の例では、必ず共有資源1、共有資源2の順番でセマフォを獲得し、使い終わったら同じ順番でセマフォを解放するようにします。この場合、タスクBは共有資源1のセマフォの獲得を待つことはありますが、タスクAがいずれセマフォを解放するため、待ち状態から実行可能状態に遷移してセマフォを獲得できます。セマフォを獲得するシステムコールには待機時間を指定できるものがあるため、これを利用して永遠に待ち続けることがないようにする方法もあります。
排他制御では「優先度逆転」の問題にも注意しなければなりません。
例えば、タスクA、タスクB、タスクCという3つのタスクがあり、この順番で優先度が高いとします。タスクCが実行状態のときに共有資源のセマフォを獲得し、解放する前にタスクAとタスクBが実行可能状態になると、最も優先度の高いタスクAが実行状態になります。タスクAが共有資源のセマフォを獲得しようとすると、タスクCが獲得中であるため、待ち状態に遷移してしまいます。続いて、2番目に優先度の高いタスクBが実行状態になり、処理を終えると、タスクCが実行状態になります。タスクCが共有資源のセマフォを解放すると、タスクAの待ち状態が解除されます。
本来であれば、最も優先度の高いタスクAが優先して処理を実行できなければなりません。しかし、うまく排他制御を行わなければ、優先度の低いタスクBやタスクCの処理が先に実行されてしまうという問題が発生します。
優先度逆転を防ぐためには、セマフォを獲得してから解放するまでの間にタスクの切り替えが起こらないように、タスクの分割および優先度の設定を行わなければなりません。また、優先度の高いタスクと低いタスクが同じ共有資源を利用しないようにするよう注意することも大切です。
また、「ミューテックス」を使う方法もあります。ミューテックスも排他制御に利用できるオブジェクトです。動作はセマフォに似ていますが、優先度逆転を防ぐための「優先度継承機能」や「優先度上限機能」を備えています。ただし、OSによってはミューテックスをサポートしていない場合もあります。
いかがでしたでしょうか? セマフォと排他制御について理解できましたか。次回は、組み込みシステム上で動作する「プログラムの構造」と「ROM化」について解説します。お楽しみに! (次回に続く)
Copyright © ITmedia, Inc. All Rights Reserved.