トロンフォーラム

第3回 タスク間同期・通信機能1

イベントフラグとセマフォ

今回は、RTOSが提供する同期の機能について説明します。

タスク間の同期

第2回では、T-Kernelでの処理の単位となる「タスク」について説明しました。

タスクはそれぞれ別々に動作しますが、ときとして複数のタスクで連携して処理を実行させたいことがあります。例えば、あるタスクの処理は他のタスクの処理が完了してから実行したいとか、タスクAとタスクBの処理が両方とも完了してからタスクCの処理を実行したいとか、逆に、タスクAの処理が完了したらタスクBとタスクCを実行したいとか、開発するシステムによっていろいろな組み合わせが考えられます。

このような複数タスク間での連携処理を行うために、あるタスクの処理が終わるまで別のタスクの実行を待たせておくことを「同期」と呼びます。T-Kernelにはさまざまな種類の同期機能が用意されていますので、必要に応じて最適な同期機能を利用することで、効率の良いプログラムを開発できるようになっています。

図1 タスク間の連携に使う同期機能

図1 タスク間の連携に使う同期機能

イベントフラグ (event flag)

先に説明したような「あるタスクの処理は他のタスクの処理が完了してから実行したい」場合などに利用できる機能としてイベントフラグがあります。イベントフラグでは、処理完了などのイベントの意味を表すフラグを、ビットパターンで表現することによってタスク間の同期を行います。

リスト1は「タスクAの処理はタスクBの処理が完了してから実行する」ようにイベントフラグを利用してプログラミングした例です※1

※ 以下のサンプルプログラムはこちらからダウンロードできます。

tk_wai_flgがイベントフラグがセットされるのを待つ機能、tk_set_flgがイベントフラグをセットする機能です。

プログラムを動作させるとタスクAの方が先に実行を開始しますが、16行目(タスクAの中)にtk_wai_flgを入れてあるので、ここで一旦処理を停止します。その後、タスクBが(doWorkBの処理を完了した後で)27行目のtk_set_flgを実行すると、タスクAがtk_wai_flgによる停止状態(T-Kernelで言う「待ち状態」)から戻り、doWorkAを実行できるようになります。

なお、T-Kernelではイベントフラグなどを利用する場合、予めその準備をしておく必要があります。これが40行目のtk_cre_flg(イベントフラグの生成)です。T-Kernelでは、目的に応じて複数のイベントフラグを生成して利用することができます。

リスト1と同じ様に、タスクAとタスクBの処理が両方とも完了してからタスクCの処理を実行する、タスクAの処理が完了したらタスクBとタスクCを実行するという処理もイベントフラグを利用して実現できます。

この場合のプログラム例をリスト2とリスト3に示します。

※ 以下のサンプルプログラムはこちらからダウンロードできます。

※ 以下のサンプルプログラムはこちらからダウンロードできます。

ここで示した同期方法は一例にすぎません。イベントフラグを利用すると、ここで示した以外にも、さまざまな依存関係に合わせた同期の機能が実現できます。

イベントフラグの生成 tk_cre_flg (リスト1の40行目など)

イベントフラグを生成します。

パラメータにはT_CFLG型の構造体へのポインタを指定します。構造体は以下のように定義されていて、イベントフラグに関する情報を設定することができます。

必ず設定しなければならないのは flgatr と iflgptn の2つです。

リスト1の34行目では、以下の値を変数の宣言時に設定しています。

  • flgatrには、TA_TFIFO|TA_WMUL を設定しています。
    ここではtk_wai_flgでタスクが待ち状態になる場合の並び順などを設定します。
    TA_TFIFO
    : 待ち状態になる場合の並び順はFIFOにする。
    TA_WMUL
    : 複数のタスクが待ち状態になることができる。
  • iflgptnには、0 を設定しています。
    この値は、イベントフラグの初期値になります。
    リスト1からリスト3ではいずれも 0 を設定していますので、
    全てのフラグがクリアされた状態になっています。

なお、イベントフラグの生成に成功すると、tk_cre_flgの戻値として、生成したイベントフラグの識別子(イベントフラグID)が返されます※2

イベントフラグ待ち tk_wai_flg (リスト1の16行目など)

イベントフラグがセットされるのを待ちます。

第1引数にはイベントフラグIDを指定します。

第2引数には待ちのモードを指定します。
待ちのモードとしてはTWF_ANDW (AND待ち)、またはTWF_ORW(OR待ち)を指定することができます。

  • TWF_ANDW
    指定したフラグが全部セットされるのを待つ
  • TWF_ORW
    指定したフラグのいずれかがセットされるのを待つ

