はじめに
前回はRTOS、タスク、割り込みについて勉強しました。RTOS、タスク、割り込みについて
今回は排他制御について勉強します。
排他制御について
プロセスが独立に動作している場合は排他制御というものは不要ですが、並列処理などで複数のプロセスを協力して動作させたい場合に、共有資源を資源を利用する場合があります。
その際に使われるのが排他制御となります。
共有資源の利用
複数のプロセスで処理を分担している場合でも、データを相互に共有して処理を進めることが多いです。その際に共有しているデータを同時に更新してしまうという競合問題が発生します。図に共有データを同時に更新する場合の例を示します。
図1:共有資源のイメージ |
図において任意のプロセスが終了してから別のプロセスが動作するのなら問題は生じないのですが、実際はプロセスの切り替えが途中で発生する場合があります。
プロセスの切り替えタイミングにより結果が異なるという例をを表1に示します。
表1プロセス実行タイミングによる結果の違い
タイミング例1の場合はプロセスA、Bの演算結果がXに反映されてますが、タイミング例2の場合はプロセスAの演算結果が反映されておりません。
プロセスが任意のタイミングで切り替えられても計算結果に矛盾を生じさせないように排他的制御の機能が必要になります。また、このように複数のタスクが同時に実行してはいけない区間のことをクリティカルセクションといいます。
セマフォ
排他制御の中にセマフォと呼ばれるものがあります。先ほどの問題はバイナリセマフォ(1と0の状態を持つデータ)を使うことで解決することが出来ます。クリティカルセクションを実行しようとするタスクはセマフォが1か確認します。1の場合は誰も共有資源を使ってないので、セマフォ値から1減算し0に変えてクリティカルセクションの実行を開始します。セマフォから1減算する処理のことをP操作(オランダ語が由来でPasseer:通過する)といいます。
クリティカルセクションの実行を終えたタスクはセマフォを1加算し他のタスクがクリティカルセクションに入れるようにします。このセマフォに1加算する操作のことをV操作(オランダ語が由来でVerhoog:腕木を上げる)といいます。
今回は1か0の2値でしたが、2値以上を持つセマフォもありカウンティングセマフォ(ジェネラルセマフォ)といいます。カウンティングセマフォの方がより高度な制御を実施することが出来ます。
(補足)セマフォの由来
セマフォはもともと鉄道などで利用されていた腕木信号が元になっております。
図2:腕木信号のイメージ(引用元) |
クリティカルセクションから抜けたタスクがV操作(腕木を上げる)しますが、図2の例だと腕木が上がった状態は通行止めなので、他のタスクが使用できない状態となってしまいます。
腕木信号の画像を調べると図2のような腕木信号しか出てこなかったのですが、昔(海外?)は逆で腕木が上がった状態が進行で、腕木が下がった状態が停止だと推定してます。
このように関連付けするとP操作とV操作がどういう操作か覚えやすいと思います。
デッドロック
排他制御を行う際に注意しなければいけない事象の1つにデッドロックがあります。簡単に言うとデッドロックとはお互いが相手の処理が終わるのを待っている状態です。
図3にデッドロックが発生してしまう例を示します。
図3:デッドロックの例 |
図3でデッドロック発生してしまった理由として、二つのプロセスで資源を獲得する順番が異なっていることがあげられます。タスクAは共有資源1→2の順で確保してますが、タスクBは共有資源2→1の順で確保してます。
なので、デッドロックを回避するために下記注意する必要があります。[]
- 複数の資源を必要とする際は資源を確保する順番を同一にすべき。
- または、複数の資源を必要とする処理では、1度にまとめて資源の確保を行うべき
- 使用済みの資源は直ちに開放すること
- 資源を排他的に使用するときは、他のプロセスが使用してないことを確認してから確保の要求を行う事
- 排他的に利用するのか共有するのかをはっきり区別して要求すること
プライオリティインバージョン(優先度逆転)
排他制御を実施する際にはデッドロック以外にもプライオリティインバージョン(優先度逆転)にも注意が必要です。図4に優先度逆転現象が起きる例を示します。
図4:優先度逆転の例 |
この事象を回避するためにRTOSが持つミューテックスという機能を使います。ミューテックスには優先度上限プロトコルと優先度継承プロトコルがあります。優先度上限プロトコルは共有資源を獲得する際にタスクの優先度をあらかじめ設定した値に変更するものです。優先度上限プロトコルは上限値をあらかじめ設定しなければいけないためシステムの規模が大きくなるとメンテナンスをするのが大変というデメリットがあります。
優先度継承プロトコルは共有資源を獲得している最中にウェイト状態となっているタスクの優先度を引き継ぐというものです。タスクの優先度が自動的に変化するため運用もしやすいのが特徴です。
また、この2つのプロトコルについては下記のサイトがわかりやすかったので、参照お願いします。
セマフォ以外の排他制御
割り込み禁止による排他制御
以前の記事でも紹介しましたが、割り込みは非タスクと呼ばれるタスクとは別のものでした。
セマフォでは他のタスクが共有資源を使ってタスクを実行できない場合、ウェイト状態に遷移しますが、割り込みは非タスクのためウェイト状態が無く、セマフォを使う事が出来ません。
ただ、割り込み処理でも共有資源にアクセスすることがあるため、排他制御は必要になります。その際に使われるのが割り込み禁止(CPUロック)です。
割り込み禁止は基本的にタスク側で実施します。(優先度的にタスクより割り込み処理の方が高いため。)ただし、複数の割り込み(多重割り込み)が発生した際の優先順位を決める場合に、割り込み側でも割り込み禁止を実施することがあります。
また、割り込み禁止には次のような注意点があります。[]
- 割り込み禁止区間はなるべく小さくすること(基本的に割り込みの方が優先度が高く、割り込み禁止区間が長くなってしまうと割り込みとしての機能を果たさないので)
- 割り込み禁止区間中は使えるサービスコールに制限があります。
ディスパッチ禁止による排他制御
以前の記事でも紹介しましたが、タスクはレディ状態からディスパッチされることでランニング状態となり、処理が実施されます。RTOS、タスク、割り込みについて
ディスパッチ禁止することで、他のタスクはレディ状態に遷移できないためプログラムの実行ができなくなります。(割り込み禁止は非タスク - タスクもしくは非タスク - 非タスク間の排他制御でしたが、ディスパッチの禁止はタスク - タスク間の排他制御になります)
注意点としてはディスパッチ禁止はあくまでタスク - タスク間での排他制御なので、割り込みが発生した場合は割り込みの方が優先されてしまいます。
(セマフォとディスパッチ禁止どちらを使うのが良いのかはわかりませんでした。。。)
スピンロックによる排他制御
マルチプロセッサシステムでメモリを共有している場合は先ほど紹介した割り込み禁止やディスパッチ禁止で排他制御を実施することが出来ません。(物理的に複数のプログラムが実行されている状態となっているため)また、プロセッサごとに異なるOSを搭載している場合はOSの機能であるセマフォを使うことが出来ません。このような場合に使われるのがスピンロックです。
スピンロックとは端的にいうとクリティカルセクションを獲得できるまでループに入り待機する処理です。複数のプロセッサが参照できる共有メモリなどにフラグを設定し、定期的にそのフラグの状態を確認することで、クリティカルセクションに行くか再びループするかを判別しています。
スピンロックはループ処理を実施しているという事もあり、何もしていないのにCPUを占有してしまいます。短時間だけなら問題はないのですが、長時間スピンロックされると無駄が多くなってしまいます。
OSによって最初にスピンロックを実施し、時間がかかりそうならタスクをウェイト状態に戻す2つを組み合わせて実施する場合があります。(ただしウェイト状態からレディ状態に遷移し、ランニング状態に遷移するためには時間がかかるため注意が必要です。)
まとめ
今回は排他制御について勉強しました。
セマフォを使うことで排他制御を実施できますが、デッドロックや優先度逆転などに注意が必要です。
また、セマフォ以外にも割り込み禁止、ディスパッチ禁止、スピンロックなどがあり、状況に応じてそれぞれを使い分けることが必要です。(ディスパッチ禁止についてはセマフォとどのように使い分けるか不明だったため引き続き調査が必要です。。。)
0 件のコメント:
コメントを投稿