リアルタイムオペレーティングシステム(RTOS)は、時間制約が厳密に定義されたデータとイベントを処理するリアルタイムコンピューティングアプリケーション向けのオペレーティングシステム(OS)です。RTOSは主にマイクロコントローラなどのリソース制約のあるデバイスを対象としています。これは、マルチタスク環境やマルチプログラミング環境において、スケジューラ、データバッファ、または固定タスク優先順位付けによってシステムリソースの共有を管理するUnixなどのタイムシェアリングオペレーティングシステムとは異なります。すべての操作は、指定された時間とリソース制約内で検証可能な形で完了する必要があり、そうでなければRTOSはフェイルセーフになります。リアルタイムオペレーティングシステムはイベント駆動型でプリエンプティブであるため、OSは競合するタスクの関連する優先順位を監視し、タスクの優先順位を変更できます。
RTOSの重要な特徴は、アプリケーションのタスクを受け入れて完了するまでにかかる時間に関する一貫性のレベルです。変動は「ジッター」と呼ばれます。[ 1 ]主な設計目標は高いスループットではなく、ソフトまたはハードのパフォーマンスカテゴリを保証することです。通常または一般的に期限を満たすことができるRTOSはソフトリアルタイムOSですが、期限を決定論的に満たすことができる場合はハードリアルタイムOSです。[ 2 ]
RTOSはスケジューリングのための高度なアルゴリズムを備えています。スケジューラの柔軟性により、コンピュータシステム全体でプロセスの優先順位をより広範にオーケストレーションできますが、リアルタイムOSは多くの場合、限られたアプリケーションセットに特化しています。リアルタイムOSの重要な要素は、割り込みレイテンシとスレッド切り替えレイテンシを最小限に抑えることです。リアルタイムOSは、一定期間内に実行できる作業量よりも、応答の速さや予測可能性の高さで評価されます。[ 3 ]
最も一般的なデザインは以下のとおりです。
タイムシェアリング設計では、必要以上に頻繁にタスクが切り替わりますが、よりスムーズなマルチタスク処理が可能になり、あたかもプロセスやユーザーがマシンを独占的に使用しているかのような錯覚を与えます。
初期のCPU設計では、タスクの切り替えに多くのサイクルが必要で、その間CPUは他の有用な処理を一切行うことができませんでした。切り替えに非常に時間がかかるため、初期のOSは不要なタスク切り替えを避けることでCPU時間の浪費を最小限に抑えようとしました。
一般的な設計では、タスクには次の3つの状態があります。
一般的に、 CPUコアごとに一度に実行できるタスクは 1 つだけなので、ほとんどのタスクはブロック状態または準備完了状態になります。準備完了キュー内のアイテム数は、システムが実行する必要のあるタスクの数と、システムが使用するスケジューラの種類によって大きく異なります。単純な非プリエンプティブだがマルチタスクなシステムでは、タスクは CPU 上の時間を他のタスクに譲らなければならないため、準備完了キューに実行準備完了状態のタスクが全体的に多く含まれる可能性があります (リソース枯渇)。
通常、スケジューラのレディリストのデータ構造は、プリエンプションが禁止され、場合によってはすべての割り込みが無効になるスケジューラのクリティカルセクションで費やされる最悪の場合の時間を最小限に抑えるように設計されていますが、データ構造の選択は、レディリストに存在できるタスクの最大数にも依存します。
準備完了リストにタスクが数個しか含まれていない場合は、準備完了タスクの双方向リンクリストが最適でしょう。準備完了リストに通常は少数のタスクしか含まれていないが、時折それ以上のタスクが含まれる場合は、リストを優先度順にソートする必要があります。そうすることで、実行する最優先度のタスクを見つける際にリストを走査する必要がなくなります。その代わりに、タスクを挿入する際にはリストを走査する必要があります。
この検索中は、プリエンプションを抑制してはいけません。長いクリティカルセクションは、より小さな部分に分割する必要があります。優先度の低いタスクの挿入中に、優先度の高いタスクが準備完了となる割り込みが発生した場合は、優先度の高いタスクを挿入し、優先度の低いタスクが挿入される直前に実行することができます。
クリティカルレスポンスタイム(フライバックタイムとも呼ばれる)とは、新しい準備完了タスクをキューに追加し、最優先タスクの状態を実行状態に復元するのにかかる時間のことです。適切に設計されたRTOSでは、新しいタスクの準備完了には準備完了キューのエントリごとに3~20命令、最優先タスクの復元には5~30命令が必要です。
高度なシステムでは、リアルタイムタスクは多くの非リアルタイムタスクと計算リソースを共有するため、準備完了リストは任意に長くなる可能性があります。このようなシステムでは、リンクリストとして実装されたスケジューラの準備完了リストは不十分です。
一般的に使用されるRTOSスケジューリングアルゴリズムには、次のものがあります。[ 4 ]
Unixのようなマルチタスクオペレーティングシステムは、リアルタイムタスクには不向きです。スケジューラはコンピュータへの負荷が最も低いジョブに最優先で割り当てるため、時間制約のあるジョブが十分なリソースにアクセスできることを保証する方法はありません。マルチタスクシステムは、複数のタスク間でデータとハードウェアリソースの共有を管理する必要があります。通常、2つのタスクが同じ特定のデータまたはハードウェアリソースに同時にアクセスすることは安全ではありません。[ 5 ]この問題を解決するための一般的なアプローチは3つあります。
汎用オペレーティングシステムでは、通常、ユーザープログラムによる割り込みの無効化は許可されていません。これは、ユーザープログラムがCPUを制御できてしまうためです。最新のCPUの中には、割り込みの制御をオペレーティングシステムの重要なリソースとみなすため、ユーザーモードコードによる割り込みの無効化を許可しないものもあります。しかし、多くの組み込みシステムやRTOSでは、システムコールの効率を高め、OSの介入なしにアプリケーションが動作環境をより詳細に制御できるように、アプリケーション自体をカーネルモードで実行することを許可しています。
シングルプロセッサシステムでは、カーネルモードで実行され、割り込みをマスクするアプリケーションが、共有リソースへの同時アクセスを防止する最もオーバーヘッドの少ない方法です。割り込みがマスクされ、現在のタスクがブロッキングOS呼び出しを行わない間は、他のタスクや割り込みが制御権を奪うことができないため、現在のタスクはCPUを排他的に使用でき、クリティカルセクションが保護されます。タスクがクリティカルセクションを抜けると、割り込みのマスクを解除する必要があります。保留中の割り込みがあれば、実行されます。割り込みの一時的なマスクは、クリティカルセクションを通過する最長パスが、必要な最大割り込みレイテンシよりも短い場合にのみ行うべきです。通常、この保護方法は、クリティカルセクションがわずか数個の命令で構成され、ループを含まない場合にのみ使用されます。この方法は、ビットが異なるタスクによって制御されるハードウェアビットマップレジスタを保護するのに最適です。
共有リソースを他のすべてのタスク(フラッシュメモリへの書き込み待ちなど)をブロックせずに予約する必要がある場合は、汎用オペレーティングシステムでも利用可能なミューテックスやOSが管理するプロセス間メッセージングなどのメカニズムを使用するのが望ましい。これらのメカニズムはシステムコールを伴い、通常は終了時にOSのディスパッチャコードを呼び出すため、実行に数百のCPU命令を要する。一方、割り込みをマスクする場合は、プロセッサによってはわずか1命令で済む場合もある。
(非再帰的な)ミューテックスは、ロックされているか、ロック解除されているかのどちらかです。タスクがミューテックスをロックすると、他のすべてのタスクは、所有者である元のスレッドによってミューテックスのロックが解除されるまで待機する必要があります。タスクは、ミューテックスの待機にタイムアウトを設定できます。ミューテックスベースの設計には、優先順位の逆転やデッドロックなど、よく知られた問題がいくつかあります。
優先度逆転では、優先度の低いタスクがミューテックスを持っているため、優先度の高いタスクが待機状態になりますが、優先度の低いタスクには作業を完了するためのCPU時間が与えられません。一般的な解決策は、ミューテックスを所有するタスクが、最も優先度の高い待機中のタスクの優先度を「継承」することです。しかし、待機レベルが複数ある場合、この単純なアプローチはより複雑になります。タスクAがタスクBによってロックされたミューテックスを待機し、タスクBがタスクCによってロックされたミューテックスを待機している場合などです。複数のレベルの継承を処理すると、他のコードが優先度の高いコンテキストで実行されるため、中優先度のスレッドが飢餓状態になる可能性があります。
デッドロックとは、2つ以上のタスクがタイムアウトなしでミューテックスをロックし、他のタスクのミューテックスが解放されるのを永久に待ち続けることで、循環依存が発生する状態です。最も単純なデッドロックのシナリオは、2つのタスクが交互に2つのミューテックスをロックするものの、その順序が逆の場合に発生します。デッドロックは、慎重な設計によって防止できます。
リソース共有のもう1つのアプローチは、タスクが組織化されたメッセージパッシング方式でメッセージを送信することです。このパラダイムでは、リソースは1つのタスクによってのみ直接管理されます。別のタスクがリソースを照会または操作したい場合は、管理タスクにメッセージを送信します。単純なメッセージベースのシステムは、リアルタイム動作はセマフォシステムほど明確ではありませんが、ほとんどのプロトコルデッドロックの危険性を回避し、一般的にセマフォシステムよりも動作が安定しています。ただし、セマフォと同様の問題が発生する可能性があります。優先度逆転は、タスクが低優先度のメッセージを処理しているときに、受信メッセージキュー内の高優先度のメッセージ(または高優先度のタスクから間接的に発生したメッセージ)を無視した場合に発生する可能性があります。プロトコルデッドロックは、2つ以上のタスクが互いに応答メッセージの送信を待っている場合に発生する可能性があります。
割り込みハンドラは最優先タスクの実行をブロックするため、またリアルタイムオペレーティングシステムはスレッドのレイテンシを最小限に抑えるように設計されているため、割り込みハンドラは通常、できるだけ短く保たれます。割り込みハンドラは、可能な限りハードウェアとのやり取りを延期します。通常必要なのは、割り込みを認識または無効化し(割り込みハンドラが戻ったときに再び発生しないようにするため)、タスクに処理が必要であることを通知することだけです。これは、セマフォを解放したり、フラグを設定したり、メッセージを送信したりして、ドライバタスクのブロックを解除することで実現できます。スケジューラは、多くの場合、割り込みハンドラのコンテキストからタスクのブロックを解除する機能を提供します。
OSは、スレッド、ミューテックス、メモリなど、管理するオブジェクトのカタログを保持しています。このカタログの更新は厳密に制御されなければなりません。そのため、アプリケーションがOS関数を呼び出している最中に、割り込みハンドラがOS関数を呼び出すと問題が発生する可能性があります。割り込みハンドラから呼び出されたOS関数は、アプリケーションの更新によってオブジェクトデータベースが矛盾した状態になっていることに気づくかもしれません。この問題に対処するには、統合アーキテクチャとセグメント化アーキテクチャという2つの主要なアプローチがあります。統合アーキテクチャを実装するRTOSは、内部カタログの更新中に割り込みを無効にすることでこの問題を解決します。この方法の欠点は、割り込みレイテンシが増加し、割り込みが失われる可能性があることです。セグメント化アーキテクチャは、OSを直接呼び出しませんが、OS関連の処理を別のハンドラに委任します。このハンドラは、どのスレッドよりも高い優先度で実行されますが、割り込みハンドラよりは低い優先度です。このアーキテクチャの利点は、割り込みレイテンシにほとんどサイクルを追加しないことです。その結果、セグメント化アーキテクチャを採用したOSは、統合アーキテクチャを採用したOSに比べて予測可能性が高く、より高い割り込み頻度にも対応できる。
同様に、 x86互換ハードウェアのシステム管理モードでは、オペレーティングシステムに制御を戻すまでにかなりの時間がかかる場合があります。
メモリ割り当ては、他のオペレーティングシステムよりもリアルタイムオペレーティングシステムにおいてより重要である。
まず、安定性を確保するためには、メモリリーク(割り当てられたメモリが使用後に解放されない状態)があってはなりません。デバイスは再起動を必要とせず、永続的に動作する必要があります。そのため、動的メモリ割り当ては推奨されません。可能な限り、必要なメモリ割り当てはすべてコンパイル時に静的に指定されます。
動的メモリ割り当てを避けるもう 1 つの理由は、メモリの断片化です。メモリの小さなチャンクを頻繁に割り当てたり解放したりすると、利用可能なメモリが複数のセクションに分割され、十分な空きメモリがあるにもかかわらず、RTOS が十分な大きさの連続したメモリ ブロックを割り当てることができない状況が発生する可能性があります。次に、割り当ての速度が重要です。標準的なメモリ割り当て方式では、適切な空きメモリ ブロックを見つけるために不定長のリンク リストをスキャンしますが、メモリ割り当ては一定時間内に行われる必要があるため、RTOS ではこれは許容できません。[ 6 ]
機械式ディスクは応答時間がはるかに長く、予測も難しいため、上記で説明したRAM割り当てと同じ理由で、ディスクファイルへのスワップは使用されません。
単純な固定サイズブロックアルゴリズムは、オーバーヘッドが少ないため、単純な組み込みシステムでは非常にうまく機能します。
すべてのRTOSは、対応するポートを備えたマイクロコントローラ上で動作できますが、Zephyrのような一部のRTOSは、外部フラッシュメモリ、I2Cデバイス、UARTコマンドやスリープモードなどの内部周辺機器といった、多数のデバイスドライバをさらに抽象化することができます。これにより、開発者がドライバを作成する必要がなくなり、サポートされていないドライバを移植するためのAPIセットが提供され、サポートされているドライバはアプリケーションコード内ですぐに使用できるようになります。