リアルタイムOSのシステムコールシステムコール(★★)[組み込み製品、リアルタイムOS、組み込みドライバーの開発依頼は、フィールドデザインまでお気軽にお問い合わせください。]ここでは、リアルタイムOSのシステムコールについて内部の構造を説明します。重要なのはセマフォです。OSにセマフォさえ実装されていれば、他のシステムコールは自分で作れてしまいます。そのため、ここでは下記の重要なシステムコールだけ説明します。タイマー機能や、メモリ管理機能は、OSのシステムコールを使わずとも、ユーザーにて作成できるため、説明しません。割り込み管理セマフォタスク管理タスク管理のシステムコールには下記のものがあります。xTaskCreate()vTaskDelete()vTaskDelay()vTaskSuspend()vTaskResume()xTaskCreate()は、タスクを生成するシステムコールです。引数として、タスクの関数へのポインタ、タスクが使用するスタックサイズ、優先度を取り、生成したハンドル番号を返します。タスクの関数へのポインタは、どこからタスクを開始するかというパラメータですので、絶対に必要なパラメータです。スタックサイズは、アプリケーションにもよりますが、512Byteとかの値です。一般的に、組み込みソフトウェアの場合は、スタックオーバーフローを避けるために、サイズの大きいローカル変数の使用を抑えるように実装します(スタックオーバーフローは、デバッグが大変で解析に何日もかかるので)。そのため、テンポラリに使われる変数以外はグローバル変数かstatic変数として実装するのが一般的です。そのことで、それほど大きなスタックサイズは必要としません。優先度は、特にタイミングがシビアな機能を実装しないのであれば、すべてのタスクで同じ値で問題ありません。ハンドル番号は、タスクが生成された時に割り当てられるもので、のちのち、タスクを削除したいとか、サスペンド、レジュームしたいなどの場合に利用します。このシステムコールの内部では、指定されたサイズのスタックの確保と、TCBの領域の確保と、TCBに引数で渡されたパラメータを初期値にいれるだけです。タスクの状態は、READY状態にして、READYキューに積みます。一般的な組み込みソフトウェアでは、スケジューラを開始する前に、タスクはすべて生成しておきます(静的にタスク生成すると言う)。理由は、途中でタスクを生成し、確保するヒープ領域が足りなくなり、生成に失敗すると、システムとしては、回復することができず、破綻してしまうからです。vTaskDelete()は、タスクを削除するシステムコールです。引数として、ハンドル番号を取ります。これは、xTaskCreate()にてタスクを生成した時に渡されたものを指定します。このシステムコールの内部では、確保されているスタックやTCBを解放します。vTaskDelay()は、タスクを一定時間、WAIT状態にするシステムコールです。引数として、WAITするOSの単位時間(tickといい、普通は1ms)の数を取ります。ハードウェアの初期化に20msなどかかる場合などは、このシステムコールを使ってその時間を待ちます。while()で指定クロック時間をループさせる方法に比べると、待っている時間は他のタスクの動作が可能ですので、他のタスクの動作に影響を与えません。while()で指定クロックをループさせる方法では、途中で他のタスクに切り替わってしまうと、指定時間より長く待ってしまい、無駄になります。このシステムコールの内部では、WAIT状態にし、時間が経過したらREADYキューに積むように、tick割り込み動作に時間をカウントダウンするように登録します。vTaskSuspend()は、タスクを停止させるシステムコールです。引数として、停止させるタスクのハンドル番号を取ります。この停止状態は一般的にあまり利用されることがないので、本コラムではあえて扱っていません。異常ケースが発生した時など、関連させるタスクを停止状態にし、資源をリセットしたりする時に使います。使われることは、本当にごく稀です。このシステムコールの内部では、タスクを停止状態にするだけです。WAIT状態とは違いますので、vTaskResume()が呼ばれるまで、タスクが動作することはありません。vTaskResume()は、停止しているタスクを再開させるものです。引数として、再開させるタスクのハンドル番号を取ります。このシステムコールの内部では、タスクをWAIT状態にするだけです。割り込み管理のシステムコールには下記のものがあります。taskENTER_CRITICAL()taskEXIT_CRITICAL()taskENTER_CRITICAL()は、割り込み禁止にするシステムコールです。割り込み禁止にしている時間は、短くなければなりません。その期間は、OSが止まってしまっているので、時間的にシビアな処理ができない状態となっています。すべてのタスクや割り込みに影響を与えるため、一般的にこのシステムコールを使うことを避けます。セマフォしかないOSでキューやイベントフラグの機能を自分で作成したい時は、このシステムコールを使って、作成することはあります。このシステムコールの内部では、CPUの割り込み禁止命令を呼んでいるだけです。taskEXIT_CRITICAL()は、割り込み許可にするシステムコールです。このシステムコールの内部では、CPUの割り込み禁止許可を呼んでいるだけです。セマフォセマフォのシステムコールには下記のものがあります。セマフォは、タスク間やタスクと割り込み間で同期を取りたい場合や、変数・ハードウェアを排他制御してアクセスしたい場合に使います。xSemaphoreCreateBinary()xSemaphoreCreateCounting()vSemaphoreDelete()xSemaphoreGive()xSemaphoreTake()xSemaphoreCreateBinary()は、バイナリセマフォの生成を行います。引数はなく、戻り値として、セマフォのハンドル番号を返します。このシステムコールの内部では、セマフォの管理領域を確保し、セマフォの初期値(=0)を設定します。xSemaphoreCreateCounting()は、カウンティングセマフォの生成を行います。引数は、セマフォの最大値と、セマフォの初期値です。戻り値として、セマフォのハンドル番号を返します。このシステムコールの内部では、セマフォの管理領域を確保し、引数で指定されたセマフォの最大値、初期値を設定します。vSemaphoreDelete()は、セマフォの削除を行います。引数は、生成の時に取得したセマフォのハンドル番号です。このシステムコールの内部では、セマフォの管理領域を確保します。このシステムコールを使うことは一般的にはありません。xSemaphoreGive()は、セマフォを与える(postする)動作をします。引数として、セマフォのハンドル番号を取ります。与える(postする)ことができるセマフォがない状態で、このシステムコールを呼び出すとエラーになります。このシステムコールの内部では、セマフォの値を+1しています。そのことで、セマフォを待っているタスクがREADY状態になります。割り込みルーチンから呼び出す場合は、xSemaphoreGiveFromISR()を利用します。このように分けているかといいますと、xSemaphoreGive()内部ではOS資源を操作するため、CPUの割り込み禁止と割り込み許可を呼んでいます。割り込みルーチンの途中で割り込み許可を呼んでしまうと、他の割り込みが入ってしまい、システムがおかしい状態になってしまいます。そのため、xSemaphoreGiveFromISR()内では、それらを使わないようにしてあります。xSemaphoreTake()は、セマフォを取る(waitする)動作をします。引数として、セマフォのハンドル番号と、待つ期間(tick単位)を取ります。指定した待ち時間内にセマフォが取れない場合は、エラーを返します。このシステムコールの内部では、呼び出したタスクをWAIT状態にして、セマフォが与えられるまで待ちます。セマフォが与えられると、セマフォの値を-1します。割り込みルーチン内でも、xSemaphoreTakeFromISR()を使って呼び出すことはできますが、待ち時間はゼロのみとなります。呼び出した時にセマフォが余っていれば、セマフォを取れますが、そうでなければエラーになります。xSemaphoreTakeFromISR()にて、待ち時間を指定できなかというと、割り込みルーチン内では割り込みを禁止にしているので、他の割り込みがかかりません(厳密には、割り込みプライオリティを使って、多重に割り込みをかけることはできますが、ここでは説明しません)。tickをカウントする割り込みもかかりません。そのため、そこで、待ってしまうと、永遠に待ちが解除されないことになり、システムが止まってしまうことになります。