こんにちは、ナナです。
ここまで、イベントフラグ・データキュー・メールボックスと主にタスク間でデータを通信するためのオブジェクトを解説してきました。
「セマフォ」は、ITRONの中では同期・通信機能として同じ部類に定義された機能ですが、通信を主としたこれまでの機能とは役割が異なります。
セマフォには大きく分けて次の2種類があります。
まずは「カウンティングセマフォ」から解説し「セマフォ」の基礎を学びましょう!
本記事では次の悩みを解消する内容となっています。
それでは、「カウンティングセマフォ」を解説しましょう!
セマフォ機能の概要
師匠!今日のテーマは「セマフォ」ですか?恥ずかしながら我が人生において、はじめてお目にかかる言葉です。
そうだね、なかなか日常で「セマフォ」という言葉を耳にすることはないだろうね。でも、マルチタスクのシステムにおいて排他制御を司る「セマフォ」はすごく大事な機能だね。
「セマフォ」という言葉を始めて耳にする方もいることでしょう。まずは、「セマフォ」の概要から学んでいきましょう。
セマフォの仕様
それでは、まずはITRON仕様を確認してみましょう。
このように「使用されていない資源の有無や数量を表現する」のがセマフォです。
ここで示されている「使用されていない資源」とは何なのか?
例えばレンタサイクルショップがあったとします。貸し出せる自転車の台数は決まってますよね。
この資源の「残数」を管理するのがセマフォです。
このようにソフトウェア上で管理する資源に対して、その利用できる数を管理するのが「セマフォ」なんです。
セマフォで使用する代表的なサービスコール
セマフォではよくこれらのサービスコールを使用しますので覚えておきましょう。
「セマフォ」は略称で「セマ」と呼んだりすることがあります。
そのため、sig_semは「シグセマ」、wai_semは「ウェイセマ」なんて呼んだりしますよ!
セマフォオブジェクトの生成方法
師匠!それではいつも通り、まずはオブジェクトの生成が必要ですよね。どんな風に「セマフォ」は作ればよいのでしょうか?
はい、その通り。では、セマフォオブジェクトの生成からやってみましょう。指定するパラメータはシンプルなので難しくありませんよ。
セマフォ生成のための静的API
セマフォを生成するための静的APIを見てみましょう。
CRE_SEM(ID semid, {ATR sematr, UINT isemcnt, UINT maxsem});
引数パラメータを解説しておきます。
ID | semid | 生成するセマフォのID。整数として一意のIDを割り付ける。 |
ATR | sematr | セマフォの属性 |
UINT | isemcnt | 資源数の初期値 |
UINT | maxsem | 資源数の最大値 |
属性には(TA_TFIFO || TA_TPRI)が指定できます。これはセマフォに対してタスクが待ち状態になった場合の、待ち順を決定するパラメータです。
maxsemとisemcntに関しては管理したい対象資源の数を指定します。
生成時に必要となる「初期値」は「最大数」に合わせておくのが自然でしょう。
最大資源数が2以上のセマフォのことを「カウンティングセマフォ」と呼びます。
CRE_SEMによるセマフォの生成定義
では、セマフォを生成してみましょう。system.cfgにセマフォを1つ生成定義してみてください。
//------------------------------------------------
// メールボックス定義
//------------------------------------------------
CRE_MBX(MBXID_MBX1, {(TA_TFIFO | TA_MFIFO), 1, NULL});
//------------------------------------------------
// セマフォ定義
//------------------------------------------------
//------------------------------------------------
// セマフォ定義
//------------------------------------------------
CRE_SEM(SEMID_SEM1, {TA_TFIFO, 2, 2});
セマフォの資源獲得と返却方法
師匠!「セマフォ」は生成以外にはどのようなサービスコールを持っているのでしょうか?「資源数」に関わるものなんですよね?
セマフォは資源を管理するため、基本的なサービスコールは「資源の獲得/返却」ができるサービスコールを提供します。
セマフォの概要を再度読んでみましょう。
セマフォとは資源数の管理をしてくれるオブジェクトです。セマフォに対する操作とは「資源の獲得(wai_sem)」と「資源の返却(sig_sem)」です。
セマフォ資源を獲得するサービスコール
では、wai_semのITRON仕様を見てみましょう。
ER wai_sem(ID semid);
つまり、wai_semサービスコールが呼び出されることで、残数を示す資源数が減少します。残数が残っている場合は、タスクに特に影響はありません。
ただし、残数が0の時にwai_semサービスコールが呼び出されると、呼び出したタスクは「待ち状態」へと変化します。
この場合、セマフォに対する資源返却があるまでタスクは「待ち状態」を続けることになります。
このように資源の獲得において、残数がない場合はタスクの動きを止めることができる、というのがセマフォの特徴です。
セマフォ資源を返却するサービスコール
それでは次に、sig_semのITRON仕様を見てみましょう。
ER sig_sem(ID semid);
つまり、sig_semによる資源が返却されるため、セマフォの資源数が増加することになります。
資源数が0となっており、別のタスクが「待ち状態」になっている場合は、sig_semによる資源返却により、直ちに待ち状態が解除されます。
このケースではセマフォの資源数は0のままです。なぜなら、返却された資源は待っていたwai_semにすぐに貸し出されてしまうためです。
wai_sem/sig_sem共に引数はセマフォIDのみで非常にシンプルなサービスコールとなっています。
これらが様々なタスクから呼び出されることで資源の獲得/返却を繰り返すのです。
カウンティングセマフォを使ったプログラム
師匠!「カウンティングセマフォ」ってプログラムからどんな風に使えばいいですか?
うん、それじゃあ「wai_sem」と「sig_sem」をタスクから呼んでみてどんな動きをするかを確認してみましょうね。
それでは、資源数「2」のセマフォを3人のタスクで獲得/返却を繰り返してみましょう。
つまり、3人のうち1人は必ず待たされることになりますね。
まずはタスクを3人分用意しましょう。
//------------------------------------------------
// タスク定義
//------------------------------------------------
CRE_TSK(TSKID_MAIN, {TA_HLNG|TA_ACT, 0, MAIN, 1, 128, NULL});
CRE_TSK(TSKID_TASK1, {TA_HLNG|TA_ACT, 0, TASK1, 1, 128, NULL});
CRE_TSK(TSKID_TASK2, {TA_HLNG|TA_ACT, 0, TASK2, 1, 128, NULL});
各タスク関数では次のように「wai_sem」と「sig_sem」を実施しながらシリアル通信で動作ログをパソコンに送信します。
//------------------------------------------------
// 概 要:MAINタスク
//------------------------------------------------
void MAIN(VP_INT exinf)
{
while(1)
{
Sci_putString("MAIN wai_sem()\n");
wai_sem(SEMID_SEM1);
Sci_putString("MAIN SEM get!\n");
dly_tsk(10000);
Sci_putString("MAIN sig_sem()\n");
sig_sem(SEMID_SEM1);
dly_tsk(1000);
}
return;
}
//------------------------------------------------
// 概 要:練習用TASK1関数
//------------------------------------------------
void TASK1(VP_INT exinf)
{
while(1)
{
Sci_putString("TASK1 wai_sem()\n");
wai_sem(SEMID_SEM1);
Sci_putString("TASK1 SEM get!\n");
dly_tsk(10000);
Sci_putString("TASK1 sig_sem()\n");
sig_sem(SEMID_SEM1);
dly_tsk(1000);
}
return;
}
//------------------------------------------------
// 概 要:練習用TASK2関数
//------------------------------------------------
void TASK2(VP_INT exinf)
{
while(1)
{
Sci_putString("TASK2 wai_sem()\n");
wai_sem(SEMID_SEM1);
Sci_putString("TASK2 SEM get!\n");
dly_tsk(10000);
Sci_putString("TASK2 sig_sem()\n");
sig_sem(SEMID_SEM1);
dly_tsk(1000);
}
return;
}
実行して、パソコン側のTeraTermでログを受信すると定期的に「タスク名 wai_sem()」のログが出力されて停止する様子が見えるでしょう。
MAIN wai_sem()
TASK1 sig_sem()
MAIN SEM get!
TASK1 wai_sem()
TASK2 sig_sem()
TASK1 SEM get!
TASK2 wai_sem()
次のように、「wai_sem」にて停止しているタスクは、別タスクからの「sig_sem」により資源獲得ができているのがわかりますね。
MAIN wai_sem() MAINタスクが獲得待ち状態へ
TASK1 sig_sem() TASK1タスクが資源を返却
MAIN SEM get! MAINタスクが資源を獲得
TASK1 wai_sem()
TASK2 sig_sem()
TASK1 SEM get!
TASK2 wai_sem()
このように複数のタスクが数が限られた資源を共有しながら使いたい場合に、待ち合わせができる仕組みを「セマフォ」は提供します。
カウンティングセマフォによるタスク間同期のまとめ
それでは「カウンティングセマフォ」の特徴をまとめます。
- セマフォはオブジェクトとして生成が必要
- セマフォは資源数を管理するオブジェクトであり、生成時に資源数を決定
- 資源数が2以上のセマフォを「カウンティングセマフォ」と呼ぶ
- セマフォ資源は「wai_sem」による獲得と「sig_sem」による返却ができる
- セマフォ資源が0の時に「wai_sem」を呼び出したタスクは「待ち状態」へとタスク状態が変化する。