また、tk_wai_flgによる待ち条件が成立した場合のフラグの操作用に以下のモードを指定することができます。

  • TWF_CLR
    条件が満足されてこのタスクが待ち解除となった場合、イベントフラグの全部のビットが0にクリアされる。
  • TWF_BITCLR
    条件が満足されてこのタスクが待ち解除となった場合、イベントフラグの待ち解除条件に一致したビットのみが0クリアされる。

これらの指定が無い場合は、条件が満足されてこのタスクが待ち解除となった場合にも、イベントフラグの値は変化しません。

第3引数にはタイムアウト時間を指定します。一定時間待ってもイベントフラグの待ちが成立しない場合、タイムアウトエラーが発生してtk_wai_flgが終了します。リスト1,2,3では待ち時間(数値)ではなくTMO_FEVRが指定されています。この場合、タイムアウトは発生しません。

イベントフラグのセット tk_set_flg (リスト1の27行目など)

イベントフラグをセットします。

第1引数にはイベントフラグIDを指定します。

第2引数にはセットするイベントフラグのパターンを指定します。1が指定されているビットがセットされるようになっています。

なお、今回のリスト1,2,3では利用していませんが、イベントフラグをクリアする機能(tk_clr_flg)もあります。

tk_clr_flgを利用すると、特定のフラグ(ビット)をクリアすることができます。

排他制御

T-KernelのようなリアルタイムOSにおいて、同期とともによく使う機能として「排他制御」があります。

タスクは見かけ上並列に実行されていますので、複数のタスクが同時に同じ資源(共有変数など)にアクセスして何らかの処理を実行しようとした場合、複数のタスクによる処理が競合することにより、正常な結果が得られない場合があります。このような現象を防止する機構が「排他制御」で、T-Kernelではセマフォとミューテックス※3を提供しています。

例えば、あるシステムでは3つのタスクがそれぞれ別の処理を実行していて、システム全体としてはタスクが処理を完了した回数をカウントするカウンタ(変数)が1つ用意されているものとします。

図2 3つのタスクで1つのカウンタを使う

図2 3つのタスクで1つのカウンタを使う

この場合、各タスクは例えばリスト4のようにプログラミングすることができます。

他のタスク(タスクBとタスクC)についても同じようなプログラムにすれば、一見すると正しく動作するように見えます。しかし、実際には以下のようなケースでうまく動作しません。

例えば、タスクBのcounter++の部分を実行中に、タスクAが実行状態になって、タスクBの処理に割り込む形でcounter++を実行してしまうと、カウントの処理が狂ってしまいます。

そのからくりはこうです。

counter++は、C言語では1行で書けますが、counterの変数がメモリに置かれている場合、CPUから見た処理手順は以下のようになります。

【counter++の処理手順】
(1) 変数(counter)から現在の値を読み出す。
(2) 読み出した値に1を加算する。
(3) 新しい値を変数(counter)に書き込む。

ちなみに、この処理をARMのアセンブリ言語で書くと、例えばリスト5のようになります。

タスクBが(2)まで実行した後で、より優先度の高いタスクAが実行状態になってcounter++を実行したとします。その場合、タスクBの処理は一旦中断され、タスクAが(1)から(3)の処理を先に実行してcounterの値を更新します。その後、タスクBが(2)から処理を再開してcounterの値を更新します。つまり、タスクAが書き込んだ新しいcounterの値が、タスクBの(3)の処理によって上書きされ、タスクAによる更新結果が消えてしまったことになります。

このような現象が発生しないようにするためには、(1)から(3)の処理を連続して(他のタスクに邪魔されずに、すなわち排他的に)実行できるようにプログラミングする必要があります。このような動作は、「排他制御」の機能によって実現します。

セマフォ (Semaphore)

排他制御には、いろいろな実現方法がありますが、代表的なものがセマフォを利用する方法です。

リスト6にセマフォを使った排他制御を追加した例を示します。

※ 以下のサンプルプログラムはこちらこちらからダウンロードできます。

リスト6のcounter++のように、複数のタスクで1つの変数(資源)を操作するような場合に、その部分をtk_wai_semとtk_sig_semで囲みます。

セマフォは、使用されていない資源の有無や数量を数値で表現することにより、その資源を使用する際の排他制御や同期を行うための機能です。

tk_wai_semでは、セマフォ資源を獲得しようとします。セマフォ資源を獲得できれば、セマフォ資源を減算したうえで処理を継続しますが、セマフォ資源を獲得できなければ、タスクを待ち状態に移行させます。

