こんにちは、ナナです。
ここまで、セマフォ機能による排他制御を解説してきました。
しかし、セマフォの本質は資源数を管理することであり、それを利用した排他制御は「できなくはないが、ほころびがある」そんな印象です。
ここで登場するのが「ミューテックス」です。
ミューテックスは、排他制御に特化させた機能を持つ排他制御の専門家なのです。
本記事では次の悩みを解消する内容となっています。
それでは、「ミューテックス」を解説しましょう!
ミューテックス機能の概要
何やら「セマフォ」を超える逸材が現れたと聞きました。それが「ミューテックス」という新人さんだそうです。どんな新人さんなのですか?
はい。「ミューテックス」は、排他制御のスペシャリストなんですよ。
バイナリセマフォでは不都合があった部分が、ミューテックスにより解決していたりするのです。
「ミューテックス」はITRONに限らず、WindowsやLinuxといった汎用OSでも実装されている排他制御用の機能です。
今回のHOSでは「ミューテックス」は利用できないのですが、システム開発を行う上で欠かせない技術として解説していきます。
ITRONのミューテックスは拡張同期・通信機能に所属
ITRON仕様書で「ミューテックス」が定義されている章番号を確認しましょう。「4.5.1章 ミューテックス」となっていますね。
この4.5章である拡張同期・通信機能に所属する機能は、スタンダードプロファイルには含む必要がない機能です。
本カリキュラム中で利用しているHOSでは、「ミューテックス機能」をサポートしていません。
組み込み開発の世界は、メモリやCPUのスペックに制限があったりすることが多いのです。
そのため、RTOS実装者によってはいくつかの機能を搭載しない選択肢が用意されているのです。
ミューテックスの仕様
それでは、まずはITRON仕様を確認してみましょう。
このように仕様においても、「タスク間の排他制御」が目的と明記されているのがミューテックスです。
HOSでミューテックスを使うためには
HOSにはアドバンスド版というバージョンが用意されています。こちらでは「ミューテックス」機能をサポートしています。
公式サイトの『Hyper Operating System(ITRON仕様OS)』において、「v4a」となっているものが、アドバンスド版のHOSです。
わたし自身、この「アドバンスド版」は利用したことはありません。OSの起動方法なども変更されているようです。
使いたい人はアドバンスド版をポーティングしてみるのもいいかもしれませんね。
ミューテックスの使い方
今の開発環境では「ミューテックス」は使えないんですね~。残念。
でもでも、将来使うかもしれないから、どんな機能があってどんな風に使うのかを知っておきたいです。
それはいい心がけだね。ミューテックスは非常にメジャーな機能です。実践的な開発を仕事で行うような人たちは、知っておいて損はないでしょう!
それではいつも通り、オブジェクトの作り方から行きましょう。
ミューテックス生成のための静的API
ミューテックスを生成するための静的APIを見てみましょう。
CRE_MTX(ID mtxid, {ATR mtxatr, PRI ceilpri});
引数パラメータを解説しておきます。
ID | mtxid | 生成するミューテックスのID。整数として一意のIDを割り付ける。 |
ATR | mtxatr | ミューテックスの属性。主にプロトコルを指定する。 |
PRI | ceilpri | 上限のタスク優先度 ※後述する「優先度上限プロトコル」を選択した時のみ有効 |
属性には(TA_FIFO || TA_TRPI || TA_INHERIT || TA_CEILING)が指定できます。ミューテックス特有のパラメータが下記です。
属性 | 機能概要 |
---|---|
TA_INHERIT | 優先度継承プロトコルの適用 |
TA_CEILING | 優先度上限プロトコルの適用 |
プロトコルと呼ばれる2つの仕組みがミューテックスには備わっています。使いたい人はこの属性を付ければよいですよ。
プロトコルの詳細は後ほど、紹介しましょう。
ミューテックスのロック/アンロック
セマフォでは「wai_sem」による獲得、「sig_sem」による返却で排他を行いました。
ミューテックスでは次のサービスコールを利用します。
ER loc_mtx(ID mtxid);
ER unl_mtx(ID mtxid);
名前が「ロック:loc」「アンロック:unl」となっており、鍵を掛けるようなイメージに近くなっています。
引数はCRE_MTXで生成したミューテックスIDになります。
それではセマフォの時のプログラムをミューテックスに書き換えてみましょう。
書き込み側のタスク
void setPoints(void)
{
・・・
loc_mtx(MTXID_MTX1);
points = 50;
unl_mtx(MTXID_MTX1);
}
読み取り側のタスク
void printPoints(void)
{
loc_mtx(MTXID_MTX1);
if (points == 100)
{
printf("%d:満点です", points);
}
unl_mtx(MTXID_MTX1);
}
このようにクリティカルセクションを「loc_mtx」「unl_mtx」で括るだけです。セマフォを使ったときと、名前が異なるだけで扱い方は一緒ですね。
「ミューテックス」も「セマフォ」も排他制御の使い方は同じなんです。
ミューテックスがサポートするプロトコル
「優先度継承プロトコル」と「優先度上限プロトコル」っていったい何なんですか?
ミューテックスに適用できるオプションと考えるといいね。必要に応じて指定すれば、その機能が有効になるんだよ。
「プロトコル」という言葉は、主に通信を行う場面で使われるものですが、ミューテックスにおいては機能オプションと捉えればよいでしょう。
これらの機能は「優先度逆転問題」と呼ばれるものを防止するために作られているのですが、まずはどのような機能なのかを解説しましょう。
ミューテックスの「優先度上限プロトコル」とは
CRE_SEMの属性に対して「TA_CEILING」を指定することで有効化されます。
優先度上限プロトコルは、ミューテックスをロックしたときに、タスク優先度を指定の優先度へ一時的に引き上げるオプションです。
指定の優先度はCRE_SEMの「ceilpri」として設定します。つまり、生成時に決めておく必要があるということになります。
資源をロックしている時に優先度を上げることでいち早く仕事を終わらせることができるようにする仕組みです。
ただし、上限優先度をどの値にするかは決めづらい部分があるため、私はこのオプションを使ったことはありません。
ミューテックスの「優先度継承プロトコル」とは
CRE_SEMの属性に対して「TA_INHERIT」を指定することで有効化されます。
優先度継承プロトコルは、2つのタスクがミューテックスをロックすることで優先度に影響を与えるプロトコルです。
平社員タスク(優先度:5)と社長タスク(優先度:1)がいます。平社員が先にミューテックスをロックし、その後に社長がミューテックスをロックしようとしたシーンです。
このように、ミューテックスをロックしただけでは、平社員タスクの優先度は変化しません。
しかし、優先度の高い社長タスクがミューテックスをロックしようとすると、鍵がないためミューテックスのアンロック待ち状態になります。
この瞬間、ミューテックスを持っている平社員タスクは、待っている社長タスク優先度を一時的に借りることができるようになるのです。
このようにタスク優先度を一時的に「継承」することから、この名前がついています。
優先度継承プロトコルはものすごく理にかなった機能だと思ってます。ミューテックスを使う際は、このオプションは常に有効化することをオススメします。
セマフォと異なるミューテックスの特徴
「ミューテックス」と「セマフォ」の違いは2つのプロトコルをサポートしているってことなんですね。
いや、それだけじゃないんだよ。「ミューテックス」には他にも違いがあるんですよ。
ミューテックスは「排他制御」を理にかなった方法で管理するため、セマフォとは異なる部分が他にもあります。紹介しましょう。
ロックしたタスクはミューテックスの所有権を持っている
「セマフォ」では、そもそも獲得(wai_sem)すらしていないタスクがセマフォの返却(sig_sem)を行うことができました。
「ミューテックス」では、ロックしたタスクしかアンロックすることはできません。ミューテックスを鍵とイメージすれば、この動きは当然ですよね。
これはミューテックスに対して、タスクが所有権を持っているからこそできることなのです。
再起ミューテックスに対応している場合がある
「再起ミューテックス」はミューテックスの種類のうちの1つです。
ITRONではサポートしていませんが、世の中の開発環境では、この再起ミューテックスをサポートしているものもあります。これは結構便利なので特徴を知っておくとよいです。
再起ミューテックスとは、とあるタスクが対象のミューテックスに対して、複数回のロックを行ってもデッドロックしないミューテックスのことです。
セマフォにおいて同じことをするとデッドロックが発生します。
しかし、再起ミューテックスの場合に限り、ロック/アンロックをネスト構造として記述が可能になるのです。
セマフォの場合
void MAIN(VP_INT exinf)
{
while(1)
{
wai_sem(SEMID_SEM1); // 1回目
wai_sem(SEMID_SEM1); // デッドロック発生
sig_sem(SEMID_SEM1);
sig_sem(SEMID_SEM1);
dly_tsk(1000);
}
return;
}
再起ミューテックスの場合
void MAIN(VP_INT exinf)
{
while(1)
{
loc_mtx(MTXID_MTX1); // 1回目
loc_mtx(MTXID_MTX1); // 2回目
unl_mtx(MTXID_MTX1); // 返却
unl_mtx(MTXID_MTX1); // 返却
dly_tsk(1000);
}
return;
}
ITRONのミューテックスは再起に対応していないため、同じプログラムを書くと2回目のloc_mtxが失敗しますので注意しましょう。正しく排他区間をガードできません。
再起ミューテックスの便利なシーンは関数を跨いだ排他処理です。
ロック/アンロックをネストできるため、各関数の中で独立してロック/アンロックするように設計すればよいので、管理が楽なのです。
皆さんが利用するシステムではひょっとしたら「再起ミューテックス」がサポートされているかもしれません。そんな時は使ってみると便利なことがわかりますよ。
ミューテックスの特徴まとめ
それではミューテックスの特徴を振り返りましょう。
- 管理する資源は「ロック」「アンロック」にて施錠/開錠する
- 優先度上限プロトコルに対応している
- 優先度継承プロトコルに対応している
- ロックしたタスクがミューテックスの所有権を持っている
- 再起ミューテックスに対応している場合もある