2-1. μT-Kernel 3.0の基本
ソフトウェアの構成
μT-Kernel 3.0をOSとしたソフトウェアの構成を図2-1-1に示します。
ソフトウェアは大きくアプリケーションとシステムソフトウェアに分かれます。
アプリケーションは特定の用途に必要となる機能を実現するためのプログラムであり、組込みシステムの場合も、その製品特有の機能を実現するのはアプリケーションです。
システムソフトウェアは、アプリケーションをマイコン上で実行するための、OSを中心としたプログラムです。
システムソフトウェアは以下から構成されます。
- OS
システムソフトウェアの中心となるプログラムです。本記事ではμT-Kernel 3.0がこれに該当します。 - デバイスドライバ
デバイスドライバは、I/Oデバイスを制御するソフトウェアであり、I/Oデバイスごとに存在します。μT-Kernel 3.0のデバイス管理機能のAPIを用いて、アプリケーションからデバイスドライバを操作することができます。 - サブシステム
サブシステムはOSの機能を拡張するためのプログラムです。サブシステムの主な目的はミドルウェアの実装です。μT-Kernel 3.0のサブシステム管理機能のAPIを用いて、アプリケーションからサブシステムを操作することができます。
APIとデータ型
API(Application Programming Interface)はアプリケーションからシステムソフトウェアの機能を呼び出すためのインタフェースです。
μT-Kernel 3.0のAPIは、C言語の関数として定義されています。APIの多くはシステムコールとよばれる、以下の形式の名前を持つC言語関数です。
tk_<操作>_<操作対象>
たとえば、タスクを生成するAPIは「tk_cre_tsk」というC言語の関数です。「cre」が生成(create)の操作を表し、「tsk」が操作対象であるタスク(task) を表しています。
APIには、システムコールのほかにライブラリ関数も存在します。ライブラリ関数は名前の最初に「tk」が無いので区別できます。両者の違いは、システムコールはOSの内部で実行されるのに対し、ライブラリ関数はタスクの一部として実行される点です。ライブラリ関数はハードウェア・レベルの低水準な制御などに使用されます。
μT-Kernel 3.0では、APIの引数や戻り値などに、μT-Kernel 3.0で独自に定義されたデータ型を使用します。
μT-Kernel 3.0で定義された主な整数データの型を表2-1-1に示します。
データ型名 | 意味 |
---|---|
B | 符号付き 8ビット整数 |
H | 符号付き 16ビット整数 |
W | 符号付き 32ビット整数 |
D | 符号付き 64ビット整数 |
UB | 符号無し 8ビット整数 |
UH | 符号無し 16ビット整数 |
UW | 符号無し 32ビット整数 |
UD | 符号無し 64ビット整数 |
INT | 符号付き整数(サイズはCPUに依存) |
UINT | 符号無し整数(サイズはCPUに依存) |
整数データ以外に、特定の意味を持つデータ型も定義されます。μT-Kernel 3.0で定義された整数以外の主なデータ型を表2-1-2に示します。
データ型名 | 意味 |
---|---|
ID | カーネル・オブジェクトのID番号 |
ATR | カーネル・オブジェクトの属性 |
ER | エラーコード |
PRI | 優先度 |
TMO | タイムアウト時間 |
SZ | サイズ |
BOOL | ブール値(TRUE:真、FALSE:偽) |
μT-Kernel 3.0のデータ型の中で、ER型はエラーコードを示します。APIの多くは戻り値としてエラーコードを返します。
エラーコードは負の値の整数です。ただし、エラーが発生しなかったことを示すE_OKの値は0です。つまり、エラーコードを返すAPIの戻り値が負の場合は、API実行中にエラーが発生したと判断することができます。
カーネル・オブジェクト
μT-Kernel 3.0では操作対象をオブジェクトとよびます。オブジェクトという名称は様々な意味で使われますので、本記事では区別するためにカーネル・オブジェクトとよぶこととします。
プログラムの実行単位であるタスクも、カーネル・オブジェクトの一つです。また、この後に説明するタスク間の通信や同期にも各種のカーネル・オブジェクトが利用されます。表2-1-3にμT-Kernel 3.0のカーネル・オブジェクトの一覧を示します。表中の略称はAPIなどで使用されるものです。
機能分類 | 名称 | 略称 |
---|---|---|
タスク | タスク | tsk |
同期・通信 | セマフォ | sem |
ミューテックス | mtx | |
イベントフラグ | flg | |
メッセージバッファ | mbf | |
メールボックス | mbx | |
メモリ管理 | 可変長メモリプール | mpl |
固定長メモリプール | mpl | |
タイムイベント | アラームハンドラ | alm |
周期ハンドラ | cyc |
カーネル・オブジェクトには以下の共通の規則があります。
- カーネル・オブジェクトはAPIにより生成・削除される
カーネル・オブジェクトは必ずAPIによって生成されます。つまり、カーネル・オブジェクトを使用するにはまずAPIを使って生成しなくてはなりません。また、使用の終わったカーネル・オブジェクトはAPIを使って削除します。
カーネル・オブジェクトを生成するAPIは、tk_cre_XXX、削除するAPIはtk_del_XXXの形式の名称です。XXXの部分に対象とするカーネル・オブジェクトの略称が入ります。 - カーネル・オブジェクトはID番号により管理される
カーネル・オブジェクトが生成されると、ID番号が割り当てられます。ID番号はカーネル・オブジェクトを生成するAPIの戻り値として返ってきます。ID番号は自動的に割り当てられますので、特定の値を指定することはできません。
カーネル・オブジェクトをAPIで操作するには、ID番号を使って対象となるカーネル・オブジェクトを指定します。
2-2. タスクの基本
タスクとタスク管理機能
タスクはプログラムの基本的な実行単位です。μT-Kernel 3.0上で実行されるプログラムは、基本的には複数のタスクの集合であり、アプリケーションもまた複数のタスクの集合といえます。ただし、タスク以外にハンドラとよばれる実行単位も存在します。ハンドラについては後の項で説明します。
μT-Kernel 3.0では、APIによってタスクに関する各種の操作を行うことができます。これらの機能をタスク管理機能とよんでいます。タスク管理機能の主なAPIを表2-2-1に示します。
API名 | 機能 |
---|---|
tk_cre_tsk | タスクの生成 |
tk_sta_tsk | タスクの起動(タスクの動作開始) |
tk_ext_tsk | 自タスクの終了 |
tk_del_tsk | タスクの削除 |
tk_exd_tsk | 自タスクの終了と削除 |
初期タスクとusermain関数
タスクは、プログラムからAPIを使って生成し、実行開始します。ただし、プログラムで最初に実行されるタスクだけはμT-Kernel 3.0が生成して実行開始します。
この最初のタスクは初期タスクとよばれます。初期タスクはμT-Kernel3.0の起動時に生成・実行され、以下のように動作します。
-
システムソフトウェアの初期化処理
デバイスドライバの登録など、システムソフトウェアの初期化処理を実行します。 -
アプリケーションの実行
アプリケーションのメイン関数を実行し、アプリケーションを開始します。アプリケーションのメイン関数はusermainという名称の関数です。usermain関数の内容は、アプリケーションに応じて自由に記述することができます。
usermain関数は、アプリケーションで使用するタスクの生成、実行、その他のカーネル・オブジェクトの生成など、アプリケーションの初期化処理を行います。 -
システムソフトウェアの終了処理
アプリケーションのメイン関数(usermain関数)が終了すると、システムソフトウェアの終了処理を行います。μT-Kernel 3.0の動作も終了します。
注意すべき点は、usermain関数が終了すると、他のタスクなどが実行中であっても、システムソフトウェア全体が終了してしまうことです。このため、アプリケーションの実行中にusermain関数が終了しないようにしなければなりません。
タスクの優先度
タスクを生成する際にそのタスクの優先度の初期値を指定します。タスク生成後はAPIによって優先度を変更することもできます。
タスクの優先度は、タスクの優先度スケジューリングで使用される値です。タスクの優先度は、1から始まる正の整数です。最大値はμT-Kernel 3.0のビルド時に決めることができますが、16以上であることが仕様で定められています。
タスクの優先度は値が小さいほど優先度は高くなります。つまり、優先度1が最も高い優先度です。
タスクの属性
タスクの属性は、そのタスクの性質を表します。タスクの生成時に指定し、生成後は変更することはできません。
タスクの属性には表2-2-2に示すように様々なものがあり、複数を同時に指定することもできます。
一般的なアプリケーションのタスクで指定される属性は、TA_HLNG属性とTA_RNG3属性の二つです。TA_HLNG属性はそのタスクがC言語で記述されていることを示します。TA_RNG3属性はそのタスクが保護レベル3(アプリケーションの保護レベル)で実行されることを示します。
属性名 | 意味 | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
TA_HLNG | 対象タスクは高級言語(C言語)で記述されている | ||||||||||
TA_ASM | 対象タスクはアセンブリ言語で記述されている | ||||||||||
TA_RNGn |
対象タスクを保護レベルnで実行する nは0~3の値であり、以下の意味がある
|
||||||||||
TA_SSTKSZ | システムスタックのサイズを指定する | ||||||||||
TA_USERSTACK | ユーザスタックの領域を指定する | ||||||||||
TA_USERBUF | ユーザが指定した領域をスタックに使用する | ||||||||||
TA_DSNAME | デバッグ用のオブジェクト名を使用する | ||||||||||
TA_FPU | FPU(浮動小数点演算ユニット)を使用する | ||||||||||
TA_COPn | コプロセッサnを使用する nの値の範囲はマイコンの仕様に従って決まる |
タスクの状態
タスクはそれぞれ状態を持ち、APIによって状態を変化させながら実行していきます。
タスクの状態の中で重要なものは「休止状態」、「実行状態」、「実行可能状態」、「待ち状態」です。このうち、実行状態と実行可能状態を総称して「実行できる状態」とよびます。タスクの主な状態とその遷移を図2-2-1に示します。
タスクの主な状態について以下に説明します。
-
休止状態(DORMANT)
タスクがまだ実行されていないか、もしくは実行が終了して、停止している状態です。この状態のタスクのコードは実行されません。タスクが生成された直後は「休止状態」です。「休止状態」のタスクは、タスクの起動API(tk_sta_tsk)によって、「実行できる状態」に遷移します。 -
実行できる状態
タスクを実行する準備ができている状態です。ただし、実際に実行できるタスクは同時には一つだけです。よって、「実行できる状態」のタスクの中で一番に優先度の高いタスクが「実行状態」となります。同じ優先度のタスクが複数ある場合は、先に「実行できる状態」となったタスクが「実行状態」となります。-
実行状態(RUNNING)
タスクが実行中の状態です。ある一時において実行状態のタスクはただ一つです。 -
実行可能状態(READY)
タスクが実行する準備はできてますが、より優先度の高いタスクが実行状態のために、実行されるのを待っている状態です。
「実行できる状態のタスク」の中から優先度に従って実行すべきタスクを決め、「実行状態」とする動作のことをディスパッチといいます。逆に、「実行状態」のタスクを一時中断して実行可能状態にする動作を「プリエンプト」といいます。
「実行状態」のタスクは、自タスクの終了API(tk_ext_tsk)によって、「休止状態」に遷移します。また、各種のAPIにより待ち状態に遷移します。
-
実行状態(RUNNING)
-
待ち状態(WAITING)
何らかの条件の成立を待って、タスクが実行を一時中断している状態です。待ちの条件には様々なものがあります。具体的な例は以降で説明していきます。
タスクの待ち条件が成立すると「実行できる状態」に遷移します。
タスクの状態にはこのほかに、タスクが生成される前の仮想的な状態である「未登録状態」と、「強制待ち状態」があります。強制待ちはアプリケーションでは使用を禁止されていますので、本記事では扱いません。
2-3. タスクの基本的な同期
タスクの同期とは
タスクの同期とは、複数のタスクの間で時間的な動作のタイミングを合わせることです。μT-Kernel 3.0ではマルチタスクの機能によって複数のタスクが同時に並行実行されますが、これらのタスクの間に何らかの依存関係がある場合には、タスクの同期が必要となります。
μT-Kernel 3.0はタスクの同期を実現するために様々な機能をもっています。そのうち、タスクに対する直接的な操作によってタスク間の同期を行う機能をタスク付属同期機能とよびます。タスク付属同期機能の主なAPIを表2-3-1に示します。
API名 | 機能 |
---|---|
tk_dly_tsk | タスクの遅延(タスクの動作の一時停止) |
tk_wup_tsk | タスクの起床要求 |
tk_slp_tsk | タスクの起床待ち |
タスクの動作の一時停止
マルチタスクの実行環境では、タスクが自分の必要な処理を終えたら、速やかに他のタスクが動作できるようにすることが重要です。
このような場合、タスクの遅延API(tk_dly_tsk)を呼び出し、一時的にタスクの動作を停止して他のタスクに実行の権利を譲ることができます。APIを呼び出したタスクは指定した時間の経過待ちの状態となります。
タスクの起床
タスクの処理の順序を同期させたい場合は、タスクの起床要求API(tk_wup_tsk)とタスクの起床待ちAPI(tk_slp_tsk)を使用するとこれを実現できます。
タスクの起床を使った同期の方法を以下に示します。
後から処理を実行するタスク
後から処理を実行するタスクは、その処理の前にタスクの起床待ちAPI(tk_slp_tsk)を呼び出し、起床待ち状態に遷移します。他のタスクから起床されるまで待ち状態は続きます。
先に処理を実行するタスク
先に処理を実行するタスクは、その処理を実行し終えたら、タスクの起床要求API(tk_wup_tsk)を呼び出し、待っているタスクを起床します。
タスクの起床待ちAPIとタスクの起床要求APIには、呼び出される順番の制約はありません。
タスクの起床待ちAPIを呼び出した際に、すでにタスクの起床要求APIが呼び出されていた場合は、待ち状態に遷移せずに実行が継続されます。
また、起床要求の回数はカウントされます。たとえば、起床待ちAPIを呼び出す前に、起床要求APIを2回呼び出したとすると、2回の起床待ちAPIが起床待ち状態とならずに実行が継続されます。つまり、タスクの起床APIとタスクの起床待ちAPIは一対一で対応します。
タスクの起床による同期の流れ
タスクの起床要求API(tk_wup_tsk)とタスク起床待ちAPI(tk_slp_tsk)による同期の流れを、以下に例をあげて説明します。
二つのタスクTASK-A、TASK-Bがあり、TASK-Aの処理のあとにTASK-Bの処理を実行したい場合を考えます。TASK-AとTASK-Bは以下のようにAPIを使用して同期をとります。
TASK-Aの動作
TASK-Aは処理の終了後に、タスク起床API(tk_wup_tsk)を呼び出し、TASK-Bに起床を要求します。
TASK-Bの動作
TASK-Bは処理の前に、タスク起床待ちAPI(tk_slp_tsk)を呼び出し、TASK-Aから起床が要求されるまで起床待ち状態となって動作を一時中断します。
TASK-AとTASK-Bのどちらが先にAPIを呼び出すかにかかわらず、どちらの場合でも以下の様にTASK-Aの処理の後にTASK-Bの処理が実行されるようになります。
まずTASK-Bが先にタスク起床待ちAPI(tk_slp_tsk)を呼び出した場合を図2-3-1に示します。
- 最初にTASK-Bが実行状態、TASK-Aが実行可能状態とします(優先度はTASK-Bが高いとします)。
- TASK-Bはタスクの起床待ちAPI(tk_slp_tsk)を呼び出して起床待ち状態に遷移します。
- TASK-Aが実行状態になります。
- TASK-Aはタスクの起床要求API(tk_wup_tsk)を呼び出し、TASK-Bに起床要求を行います。
- TASK-Bは起床待ちの条件が成立したので実行状態となります。
次に、TASK-Aが先にタスクの起床要求API(tk_wup_tsk)を呼び出した場合を図2-3-2に示します。
図2-3-2の流れを説明します。
- 最初にTASK-Aが実行状態、TASK-Bが実行可能状態とします(優先度はTASK-Aが高いとします)。
- TASK-Aはタスクの起床要求API(tk_wup_tsk)を呼び出し、TASK-Bに起床要求を行います。TASK-Aは実行状態のままですので、TASK-Bの実行可能状態も継続します。
- TASK-Aが実行を終えTASK-Bが実行状態になります。
- TASK-Bがタスクの起床待ちAPI(tk_slp_tsk)を呼び出します。この時、すでにTASK-Aによる起床要求が行われていますので、起床待ちの条件は成立し、TASK-Bは実行を継続します。
2-4. イベントフラグ
イベントフラグとは
イベントフラグは、主にタスク間の同期を制御するために使用されるμT-Kernel 3.0のカーネル・オブジェクトです。
イベントとは、有・無で表現できるプログラム上の何らかの情報です。たとえば、「データの準備ができた」や「ボタンが押された」などがイベントの例です。
イベントの有・無は1ビットのデータで表現することができます。このイベントの有・無を表現するビットの集まりがイベントフラグです。イベントフラグは、前項で説明したタスクの起床による同期に比べて、より多くの情報を伝えることができ、また複数のタスク間で同期を行うことができます。
イベントフラグはμT-Kernel 3.0のAPIで操作します。イベントフラグを制御する主なAPIを表1に示します。
API名 | 機能 |
---|---|
tk_cre_flg | イベントフラグの生成 |
tk_set_flg | イベントフラグのセット |
tk_clr_flg | イベントフラグのクリア |
tk_wai_flg | イベントフラグ待ち |
tk_del_flg | イベントフラグの削除 |
イベントフラグの操作
イベントフラグは、1ビットのデータの集まりです。1つのイベントフラグのビットの数は、そのマイコンのUINT型のビットサイズとなっていますので、C言語のint型のサイズと一致します。
イベントフラグの個々のビットに対し、APIによりセット、クリアの操作ができます。セットはビットを1にすることであり、クリアはビットを0にすることです。また、APIによりイベントフラグの特定のビットがセットされるまで、タスクを待ち状態にすることができます(図2-4-1)。
イベントフラグを使ったタスク間の同期の基本的な操作手順を以下に示します。
- 最初にイベントフラグの生成API(tk_cre_flg)でイベントフラグを生成します。
- イベントを知らせるほうのタスクは、イベントが発生したならば、イベントフラグのセットAPI(tk_set_flg)を呼び出すことによって、対応するイベントフラグのビットをセットします。一つのAPIで同時に複数のビットをセットすることができます。
- イベントを待つほうのタスクは、イベントフラグの待ちAPI(tk_wai_flg)を呼び出すことによって、イベントの発生を待ちます。指定したビットがセットされるまで、タスクはイベントフラグの待ち状態となり、動作を一時中断します。すでに指定したビットがセットされていた場合は、待ち状態にはならずに動作が継続されます。
なお、ビットのセットを待つことはできますが、ビットのクリアを待つことはできません。 - イベントを待つほうのタスクは、イベントの待ちが解除されたら、そのイベントフラグのビットをクリアし、次のイベントが受け取れるようにします。イベントフラグをクリアするには、イベントフラグのクリアAPI(tk_clr_flg)を呼び出すか、またはAPIの指定によりイベントフラグの待ちが解除されたときに自動的にクリアすることもできます。
複数のイベント待ち
イベントフラグの待ちAPI(tk_wai_flg)では、一つのイベントフラグの中の複数のビットを同時に待つことができます。その際、待ち条件として以下を選択できます。
- AND待ち 指定したビットがすべてセットされるまで待つ
- OR待ち 指定したビットのいずれかがセットさせるまで待つ
なお、AND待ちとOR待ちを組み合わせることはできません。
イベントフラグによる複数のタスクの同期
イベントフラグによるタスク間の同期は、一対一のタスク間だけではなく、複数のタスク間で行うことができます。
イベントフラグのセットは、特定のタスクだけではなく、複数のタスクから行うことができます。
一方、イベントフラグを待つタスクは、イベントフラグ生成時の属性により、複数のタスクの待ちを許すか否かが決められます。
TA_WMUL属性のイベントフラグでは、複数のタスクが同時に待ち状態となることができます。つまり、一つのイベントの発生を複数のタスクが待つことができます(図2-4-2-a)。
TA_WSGL属性のイベントフラグでは、複数のタスクが同時に待ち状態になることを許しません。すでに待ち状態のタスクが存在するのに、イベントフラグの待ちAPI(tk_wai_flg)を呼び出した場合はエラーとなります(図2-4-2-b)。
2-5. セマフォとミューテックス
セマフォとは
セマフォは、主に共有する資源の排他制御のために使用されるμT-Kernel 3.0のカーネル・オブジェクトです。
セマフォは、資源の有無や数量を表す資源数という値を持っています。資源数とは、その資源を同時に使用できるタスクの数と考えれば良いでしょう。それぞれのセマフォには、生成時に資源数の値が設定されます。
多くの場合、セマフォの資源数は1です。つまり、その資源は同時に一つのタスクしか使用できません。資源数が1のセマフォはバイナリセマフォとよばれます。
資源数が1ではない、つまり同時に複数のタスクから使用可能なセマフォは計数セマフォとよばれます。計数セマフォが使われるケースはあまり多くありませんが、例として、外部との通信のコネクションなどがあります。
セマフォはμT-Kernel 3.0のAPIで操作します。セマフォを制御する主なAPIを表2-5-1に示します。
API名 | 機能 |
---|---|
tk_cre_sem | セマフォの生成 |
tk_wai_sem | セマフォ資源獲得 |
tk_sig_sem | セマフォ資源返却 |
tk_del_sem | セマフォの削除 |
セマフォによる排他制御
セマフォを使って共有する資源の排他制御を行う際の手順を以下に示します。
- セマフォの生成API(tk_cre_sem)でセマフォを生成します。原則として、セマフォと共有する資源は一対一で対応させるようにします。つまり、一つの共有資源に対して一つのセマフォを生成します。
- タスクは、共有する資源を使用する前に、セマフォの資源獲得API(tk_wai_sem)を呼び出し、セマフォから資源を獲得します。資源が獲得できれば、タスクはその資源を使用できます。もし、すでに他のタスクが同じ資源を獲得済みであれば、タスクはセマフォの資源待ち状態となり、動作を一時中断します。
- タスクは、獲得した共有資源の使用が終わったら、セマフォの資源返却API(tk_sig_sem)を呼び出し、セマフォへ資源を返却します。もし、他のタスクがそのセマフォに対してセマフォの資源待ち状態であれば、そのタスクの待ち状態を解除します。待ち状態を解除されたタスクは、資源を獲得します。
セマフォの資源獲得をしたタスクは、資源を共有する他のタスクが資源待ち状態で必要以上に長く動作できないのを防ぐために、できる限り速やかに資源の使用を終えて返却することが重要です。
セマフォによる排他制御の例
セマフォによるタスク間の排他制御の動作について、例をあげて説明します。
二つのタスクTASK-A、TASK-Bと、タスク間で共有する資源が一つあるとします。
TASK-A、TASK-Bの共有する資源の排他制御は以下のように行います(図2-5-1)。
- 二つのタスクTASK-AとTASK-Bが存在し、TASK-Bのみが実行状態です。タスクの優先度はTASK-Aのほうが高いとします。
- TASK-Bがセマフォの資源獲得API(tk_wai_sem)を呼び出し、資源を獲得します。
- TASK-Aが実行状態となり、優先度の低いTASK-Bは実行可能状態になります。
- TASK-Aがセマフォの資源獲得API(tk_wai_sem)を呼び出します。
- 資源はすでにTASK-Bが獲得しているので、TASK-Aは資源獲得ができずに、資源獲得待ち状態になります。そこでTASK-Bが再び実行状態になります。
- TASK-Bがセマフォの資源返却API(tk_sig_sem)を呼び出し、資源を返却します。
- 資源が返却されたので、TASK-Aは資源を獲得し実行状態となります。優先度の低いTASK-Bは実行可能状態になります。
ミューテックスとは
ミューテックスは、主にタスクのクリティカルセクションの排他制御のために使用されるμT-Kernel 3.0のカーネル・オブジェクトです。
クリティカルセクションとは、複数のタスクによる処理が同時に実行されると問題が生じる実行区間のことです。
ミューテックスの機能はセマフォと似ています。ただ、セマフォが共有する資源全般の排他制御を対象としているのに対し、ミューテックスはタスクのクリティカルセクションの排他制御に特化しています。
ミューテックスはμT-Kernel 3.0のAPIで操作します。ミューテックスを制御する主なAPIを表2-5-2に示します。
API名 | 機能 |
---|---|
tk_cre_mtx | ミューテックスの生成 |
tk_loc_mtx | ミューテックスのロック |
tk_unl_mtx | ミューテックスのアンロック |
tk_del_mtax | ミューテックスの削除 |
ミューテックスによる排他制御
ミューテックスを使ったクリティカルセクションの排他制御の手順を以下に示します。
- 排他制御したいクリティカルセクションに対応してミューテックスを生成します。
- タスクはクリティカルセクションを実行する前に、ミューテックスのロックAPI(tk_loc_mtx)を呼び出し、ミューテックスをロックします。もし、すでに他のタスクがミューテックスをロックしていれば、タスクはミューテックスのアンロック待ち状態となり、動作を一時中断します。
- タスクはクリティカルセクションの実行が終わったら、ミューテックスのアンロックAPI(tk_unl_mtx)を呼び出し、ミューテックスをアンロックします。もし、他のタスクがそのミューテックスに対してアンロック待ち状態であれば、その待ち状態を解除します。待ち状態を解除されたタスクは、そのミューテックスをロックします。
ミューテックスをロックしたタスクは、他のミューテックスを共有するタスクがロック待ち状態で必要以上に長く動作できないのを防ぐために、できる限り速やかにクリティカルセクションの実行を終えてアンロックすることが重要です
ミューテックスとセマフォの相違点
ミューテックスとセマフォの機能は類似していますが、以下の点で異なります。
- ミューテックスには資源数の設定がありません。クリティカルセクションのセマフォは、一つのタスクのみの実行を許します。この機能はバイナリセマフォに相当するものであり、ミューテックスには計数セマフォに相当する機能がありません。
- ミューテックスはロックしたタスクと強く紐づけられます。ミューテックスをアンロックできるのは、ロックしたタスクのみです。また、ロックしたタスクが、ロックしたまま終了した場合、自動的にアンロックされます。一方、セマフォの場合は、タスクとの間にこのような紐づけはなく、資源を獲得したタスクと資源を返却するタスクが異なっていてもかまいません。
- ミューテックスでは、タスク間の排他制御において発生する優先度の逆転の問題を解決するために、ロック中のタスクの優先度を自動的に変更する機能があります。この機能はミューテックスの特徴でもあり、ミューテックスとセマフォの最も大きな相違点です。
2-6. メッセージバッファとメールボックス
メッセージバッファとは
メッセージバッファは、タスク間のデータの通信に使用されるμT-Kernel 3.0のカーネル・オブジェクトです。
メッセージバッファによりタスク間で通信されるデータをメッセージとよびます。メッセージは任意のサイズのデータであり、内容はアプリケーションで自由に決められます。
メッセージバッファの機能は、タスクから送られてきたメッセージを格納し、他のタスクへ渡すことです。メッセージバッファに複数のメッセージが格納されている場合は、格納された順番にメッセージが受け取られます。
メッセージバッファはμT-Kernel 3.0のAPIで操作します。メッセージバッファを制御する主なAPIを表2-6-1に示します。
API名 | 機能 |
---|---|
tk_cre_mbf | メッセージバッファの生成 |
tk_snd_mbf | メッセージバッファへ送信 |
tk_rcv_mbf | メッセージバッファから受信 |
tk_del_mbf | メッセージバッファの削除 |
メッセージバッファによる通信手順
メッセージバッファに対する基本の操作は、メッセージの送信と受信です(図2-6-1)。
メッセージバッファを使ったタスク間の通信の基本的な手順を以下に示します。
- メッセージバッファの生成API(tk_cre_mbf)を使用してメッセージバッファを作成します。このとき、メッセ―ジバッファのサイズと、一つのメッセージの最大サイズを指定します。メッセ―ジバッファのサイズは、メッセージを格納するメモリ領域のサイズです。このサイズを超えてメッセージを格納することはできません。
- 送信側のタスクはメッセージの送信API(tk_snd_mbf)を呼び出し、メッセージをメッセージバッファに送ります。メッセージはメッセージバッファに格納されますが、このときすでにメッセージが格納されていて、メッセージバッファに空きが無い場合は、タスクはメッセージ送信待ち状態となり、動作を一時中断します。メッセージバッファに空きがある場合は、待ち状態にはならずに動作が継続されます。
- 受信側のタスクはメッセージの受信API(tk_rcv_mbf)を呼び出し、メッセージバッファからメッセージを受け取ります。この時、メッセージバッファの中にメッセージが無い場合は、タスクはメッセージ受信待ち状態となり、動作を一時中断します。
メッセージバッファによるタスク間の通信は基本的には非同期に行われます。ただし、送信時にメッセージバッファに空きの無い場合と、受信時にメッセージバッファにメッセージが無い場合は、タスクは待ち状態となり、タスク間の同期が行われます。
メッセージバッファの特別な使い方として、メッセージバッファのサイズを0とした同期通信があります。メッセージバッファのサイズが0の場合、メッセージの格納はできませんので、送信と受信の両方で同期が取られます。
メールボックスとは
メールボックスは、タスク間のデータの通信に使用されるμT-Kernel 3.0のカーネル・オブジェクトです。
メールボックスによりタスク間で通信されるデータをメッセージとよびます。メッセージは任意のサイズのデータであり、内容はアプリケーションで自由に決められます。ただし、メールボックスのメッセージは、その先頭にOSが使用するメッセージヘッダの領域を確保しておく必要があります。
メールボックスの機能は、タスクから送られてきたメッセージを他のタスクへ渡すことです。すでに説明したメッセージバッファと類似した機能ですが、メッセージバッファがメッセージのデータそのものを送受信するのに対して、メールボックスはメッセージの先頭アドレスのみを送受信するという違いがあります。よって、メッセージを送る際には送信側タスクがメモリを確保し、受信側タスクではメッセージの使用が終わったらメモリを解放するが必要があります。μT-Kernel 3.0にはメモリの動的な管理を行うメモリプール機能があり、通常はメールボックスとメモリプールの機能を合わせて使用します。
メールボックスはμT-Kernel 3.0のAPIで操作します。メールボックスを制御する主なAPIを表2-6-2に示します。
API名 | 機能 |
---|---|
tk_cre_mbx | メールボックスの生成 |
tk_snd_mbx | メールボックスへ送信 |
tk_rcv_mbx | メールボックスから受信 |
tk_del_mbx | メールボックスの削除 |
メールボックスによる通信手順
メールボックスに対する基本の操作は、メッセージの送信と受信です。
メールボックスを使ったタスク間の通信の基本的な手順を以下に示します(図2-6-2)。
- メールボックスの生成API(tk_cre_mbf)を使用してメールボックスを作成します。
- 送信側のタスクは、送信するメッセージを用意します。メッセージの先頭アドレスを他のタスクに渡しますので、共有できるメモリ上にメッセージのメモリ領域を確保します。通常はメモリプール管理機能を用いてメッセージ用のメモリブロックを確保します。
- 送信側のタスクはメッセージの送信API(tk_snd_mbx)を呼び出し、メッセージをメールボックスに送ります。実際に送信されるのはメッセージの先頭アドレスのみです。
- 受信側のタスクはメッセージの受信API(tk_rcv_mbx)を呼び出し、メールボックスからメッセージを受け取ります。実際に受信されるのはメッセージの先頭アドレスのみです。このときメールボックスにメッセージが無い場合は、タスクはメッセージ受信待ち状態となり、動作を一時中断します。
- 受信側のタスクは、受信したメッセージを使い終わった後、そのメモリ領域を解放します。メモリプール管理機能を用いている場合はメモリブロックを返却します。
メールボックスの利点と欠点
メールボックスの利点は、メッセージの先頭アドレスのみをタスク間で転送するため、メッセージのデータをすべて転送するメッセージバッファに比べて、転送速度が速いことです。
また、メッセージがメッセージバッファのような固定的なメモリ領域に格納されるわけではないので、メッセージバッファのように空きが無くなることもありません。
一方でメールボックスの欠点は、メッセージに使用するメモリを動的に管理しなければならないことです。メッセージを送る際には送信側タスクがメモリを確保し、受信側タスクではメッセージの使用が終わったらメモリを解放するが必要があります。
メールボックスを使用する場合、メモリの動的な管理などが必要になってプログラミングの複雑度が上がりますので、メッセージの通信速度などに特に問題の無い場合は、メッセージバッファの使用を推奨します。
2-7. アラームハンドラと周期ハンドラ
アラームハンドラとは
アラームハンドラは、指定した時刻に起動されるタイムイベントハンドラです。
アラームハンドラは、μT-Kernel 3.0のカーネル・オブジェクトであり、タスクと同様のプログラムの実行単位です。アラームハンドラはすべてのタスクより優先的に実行されます。つまり、アラームハンドラが動作すると、それまで実行中だったタスクの実行は一時停止します。この動作は、後述の周期ハンドラや割込みハンドラと同様です。
アラームハンドラはμT-Kernel 3.0のAPIで操作します。アラームハンドラを操作する主なAPIを表2-7-1に示します。
API名 | 機能 |
---|---|
tk_cre_alm | アラームハンドラの生成 |
tk_sta_alm | アラームハンドラの動作開始 |
tk_stp_alm | アラームハンドラの動作停止 |
tk_del_alm | アラームハンドラの削除 |
アラームハンドラの操作
アラームハンドラの操作の手順を以下に示します(図2-7-1)。
- アラームハンドラの生成API(tk_cre_alm)を使用してアラームハンドラを生成します。
- アラームハンドラの動作開始API(tk_sta_alm)を呼び出し、アラームハンドラの起動までの時間を指定して、アラームハンドラの動作を開始します。指定した時間が経過するとアラームハンドラが起動されます。
アラームハンドラはタスクより優先的に実行されますので、実行中のタスクの動作は一時停止します。 - アラームハンドラの起動は一回のみです。ただし、再びアラームハンドラの動作開始API(tk_sta_alm)を呼び出せば、再度アラームハンドラの動作を開始することができます。
- アラームハンドラの動作開始API(tk_sta_alm)を呼び出してから、アラームハンドラが起動するまでの間に、アラームハンドラの動作停止API(tk_stp_alm)を呼び出すと、アラームハンドラは起動されなくなります。
周期ハンドラとは
周期ハンドラは、一定の周期時間で繰り返し起動されるタイムイベントハンドラです。
周期ハンドラは、μT-Kernel 3.0のカーネル・オブジェクトであり、タスクと同様のプログラムの実行単位です。周期ハンドラもアラームハンドラと同じくすべてのタスクより優先的に実行され、実行中のタスクは一時停止します。
周期ハンドラはμT-Kernel 3.0のAPIで操作します。周期ハンドラを操作する主なAPIを表2-7-2に示します。
API名 | 機能 |
---|---|
tk_cre_cyc | 周期ハンドラの生成 |
tk_sta_cyc | 周期ハンドラの動作開始 |
tk_stp_cyc | 周期ハンドラの動作停止 |
tk_del_cyc | 周期ハンドラの削除 |
周期ハンドラの基本的な動作
周期ハンドラは生成時の属性により動作が変わります。
周期ハンドラの基本的な動作と操作の手順を以下に示します(図2-7-2)。
- 周期ハンドラの生成API(tk_cre_cyc)を使用して周期ハンドラを生成します。その際に周期ハンドラが起動する周期時間間隔cyctimを設定します。
- 周期ハンドラの動作開始API(tk_sta_cyc)を呼び出し、周期ハンドラの動作を開始します。以降、周期ハンドラは生成時に設定した時間間隔cyctimで繰り返し起動されます。
周期ハンドラはタスクより優先的に実行されますので、実行中のタスクの動作は一時停止します。 - 周期ハンドラの動作停止API(tk_stp_cyc)を呼び出すと、周期ハンドラの動作は停止し、起動されなくなります。その後、再び周期ハンドラの動作開始API(tk_sta_cyc)を呼び出せば、周期ハンドラの動作は再開します。
アラームハンドラと周期ハンドラの注意点
アラームハンドラや周期ハンドラはすべてのタスクより優先的に実行されるため、ハンドラの実行中にはタスクが動作することはできません。ハンドラは速やかに処理を終え、タスクが実行できるようにする必要があります。
アラームハンドラや周期ハンドラから使用できるμT-Kernel 3.0のAPIは限定されます。たとえば、アラームハンドラや周期ハンドラはタスクのように待ち状態となることができませんのでハンドラの中では待ち状態になる可能性のあるAPIを使用できません。
2-8. 割込みハンドラ
割込みと割込みハンドラ
割込みとは、実行中のプログラムの処理を一時的に中断し、あらかじめ指定されていた別のプログラムの処理を実行するマイコンのハードウェアの機能です(図2-8-1)。
割込みの要因は、マイコンのハードウェアの仕様により決められています。割込みには様々な要因がありますが、代表的な割込み要因としては、入出力処理の終了などに伴うI/OデバイスからCPUへの通知があります。
割込みの要求が発生した際に実行されるプログラムを割込みハンドラとよびます。割込みの要因ごとに割込みハンドラが登録されます。この割込みハンドラの登録の方法も、マイコンのハードウェアの仕様により決められています。
割込み管理機能
割込みはハードウェアの機能であり、その詳細仕様はハードウェアごとに異なりますが、μT-Kernel 3.0の割込み管理機能により、共通のAPIを使って割込みに関する各種の操作が行えるようになります。たとえば、共通のAPIを使って割込みハンドラを登録することができますし、割込みハンドラをC言語の関数として記述することもできます。
ただし、割込み自体はあくまでもハードウェアの機能ですので、割込み管理機能のAPIの詳細な動作もマイコンのハードウェアごとに異なります。μT-Kernel 3.0では、ハードウェアへの依存性が高い割込みのような機能に関する仕様を実装仕様とよんでおり、マイコンごとに実装仕様書が用意されています。割込み管理機能を使用する場合には、μT-Kernel 3.0の実装仕様を参照する必要があります。
μT-Kernel 3.0の割込み管理機能の主なAPIを表2-8-1に示します。
tk_def_int以外は、システムコールではなくライブラリ関数です。これらのライブラリ関数はマイコンの割込み関係のハードウェアを直接制御するものです。
API名 | 機能 |
---|---|
tk_def_int | 割込みハンドラの定義 |
EnableInt | 割込み許可 |
DisableInt | 割込み禁止 |
ClearInt | 割込み発生要因のクリア |
SetIntMode | 割込みモードの設定 |
割込みハンドラの定義
多くのマイコンでは、割込みハンドラの記述にアセンブリ言語を必要とします。しかし、μT-Kernel 3.0の割込み管理機能を使用することにより、割込みハンドラをC言語の関数として記述することができます。また、割込みハンドラのプログラムからμT-Kernel 3.0の一部のAPIを使用することも可能です。
C言語で記述された割込みハンドラは、実際にはマイコンの割込みハードウェアによってすぐに実行されるのではなく、μT-Kernel 3.0の中の共通の割込みハンドラを経由してから実行されます。C言語で記述された割込みハンドラに対しては、TA_HLNG属性を指定します。
一方、μT-Kernel 3.0の共通の割込みハンドラを経由せず、マイコンの割込みハードウェアによってすぐに実行される割込みハンドラも使用できます。このような割込みハンドラに対しては、TA_ASM属性を指定します。
TA_ASM属性の割込みハンドラは、マイコンの割込みハードウェアの仕様にしたがって作成する必要があり、通常はアセンブリ言語で記述されます。また、TA_ASM属性の割込みハンドラの中でμT-Kernel 3.0のAPIを使用する場合は、そのマイコンに対するμT-Kernel 3.0の実装仕様を理解し、適切な処理を行わなければなりません。
TA_HLNG属性とTA_ASM属性の元来の意味としては、TA_HLNG属性がC言語などの高級言語(High-level language)で記述された割込みハンドラであり、TA_ASM属性がアセンブリ言語(Assembly language)で記述された割込みハンドラであることを表していました。しかし、近年ではArm Cortex-Mのように、OSが無くても直接C言語で割込みハンドラを記述できるマイコンや、割込みハンドラを記述できる機能を設けたCコンパイラなどもあります。
したがって、現状では高級言語かアセンブリ言語かという記述言語の区別は本質的ではなく、OS内の共通処理を経由するか否かのほうに大きな意味があるといえます。
割込みハンドラの注意点
割込みハンドラは、μT-Kernel 3.0のカーネル・オブジェクトではなく、ハードウェア上の機能である点に注意してください。
割込みハンドラは、実行中の通常のプログラムに割り込んで、優先的に実行されます。つまり、すべてのタスクよりも優先的に実行されます。
また、割込みハンドラで使用できるμT-Kernel 3.0のAPIは限定されます。たとえば、割込みハンドラはタスクのように待ち状態となることはできませんので、待ち状態になる可能性のあるAPIはすべて使用できません。
割込みの制御
μT-Kernel 3.0の起動時には、大部分の割込みは禁止状態に設定されており、これらの割込みは発生しません。割込みを使用するには以下の手順で操作します。
- 割込みハンドラの定義API(tk_def_int)を呼び出し、対象とする割込み要因に対する割込みハンドラを定義します。
- 割込みモードの設定API(SetIntMode)を必要に応じて呼び出し、割込みモードの設定を行います。割込みモードでは、割込み信号の検出モードなどを設定できますが、具体的な設定の内容はマイコンのハードウェアに依存します。
- 割込み許可のAPI(EnableInt)を呼び出し、対象とする割込みを発生できるようにします。以降、条件が整えばこの割込みが発生するようになります。
なお、割込みを有効にする前には、必ず割込みハンドラを定義しておく必要があります。割込みハンドラが定義されていない状態で割込みが発生した場合、μT-Kernel 3.0はデフォルトの割込みハンドラを実行します。デフォルトの割込みハンドラの処理内容は実装仕様に依存しますが、通常はエラー処理を行います。 - 割込みが発生すると、(1)で定義されていた割込みハンドラが実行されます。割込みハンドラの中では、割込み要因に対応した適切な処理を行います。
- 割込みハンドラの中で、割込み発生要因のクリアAPI(ClearInt)を呼び出し、発生している割込みの要因を消します。これを行わないと、割込みが発生している状態が継続し、割込ハンドラを終了すると再び同じ割込みハンドラが呼び出されてしまう場合があります。
- 割込み禁止API(DisableInt)を呼び出すことにより、割込みの発生を禁止することができます。