tk_sig_semでは、セマフォ資源を返却します。この時、セマフォ資源獲得待ち状態のタスクがあればセマフォ資源を割り当ててそのタスクの待ち状態を解除します。

各タスクから見た場合、この時の待ち状態とは、tk_wai_semという関数から戻ってこないことになりますので、結果としてcounter++の手前で実行が保留されていることになります。

さて、セマフォ資源を獲得していたタスクが、counter++を実行した後で、tk_sig_semを発行するとセマフォ資源がセマフォに返却されます。すると、tk_wai_semを発行してセマフォ資源獲得待ち状態になっていたタスクがあればセマフォ資源を獲得してその待ち状態が解除され、tk_wai_semに続く処理を実行できるようになります。

かくして、先に示したcounter++の処理手順にある(1)から(3)は、各タスク毎に連続して実行されることになり、正常にカウントされるようになりました。

このようにセマフォを使うことで、共通に利用する資源(リスト6の場合はcounterという変数)に対する排他制御を行うことが可能になります。

このプログラムであれば、tk_wai_semからtk_sig_semの間は他のタスクに邪魔されることなく実行できますので、counter++のような簡単な処理だけでなく、もっと複雑な処理も記述できます。

セマフォの生成 tk_cre_sem (リスト6の57行目)

tk_cre_semではセマフォを生成します。セマフォやイベントフラグは目的に応じて複数生成して利用することができます。

パラメータにはT_CSEM型の構造体へのポインタを指定します。構造体は以下のように定義されていて、セマフォに関する情報を設定することができます。

必ず設定しなければならないのは sematr, isemcnt, maxsem の3つです。

リスト6では以下の値を変数の宣言時に設定しています(47行目)。

  • sematrには、TA_TFIFO|TA_FIRST を設定しています。
    ここではtk_wai_semでタスクが待ち状態になる場合の並び順などを設定します。
    TA_TFIFO
    : 待ち状態になる場合の並び順はFIFOにする。
    TA_FIRST
    : セマフォ資源の獲得は待ち行列先頭のタスクを優先する。
  • isemcntには、1 を設定しています。
    この値は、セマフォの初期値になります。
    リスト6では 1 を設定していますので、最初に実行されたtk_wai_semはそのまま処理を継続できます。
  • maxsemには、1 を設定しています。
    この値は、セマフォの最大値になります。
    リスト6ではtk_wai_semとtk_sig_semを対にして使っていますが、 tk_wai_semとtk_sig_semは任意の箇所で発行できます※4
    この時、tk_sig_semをたくさん発行してしまったなどの誤りを検出できるように最大値を指定できるようになっています。

なお、セマフォの生成に成功すると、tk_cre_semの戻値として、生成したセマフォの識別子(セマフォID)が返されます※2

セマフォ資源獲得 tk_wai_sem (リスト6の16行目27行目38行目)

tk_wai_semでは、セマフォ資源を獲得します。

第1引数には、セマフォIDを指定します。

第2引数には獲得するセマフォの資源数を指定します。資源が獲得できれば、セマフォの資源数は指定された分だけ減算され、tk_wai_semを発行したタスクは待ち状態に入らずに実行を継続します。もし、資源が獲得できなければ、tk_wai_semを発行したタスクはセマフォ資源が獲得できるようになるまで待ち状態に入ります。

リスト6では1個のセマフォ資源を獲得しようとしますが、上記で説明したとおり、一度に複数のセマフォ資源を獲得することも可能で、より複雑な条件に合わせた排他制御を実現することもできます。

セマフォ資源返却 tk_sig_sem (リスト6の18行目29行目40行目)

セマフォ資源を返却します。

第1引数にはセマフォIDを指定します。

第2引数には返却するセマフォの資源数を指定します。返却先のセマフォでセマフォ資源の獲得待ち状態になっているタスクがあれば、要求カウントを確認して可能であれば資源を割り当てます。資源を割り当てられたタスクはセマフォ資源の獲得待ち状態が解除されます。


※1 doWorkAやdoWorkBでは、各タスク用の処理を行うものとし、必要に応じて待ちを発生することがあるものとします。各タスクではdoWorkAやdoWorkB以外にもさまざまな処理を行うと思いますが、リストでは簡単にするために省略してあります。

※2 もし、何らかのエラーが発生した場合、戻値は負の値になります。T-Kernelでは、この負の値を「エラーコード」と呼び、エラーの内容をその値で確認することができます。

※3 ミューテックスについては、第5回で説明します。

※4 tk_wai_semは待ちが発生することがありますので、割込みハンドラなどからは発行できません。

Return Top