システムコール(★★)

[組込み製品、リアルタイム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をカウントする割り込みもかかりません。そのため、そこで、待ってしまうと、永遠に待ちが解除されないことになり、システムが止まってしまうことになります。