第 3 章 - Azure RTOS ThreadX の機能コンポーネント
この章では、機能の観点から見た高パフォーマンスの Azure RTOS ThreadX カーネルについて説明します。 各機能コンポーネントは、わかりやすい方法で説明されています。
実行の概要
ThreadX アプリケーション内のプログラム実行には、初期化、スレッド実行、割り込みサービス ルーチン (ISR)、およびアプリケーション タイマーの 4 種類があります。
図 2 は、さまざまな種類のプログラム実行を示しています。 これらの各種類の詳細については、この章の後続のセクションで説明します。
初期化
名前が示すように、これは ThreadX アプリケーションでのプログラム実行の最初の種類です。 初期化には、プロセッサのリセットと "スレッド スケジューリング ループ" のエントリ ポイントとの間のすべてのプログラム実行が含まれます。
スレッド実行
初期化が完了すると、ThreadX はスレッド スケジューリング ループに入ります。 スケジューリング ループでは、実行準備の完了したアプリケーション スレッドが検索されます。 準備の完了したスレッドが見つかると、ThreadX はそれに制御を移します。 スレッドが完了すると (または、それより優先度の高い別のスレッドが準備完了になると)、実行はスレッド スケジューリング ループに戻り、次に優先度の高い、準備の完了したスレッドが検索されます。
スレッドの実行とスケジューリングを継続的に行うこのプロセスは、ThreadX アプリケーションで最も一般的な種類のプログラム実行です。
割り込みサービス ルーチン (ISR)
割り込みは、リアルタイム システムの基礎となります。 割り込みがなければ、外部の変化に迅速に対応することが非常に困難になります。 割り込みが検出されると、プロセッサでは、現在のプログラムの実行に関する重要な情報を保存し (通常はスタックに)、その後、定義済みのプログラム領域に制御を移します。 この定義済みのプログラム領域は、一般に割り込みサービス ルーチンと呼ばれます。 ほとんどの場合、割り込みはスレッドの実行中に (またはスレッド スケジューリング ループ内で) 発生します。 ただし、割り込みは、実行中の ISR またはアプリケーション タイマーの内部で発生する場合もあります。
図 2. プログラムの実行の種類
アプリケーション タイマー
アプリケーション タイマーは ISR に似ていますが、ハードウェア実装 (通常は単一の周期的ハードウェア割り込みが使用されます) はアプリケーションから見えません。 このようなタイマーは、タイムアウト、周期割り込み、またはウォッチドッグ サービスを実行するためにアプリケーションによって使用されます。 ISR と同様に、アプリケーション タイマーもスレッドの実行に割り込むことがよくあります。 ただし、ISR とは異なり、アプリケーション タイマーどうしで割り込むことはできません。
メモリ使用量
ThreadX は、アプリケーション プログラムと共存しています。 その結果、ThreadX の静的メモリ (つまり固定メモリ) の使用量は、開発ツール (コンパイラ、リンカー、ロケーターなど) によって決定されます。 動的メモリ (つまり実行時メモリ) の使用量は、アプリケーションによって直接制御されます。
静的メモリの使用量
ほとんどの開発ツールでは、アプリケーション プログラム イメージを "命令"、"定数"、"初期化されたデータ"、"初期化されていないデータ"、"システム スタック" の 5 つの基本的な領域に分割します。 図 3 は、これらのメモリ領域の例を示しています。
これは例にすぎないことを理解することが重要です。 実際の静的メモリのレイアウトは、プロセッサ、開発ツール、基になるハードウェアに固有のものです。
命令領域には、そのプログラムのすべてのプロセッサ命令が含まれています。 この領域は、一般に最も大きく、多くの場合 ROM 内にあります。
定数領域には、プログラム内で定義または参照される文字列も含め、さまざまなコンパイル済み定数が含まれています。 さらに、この領域には、初期化されたデータ領域の "初期のコピー" も含まれています。 "メモリ使用量" のコンパイラの初期化プロセス中に、定数領域のこの部分は、RAM 内の初期化されたデータ領域を設定するために使用されます。 定数領域は、通常は命令領域の後にあり、多くの場合は ROM 内にあります。
初期化されたデータ領域と初期化されていないデータ領域には、グローバル変数と静的変数がすべて含まれています。 これらの領域は常に RAM 内に配置されます。
システム スタックは、通常、初期化されたデータ領域と初期化されていないデータ領域の直後に配置されます。
システム スタックは、コンパイラによって初期化中に使用され、次に ThreadX によって初期化中に使用され、その後 ISR 処理で使用されます。
図 3. メモリ領域の例
動的メモリの使用量
前述のとおり、動的メモリの使用量は、アプリケーションによって直接制御されます。 スタック、キュー、およびメモリ プールに関連付けられている制御ブロックとメモリ領域は、ターゲットのメモリ領域内のどこにでも配置できます。 さまざまな種類の物理メモリを簡単に利用できるようになるため、これは重要な機能です。
たとえば、ターゲットのハードウェア環境に、高速なメモリと低速なメモリの両方が存在するとします。 優先度の高いスレッドのためにアプリケーションで追加のパフォーマンスを必要とする場合は、その制御ブロック (TX_THREAD) とスタックを高速メモリ領域に配置できます。これにより、パフォーマンスが大幅に向上する可能性があります。
初期化
初期化プロセスを理解することが重要です。 初期のハードウェア環境がここで設定されます。 さらに、ここでアプリケーションに初期のパーソナリティが付与されます。
Note
ThreadX では、可能な限り、完全な開発ツールの初期化プロセスの利用が試みられます。 これにより、将来的に、開発ツールの新しいバージョンへのアップグレードが容易になります。
システム リセット ベクター
すべてのマイクロプロセッサにリセット ロジックがあります。 リセット (ハードウェアまたはソフトウェア) が発生すると、アプリケーションのエントリ ポイントのアドレスが特定のメモリ位置から取得されます。 エントリ ポイントが取得されると、プロセッサはその位置に制御を移します。 アプリケーションのエントリ ポイントは、多くの場合、ネイティブ アセンブリ言語で記述されており、通常は開発ツールによって (少なくともテンプレート形式で) 提供されます。 場合によっては、特別なバージョンのエントリ プログラムが ThreadX によって提供されます。
開発ツールの初期化
低レベルの初期化が完了すると、開発ツールの高レベルの初期化に制御が移ります。 通常、初期化されたグローバル変数と静的 C 変数の設定はここで行われます。 それらの初期値は定数領域から取得されることに注意してください。 厳密な初期化処理は開発ツールに固有です。
main 関数
開発ツールの初期化が完了すると、ユーザー指定の main 関数に制御が移ります。 この時点で、次に行われる処理はアプリケーションによって制御されます。 ほとんどのアプリケーションでは、main 関数は単に tx_kernel_enter を呼び出します。これは ThreadX へのエントリです。 ただし、ThreadX に入る前に、アプリケーションで事前処理 (通常はハードウェアの初期化のため) を実行できます。
重要
tx_kernel_enter の呼び出しからは戻らないため、その後に処理を配置しないでください。
tx_kernel_enter
このエントリ関数は、さまざまな内部 ThreadX データ構造の初期化を調整し、アプリケーションの定義関数 tx_application_define を呼び出します。
tx_application_define から戻ると、スレッド スケジューリング ループに制御が移ります。 これは初期化が終了したことを示します。
アプリケーション定義関数
tx_application_define 関数では、初期のアプリケーション スレッド、キュー、セマフォ、ミューテックス、イベント フラグ、メモリ プール、タイマーがすべて定義されます。 また、アプリケーションの通常の動作中に、スレッドからシステム リソースの作成と削除を行うこともできます。 ただし、初期のアプリケーション リソースはすべてここで定義されます。
tx_application_define 関数には 1 つの入力パラメーターがあり、これについては触れておく価値があります。 "最初に使用可能な" RAM アドレスが、この関数の唯一の入力パラメーターです。 これは通常、スレッドのスタック、キュー、メモリ プールの初期実行時メモリ割り当ての開始点として使用されます。
Note
初期化が完了した後は、実行中のスレッドだけがシステム リソース (他のスレッドも含まれます) の作成と削除を行うことができます。 そのため、初期化中に、少なくとも 1 つのスレッドが作成される必要があります。
割り込み
初期化プロセス全体を通して、割り込みは無効のままになります。 アプリケーションで何らかの方法によって割り込みを有効にすると、予期しない動作が発生する可能性があります。 図 4 は、システム リセットからアプリケーション固有の初期化までの初期化プロセス全体を示しています。
スレッド実行
アプリケーション スレッドのスケジュール設定と実行は、ThreadX の最も重要なアクティビティです。 スレッドは通常、専用の用途を持つ半独立のプログラム セグメントとして定義されます。 すべてのスレッドの処理を組み合わせることによって、アプリケーションができあがります。
スレッドは、初期化中またはスレッド実行中に tx_thread_create の呼び出しによって動的に作成されます。 スレッドは、"準備完了" または "中断" 状態のいずれかで作成されます。
•
図 4. 初期化プロセス
スレッド実行状態
スレッドのさまざまな処理状態を理解することは、マルチスレッド環境全体を理解するための重要な要素です。 ThreadX では、スレッドの状態には "準備完了"、"中断"、"実行中"、"終了"、"完了" の 5 つがあります。 図 5 は、ThreadX のスレッド状態遷移図を示しています。
図 5. スレッドの状態遷移
スレッドは、実行準備が整ったときに "準備完了" 状態になります。 準備の完了したスレッドは、準備完了状態にある最も優先度の高いスレッドになるまで実行されません。 これが発生すると、ThreadX でスレッドが実行され、その状態が "実行中" に変更されます。
より優先度の高いスレッドの準備が整うと、実行中のスレッドは "準備完了" 状態に戻ります。 その後、新しく準備の整った優先度の高いスレッドが実行され、その論理状態が "実行中" に変わります。 スレッドのプリエンプションが発生するたびに、"準備完了" 状態と "実行中" 状態の間の遷移が発生します。
どの時点でも、1 つのスレッドのみが "実行中" 状態になります。 これは、"実行中" 状態のスレッドが、基になるプロセッサを制御できるためです。
"中断" 状態のスレッドは、実行の対象になりません。 "中断" 状態になる理由として、時間、キュー メッセージ、セマフォ、ミューテックス、イベント フラグ、メモリによる中断や、基本的なスレッドの中断があります。 中断の原因が解消されると、スレッドは "準備完了" 状態に戻ります。
"完了" 状態のスレッドは、処理を完了し、そのエントリ関数から戻ったスレッドです。 エントリ関数は、スレッドの作成時に指定されます。 "完了" 状態のスレッドを再度実行することはできません。
スレッドが "終了" 状態になるのは、別のスレッドまたはスレッド自体が tx_thread_terminate サービスを呼び出したためです。 "終了" 状態のスレッドを再度実行することはできません。
重要
完了または終了したスレッドを再度開始する必要がある場合、アプリケーションでは最初にスレッドを削除する必要があります。 その後、再作成して再度開始することができます。
スレッド開始/終了通知
アプリケーションによっては、特定のスレッドが初めて開始されたとき、完了したとき、または終了したときに通知を受け取ると役立つ場合があります。 ThreadX では、tx_thread_entry_exit_notify サービスを介してこの機能が提供されます。 このサービスによって特定のスレッドに対するアプリケーション通知関数が登録され、スレッドが実行を開始したとき、完了したとき、または終了したときに ThreadX によって呼び出されます。 アプリケーション通知関数では、呼び出された後、アプリケーション固有の処理を実行できます。 これには通常、ThreadX 同期プリミティブを介して別のアプリケーション スレッドにイベントを通知することが含まれます。
スレッドの優先度
前述のとおり、スレッドは、特定の目的を持つ半独立のプログラム セグメントです。 ただし、すべてのスレッドが等しく作成されるわけではありません。 一部のスレッドが持つ特定の目的は、他のものよりもはるかに重要です。 このようにスレッドの重要度が不均一であることは、埋め込みリアルタイム アプリケーションの特徴です。
ThreadX では、スレッドが作成されるときに、その "優先度" を表す数値を割り当てることによって、スレッドの重要度を決定します。 ThreadX の優先度の最大数は、32 ~ 1024 の範囲で、32 間隔で構成できます。 優先度の実際の最大数は、ThreadX ライブラリのコンパイル中に TX_MAX_PRIORITIES 定数によって決定されます。 優先度の数を増やしても、処理のオーバーヘッドが大幅に増加することはありません。 ただし、32 個の優先度レベルのグループごとに、その管理のために 128 バイトの RAM が追加で必要になります。 たとえば、32 個の優先度レベルでは 128 バイトの RAM、64 個の優先度レベルでは 256 バイトの RAM、96 個の優先度レベルでは 384 バイトの RAM が必要になります。
既定では、ThreadX には優先度 0 から優先度 31 まで、32 個の優先度レベルがあります。 数値が小さいほど優先度が高くなります。 したがって、優先度 0 は最も高い優先度を表し、優先度 (TX_MAX_PRIORITIES-1) は最も低い優先度を表します。
協調スケジューリングやタイム スライシングを利用して、複数のスレッドに同じ優先度を指定できます。 また、スレッドの優先度は、実行時に変更できます。
スレッドのスケジューリング
ThreadX では、優先度に基づいてスレッドをスケジュールします。 最も優先度の高い準備完了スレッドが最初に実行されます。 同じ優先度の準備完了スレッドが複数ある場合は、"先入れ先出し" (FIFO) 方式で実行されます。
ラウンドロビン スケジューリング
ThreadX では、同じ優先度を持つ複数のスレッドの "ラウンドロビン" スケジューリングがサポートされています。 これは、tx_thread_relinquish の協調呼び出しを通じて実現されます。 このサービスにより、tx_thread_relinquish の呼び出し元が再度実行される前に、同じ優先度の他の準備完了スレッドすべてに実行の機会が与えられます。
タイム スライシング
"タイム スライシング" は、ラウンドロビン スケジューリングのもう 1 つの形式です。 タイム スライスは、スレッドがプロセッサを明け渡さずに実行できるタイマー刻み (タイマー割り込み) の最大数を指定します。 ThreadX では、タイム スライシングはスレッド単位で使用できます。 スレッドのタイム スライスは作成時に割り当てられ、実行時に変更できます。 タイム スライスの期限が切れると、タイム スライスされたスレッドが再度実行される前に、同じ優先度レベルの他の準備完了スレッドすべてに実行の機会が与えられます。
スレッドが中断した後、放棄した後、プリエンプションの原因となる ThreadX サービス呼び出しを行った後、またはそれ自体がタイム スライスされた後は、スレッドに新しいスレッド タイム スライスが与えられます。
タイム スライスされたスレッドがプリエンプトされた場合は、そのタイム スライスの残りの部分が、同じ優先度の他の準備完了スレッドよりも前に再開されます。
Note
タイム スライシングを使用すると、わずかなシステム オーバーヘッドが発生します。 タイム スライシングは、複数のスレッドが同じ優先度を共有している場合にしか役立たないため、一意の優先度を持つスレッドにはタイム スライスを割り当てないでください。
優先
プリエンプションは、より優先度の高いスレッドを優先して、実行中のスレッドに一時的に割り込むプロセスです。 このプロセスは、実行中のスレッドからは認識できません。 優先度の高いスレッドが終了すると、プリエンプションが発生した正確な場所に制御が戻ります。 重要なアプリケーション イベントへの迅速な対応が容易になるため、これはリアルタイム システムでは非常に重要な機能です。 非常に重要な機能ですが、プリエンプションは、飢餓状態、過剰なオーバーヘッド、優先度の逆転など、さまざまな問題の原因になることもあります。
プリエンプションしきい値™
プリエンプションに固有の問題の一部を緩和するために、ThreadX には、"プリエンプションしきい値" と呼ばれるユニークで高度な機能が用意されています。
プリエンプションしきい値を使用すると、スレッドでプリエンプションを無効にするための優先度の "上限" を指定できます。 上限より高い優先度を持つスレッドは引き続きプリエンプトできますが、上限より低いスレッドはプリエンプトできません。
たとえば、優先度 20 のスレッドが、15 ~ 20 の優先度を持つスレッドのグループとのみやり取りするとします。 この優先度 20 のスレッドでは、そのクリティカル セクションの処理中、プリエンプションしきい値を 15 に設定することで、やり取りするすべてのスレッドからのプリエンプションを防止できます。 これにより、非常に重要なスレッド (優先度 0 から 14 まで) では、クリティカル セクションの処理中でもこのスレッドをプリエンプトでき、応答性が大幅に向上します。
もちろん、スレッドでプリエンプションしきい値を 0 に設定して、すべてのプリエンプションを無効にすることもできます。 また、プリエンプションしきい値は実行時に変更できます。
Note
プリエンプションしきい値を使用すると、指定されたスレッドのタイム スライシングが無効になります。
優先度の継承
ThreadX では、この章の後半で説明するミューテックス サービス内で、オプションの優先度の継承もサポートしています。 優先度の継承を使用すると、優先度の低いスレッドによって所有されているミューテックスを待機している優先度の高いスレッドがあるとき、その優先度を優先度の低いスレッドで一時的に受け取ることができます。 この機能により、アプリケーションでは、中間スレッド優先度のプリエンプションを排除することで、不確定な優先度の逆転を回避できます。 もちろん、"プリエンプションしきい値" を使用して同様の結果を得ることもできます。
スレッドの作成
アプリケーション スレッドは、初期化中、または他のアプリケーション スレッドの実行中に作成されます。 アプリケーションで作成できるスレッドの数に制限はありません。
スレッド制御ブロック TX_THREAD
各スレッドの特性は、その制御ブロックに含まれています。 この構造体は tx_api.h ファイルで定義されています。
スレッド制御ブロックは、メモリ内の任意の場所に配置できますが、最も一般的なのは、制御ブロックを任意の関数のスコープ外に定義してグローバルな構造体にすることです。
動的に割り当てられたすべてのメモリと同様に、他の領域に制御ブロックを配置するにはもう少し注意が必要です。 制御ブロックが C 関数内に割り当てられている場合、それに関連付けられているメモリは、呼び出し元スレッドのスタックの一部になります。 一般に、制御ブロックにローカル ストレージを使用することは避けてください。これは、関数から制御が返された後、そのローカル変数スタック領域が、別のスレッドで制御ブロックのためにそれを使用しているかどうかに関係なくすべて解放されるためです。
ほとんどの場合、アプリケーションでスレッドの制御ブロックの内容は認識されていません。 ただし、状況により、特にデバッグ中には、特定のメンバーを確認することが役に立つ場合があります。 有用な制御ブロック メンバーの一部を次に示します。
tx_thread_run_count には、スレッドがスケジュールされた回数のカウンターが格納されます。 カウンターが増加している場合は、スレッドがスケジュールされ、実行されていることを示しています。
tx_thread_state には、関連付けられているスレッドの状態が格納されます。 スレッドの取り得る状態を次に示します。
スレッドの状態 | 値 |
---|---|
TX_READY | (0x00) |
TX_COMPLETED | (0x01) |
TX_TERMINATED | (0x02) |
TX_SUSPENDED | (0x03) |
TX_SLEEP | (0x04) |
TX_QUEUE_SUSP | (0x05) |
TX_SEMAPHORE_SUSP | (0x06) |
TX_EVENT_FLAG | (0x07) |
TX_BLOCK_MEMORY | (0x08) |
TX_BYTE_MEMORY | (0x09) |
TX_MUTEX_SUSP | (0x0D) |
Note
もちろん、スレッドの制御ブロックには、スタック ポインター、タイム スライス値、優先度など、他にも多くの興味深いフィールドがあります。制御ブロック メンバーを確認することはお勧めしますが、変更は厳密に禁止されています。
重要
このセクションで前述した "実行中" 状態に相当するものはありません。 どの時点でも、実行中のスレッドは 1 つだけなので、これは必要ありません。 実行中のスレッドの状態も TX_READY です。
現在実行中のスレッド
前述のとおり、どの時点でも、実行中のスレッドは 1 つだけです。 どのスレッドが要求を行っているかに応じて、実行中のスレッドを識別する方法はいくつかあります。 プログラム セグメントでは、tx_thread_identify を呼び出すことによって、実行中のスレッドの制御ブロック アドレスを取得できます。 これは、複数のスレッドから実行されるアプリケーション コードの共有部分で役立ちます。
デバッグ セッションでは、ユーザーは内部 ThreadX ポインター _tx_thread_current_ptr を調べることができます。 これには、現在実行中のスレッドの制御ブロック アドレスが格納されます。 このポインターが NULL の場合、実行中のアプリケーション スレッドはありません。つまり、スレッドが準備完了になるまで、ThreadX はスケジューリング ループ内で待機しています。
スレッド スタック領域
各スレッドには、最後の実行とコンパイラでの使用のコンテキストを保存するために、それぞれ専用のスタックが必要です。 ほとんどの C コンパイラでは、関数呼び出しを行うためと、一時的にローカル変数を割り当てるために、スタックを使用します。 図 6 は、一般的なスレッド スタックを示しています。
スレッド スタックがメモリ内のどこに配置されるかは、アプリケーション次第です。 スタック領域は、スレッドの作成時に指定され、ターゲットのアドレス空間内の任意の場所に配置できます。 アプリケーションでスタックを高速 RAM に配置することによって、重要なスレッドのパフォーマンスを向上させることができるため、これは重要な機能です。
スタックのメモリ領域 (例)
図 6. 一般的なスレッド スタック
スタックの大きさをどの程度にするべきかは、スレッドに関してよく寄せられる質問の 1 つです。 スレッドのスタック領域には、最悪ケースの関数呼び出しの入れ子、ローカル変数割り当て、および最後の実行コンテキストの保存に対応できる十分な大きさが必要です。
最小スタック サイズ TX_MINIMUM_STACK は、ThreadX によって定義されます。 このサイズのスタックでは、スレッドのコンテキストの保存と、最小量の関数呼び出しおよびローカル変数割り当てがサポートされます。
ただし、ほとんどのスレッドにとって最小スタック サイズは小さすぎるため、ユーザーは関数呼び出しの入れ子とローカル変数割り当てを調べることによって、最悪の場合のサイズ要件を確認する必要があります。 もちろん、大きなスタック領域から始めることをお勧めします。
アプリケーションのデバッグ後、メモリが不足している場合は、スレッド スタックのサイズを調整できます。 お勧めの方法は、スレッドを作成する前に、(0xEFEF) のような簡単に識別できるデータ パターンをすべてのスタック領域に事前設定することです。 アプリケーションの性能が完全に確認された後、スタック領域を調べて、実際に使用されたスタックの量を調べることができます。これは、データ パターンがそのまま残っているスタック領域を見つけることによって行われます。 図 7 は、0xEFEF に事前設定されていたスタックの、完全なスレッド実行後の状態を示しています。
スタックのメモリ領域 (別の例)
図 7. 0xEFEF に事前設定されたスタック
重要
ThreadX では、既定で、各スレッド スタックのすべてのバイトが値 0xEF で初期化されます。
メモリの落とし穴
スレッドのスタック要件は大きくなる可能性があります。 そのため、妥当な数のスレッドを持つようにアプリケーションを設計することが重要です。 さらに、スレッド内でスタックが過剰に使用されないように注意する必要があります。 再帰アルゴリズムと大規模なローカル データ構造は避けてください。
ほとんどの場合、オーバーフローしたスタックが原因で、スタック領域に隣接する (通常はその前にある) メモリがスレッドの実行によって破損します。 結果は予測不可能ですが、ほとんどの場合、プログラム カウンターに不自然な変更が発生します。 これは、"雑草へのジャンプ" と呼ばれることがよくあります。もちろん、これを防ぐ唯一の方法は、すべてのスレッド スタックのサイズが十分であることを確認することです。
オプションの実行時スタック チェック
ThreadX では、実行時に各スレッドのスタックが破損していないかどうかを確認できます。 ThreadX では、既定で、作成時にスレッド スタックのすべてのバイトが 0xEF データ パターンで埋められます。 アプリケーションで TX_ENABLE_STACK_CHECKING を定義して ThreadX ライブラリをビルドすると、ThreadX では、中断または再開のたびに各スレッドのスタックの破損が確認されます。 スタックの破損が検出された場合、ThreadX では、tx_thread_stack_error_notify の呼び出しで指定されたとおりにアプリケーションのスタック エラー処理ルーチンを呼び出します。 または、スタック エラー ハンドラーが指定されていない場合、ThreadX では内部の _tx_thread_stack_error_handler ルーチンを呼び出します。
再入
マルチスレッドの本当の長所の 1 つは、同じ C 関数を複数のスレッドから呼び出せることです。 これによって大きな力がもたらされ、コード空間の削減にも役立ちます。 ただし、複数のスレッドから呼び出される C 関数は "再入可能" である必要があります。
基本的に、再入可能関数では、呼び出し元の戻り先アドレスを現在のスタックに格納します。あらかじめ設定したグローバル変数や静的 C 変数には依存しません。 ほとんどのコンパイラでは、戻り先アドレスがスタックに配置されます。 そのため、アプリケーション開発者は、"グローバル" なものと "静的" なものの使用についてのみ注意する必要があります。
再入不可能な関数の例として、標準 C ライブラリの文字列トークン関数 strtok があります。 この関数は、後続の呼び出しで前の文字列ポインターを "記憶" しています。 これは、静的な文字列ポインターを使用して行われます。 この関数が複数のスレッドから呼び出された場合、無効なポインターが返される可能性が高くなります。
スレッド優先度の落とし穴
スレッドの優先度の選択は、マルチスレッドの最も重要な側面の 1 つです。 実行時に何が必要かを判断するのではなく、スレッドの重要度の思い込みに基づいて優先度を割り当てたくなることがあります。 スレッドの優先度を誤用すると、他のスレッドが飢餓状態になり、優先度の逆転が発生し、処理帯域幅が減少し、アプリケーションの実行時の動作がわかりにくくなる可能性があります。
前述のとおり、ThreadX では、優先度に基づくプリエンプティブ スケジューリング アルゴリズムが提供されています。 優先度の低いスレッドは、優先度の高い実行準備の整ったスレッドが存在しなくなるまで実行されません。 優先度の高いスレッドが常に準備完了状態である場合、優先度の低いスレッドが実行されることはありません。 この状態は、"スレッドの飢餓状態" と呼ばれます。
ほとんどのスレッドの飢餓問題は、デバッグの早い段階で検出され、優先度の高いスレッドが連続して実行されないようにすることで解決できます。 または、アプリケーションにロジックを追加して、実行する機会が得られるまで、飢餓状態のスレッドの優先度を徐々に上げることができます。
スレッドの優先度に関連するもう 1 つの落とし穴は "優先度の逆転" です。 必要なリソースが優先度の低いスレッドによって保持されているために、優先度の高いスレッドが中断されると、優先度の逆転が発生します。 もちろん、場合によっては、優先度の異なる 2 つのスレッドで共通のリソースを共有する必要があります。 これらのスレッドだけがアクティブである場合、優先度の逆転時間は、優先度の低いスレッドがリソースを保持している時間に限られます。 この状態は確定的かつ正常です。 ただし、この優先度の逆転状態で、中間優先度のスレッドがアクティブになった場合、優先度の逆転時間は確定的ではなくなり、アプリケーション エラーが発生する可能性があります。
ThreadX での不確定な優先度逆転を防止するには、主に 3 つの方法があります。 まず、アプリケーションの優先度の選択と実行時の動作を、優先度の逆転問題を防ぐように設計できます。 次に、優先度の低いスレッドでは、"プリエンプションしきい値" を使用して、中間スレッドからのプリエンプションをブロックしながら、優先度の高いスレッドとリソースを共有できます。 最後に、ThreadX ミューテックス オブジェクトを使用してシステム リソースを保護しているスレッドでは、オプションのミューテックス "優先度継承" を利用して、不確定な優先度逆転を排除できます。
優先度のオーバーヘッド
マルチスレッドのオーバーヘッドを減らすための方法として、最も見落とされがちなものの 1 つは、コンテキスト切り替えの数を減らすことです。 前述のとおり、優先度の高いスレッドの実行が実行中のスレッドよりも優先される場合、コンテキスト切り替えが発生します。 より優先度の高いスレッドが準備完了状態になるのは、割り込みなどの外部イベントによる場合と、実行中のスレッドによって行われたサービス呼び出しによる場合の両方があります。
スレッドの優先度がコンテキスト切り替えのオーバーヘッドに与える影響を示すために、thread_1、thread_2、および thread_3 という名前のスレッドを持つ 3 つのスレッド環境があると仮定します。 さらに、すべてのスレッドが、メッセージを待機して中断状態にあるとします。 thread_1 は、メッセージを受信すると、すぐに thread_2 に転送します。 thread_2 はその後、メッセージを thread_3 に転送します。 thread_3 は、メッセージを破棄するだけです。 各スレッドは、メッセージを処理した後、戻って別のメッセージを待機します。
これら 3 つのスレッドを実行するために必要な処理は、それらの優先度によって大きく変わります。 すべてのスレッドの優先度が同じである場合は、各スレッドの実行前に 1 回のコンテキスト切り替えが発生します。 コンテキスト切り替えは、各スレッドが空のメッセージ キューで中断したときに発生します。
ただし、thread_2 の優先度が thread_1 よりも高く、thread_3 の優先度が thread_2 よりも高い場合、コンテキスト切り替えの数は 2 倍になります。 これは、優先度の高いスレッドの準備が整ったことが検出されると、tx_queue_send サービスの内部でもう 1 回コンテキスト切り替えが発生するためです。
ThreadX のプリエンプションしきい値のメカニズムを使用すると、このような余分なコンテキスト切り替えを回避でき、前述した優先度の選択も引き続き可能です。 スケジューリング時に複数のスレッド優先度が許可されると同時に、スレッド実行時にそれらの間で発生する不要なコンテキスト切り替えの一部を回避できるため、これは重要な機能です。
実行時のスレッド パフォーマンス情報
ThreadX では、実行時のスレッド パフォーマンス情報がオプションで提供されます。 ThreadX ライブラリとアプリケーションが TX_THREAD_ENABLE_PERFORMANCE_INFO を定義してビルドされている場合、ThreadX では次の情報が蓄積されます。
システム全体の合計数:
スレッドの再開
スレッドの中断
サービス呼び出しのプリエンプション
割り込みのプリエンプション
優先度逆転
タイムスライス
放棄
スレッド タイムアウト
中断の中止
アイドル システム リターン
非アイドル システム リターン
スレッドごとの合計数:
再開
中断
サービス呼び出しのプリエンプション
割り込みのプリエンプション
優先度逆転
タイムスライス
スレッド放棄
スレッド タイムアウト
中断の中止
この情報は、実行時にサービス tx_thread_performance_info_get および tx_thread_performance_system_info_get を通じて取得できます。 スレッド パフォーマンス情報は、アプリケーションが正しく動作しているかどうかを判断するのに役立ちます。 アプリケーションの最適化にも役立ちます。 たとえば、サービス呼び出しのプリエンプションが比較的多い場合は、スレッドの優先度やプリエンプションしきい値が低すぎることを示唆している可能性があります。 さらに、アイドル システム リターンが比較的少ない場合は、優先度の低いスレッドが十分に中断していないことを示唆している可能性があります。
デバッグの落とし穴
マルチスレッド アプリケーションでは、複数のスレッドから同じプログラム コードが実行される可能性があるため、デバッグが少し難しくなります。 このような場合、ブレークポイントだけでは不十分な場合があります。 デバッガーで条件付きブレークポイントを使用して現在のスレッド ポインター _tx_thread_current_ptr を表示し、呼び出し元スレッドがデバッグ対象のスレッドであるかどうかを確認する必要があります。
この多くは、さまざまな開発ツールのベンダーが提供するマルチスレッド サポート パッケージで処理されています。 シンプルな設計のため、ThreadX をさまざまな開発ツールと統合するのは比較的簡単です。
スタック サイズは、常にマルチスレッドの重要なデバッグ トピックです。 原因不明の動作が見られた場合、通常はまず、すべてのスレッドのスタック サイズ (特に最後に実行するスレッドのスタック サイズ) を増やすことをお勧めします。
ヒント
TX_ENABLE_STACK_CHECKING を定義して ThreadX ライブラリをビルドすることもお勧めします。 これにより、処理のできるだけ早い段階でスタックの破損の問題を特定できます。
メッセージ キュー
メッセージ キューは、ThreadX でのスレッド間通信の主要な手段です。 メッセージ キューには 1 つ以上のメッセージを格納できます。 1 つのメッセージ保持するメッセージ キューは、通常、"メールボックス" と呼ばれます。
メッセージは tx_queue_send によってキューにコピーされ、tx_queue_receive によってキューからコピーされます。 唯一の例外は、スレッドが空のキューでメッセージを待機している間、中断されている場合です。 この場合、キューに送信された次のメッセージは、スレッドの宛先領域に直接配置されます。
各メッセージ キューはパブリック リソースです。 ThreadX では、メッセージ キューの使用方法に関する制約はありません。
メッセージ キューの作成
メッセージ キューは、初期化中または実行時にアプリケーション スレッドによって作成されます。 アプリケーションでのメッセージ キューの数に制限はありません。
メッセージ サイズ
各メッセージ キューでは、いくつかの固定サイズのメッセージがサポートされます。 使用可能なメッセージ サイズは、32 ビット ワードで 1 ~ 16 です。 メッセージ サイズは、キューの作成時に指定されます。 16 ワードを超えるアプリケーション メッセージは、ポインターによって渡す必要があります。 これを実現するには、メッセージ サイズを 1 ワード (ポインターを保持するために十分) としてキューを作成し、メッセージ全体ではなくメッセージ ポインターを送受信します。
メッセージ キューの容量
キューに保持できるメッセージの数は、そのメッセージ サイズと、作成時に指定されたメモリ領域のサイズの関数です。 キューの合計メッセージ容量は、指定されたメモリ領域の合計バイト数を、各メッセージのバイト数で割ることによって計算されます。
たとえば、32 ビット ワードで 1 (4 バイト) のメッセージ サイズをサポートするメッセージ キューが 100 バイトのメモリ領域で作成されている場合、その容量は 25 メッセージになります。
キューのメモリ領域
前述のとおり、メッセージをバッファー処理するためのメモリ領域は、キューの作成時に指定されます。 ThreadX の他のメモリ領域と同様に、ターゲットのアドレス空間内のどこにでも配置できます。
かなりの柔軟性がアプリケーションに提供されるため、これは重要な特徴です。 たとえば、アプリケーションでは、重要なキューのメモリ領域を高速 RAM に配置することによって、パフォーマンスを向上させることができます。
スレッドの中断
アプリケーション スレッドは、キューに対してメッセージを送受信しようとして中断することがあります。 通常、スレッドの中断には、空のキューからのメッセージの待機が伴います。 ただし、満杯になっているキューにメッセージを送信しようとしてスレッドが中断する可能性もあります。
中断の条件が解決されると、要求されたサービスが実行され、待機中のスレッドが再開されます。 同じキューで複数のスレッドが中断された場合、それらは中断された順序で再開されます (FIFO)。
ただし、スレッドの中断を解除するキュー サービスよりも前にアプリケーションで tx_queue_prioritize を呼び出す場合は、優先度による再開も可能です。 キューの優先度付けサービスでは、最も優先度の高いスレッドが中断リストの先頭に配置され、他の中断されたスレッドはすべて、同じ FIFO 順序のまま残ります。
すべてのキューの中断でタイムアウトを使用することもできます。 基本的に、タイムアウトは、スレッドが中断されたままになるタイマー ティックの最大数を指定します。 タイムアウトが発生すると、スレッドが再開され、サービスから該当するエラー コードが返されます。
キューの送信通知
アプリケーションによっては、キューにメッセージが配置されるたびに通知を受け取ると役立つ場合があります。 ThreadX では、tx_queue_send_notify サービスを介してこの機能が提供されます。 このサービスにより、指定されたアプリケーション通知関数が指定されたキューに登録されます。 その後、メッセージがキューに送信されるたびに、このアプリケーション通知関数が ThreadX によって呼び出されます。 アプリケーション通知関数内の正確な処理は、アプリケーションによって決定されます。ただし、通常は、新しいメッセージを処理するための適切なスレッドを再開することから成ります。
キュー イベント チェーン™
ThreadX の通知機能を使用して、さまざまな同期イベントを連結できます。 これは通常、1 つのスレッドで複数の同期イベントを処理する必要がある場合に役立ちます。
たとえば、1 つのスレッドで 5 つの異なるキューからのメッセージを処理する必要があり、メッセージがない場合には中断する必要もあるとします。 これは、各キューにアプリケーション通知関数を登録し、追加のカウント セマフォを導入することによって簡単に実現できます。 具体的には、アプリケーション通知関数は、呼び出されるたびに tx_semaphore_put を実行します (セマフォのカウントは 5 つのキューすべてのメッセージの合計数を表します)。 処理中のスレッドは、tx_semaphore_get サービスを介してこのセマフォで中断します。 セマフォが使用可能になると (この場合は、メッセージが利用可能になったとき)、処理中のスレッドが再開されます。 次に、各キューにメッセージがあるか問い合わせ、見つかったメッセージを処理し、もう一度 tx_semaphore_get を実行して次のメッセージを待機します。 イベントチェーンを使用せずにこれを実現することは非常に困難であり、多くのスレッドや追加のアプリケーション コードが必要になる可能性があります。
一般的には、イベント連結によってスレッド、オーバーヘッド、RAM 要件が減少します。 より複雑なシステムの同期要件を処理するための、柔軟性が高いメカニズムも提供されます。
実行時のキュー パフォーマンス情報
ThreadX では、実行時のキュー パフォーマンス情報がオプションで提供されます。 ThreadX ライブラリとアプリケーションが TX_QUEUE_ENABLE_PERFORMANCE_INFO を定義してビルドされている場合、ThreadX では次の情報が蓄積されます。
システム全体の合計数:
送信されたメッセージ
受信したメッセージ
キューの空の中断
キューの満杯の中断
キュー満杯エラー リターン (中断未指定)
キュー タイムアウト
キューごとの合計数:
送信されたメッセージ
受信したメッセージ
キューの空の中断
キューの満杯の中断
キュー満杯エラー リターン (中断未指定)
キュー タイムアウト
この情報は、実行時にサービス tx_queue_performance_info_get および tx_queue_performance_system_info_get を通じて取得できます。 キュー パフォーマンス情報は、アプリケーションが正しく動作しているかどうかを判断するのに役立ちます。 アプリケーションの最適化にも役立ちます。 たとえば、"キューの満杯の中断" が比較的多い場合は、キューのサイズを大きくすることが有益となる可能性を示唆しています。
キュー制御ブロック TX_QUEUE
各メッセージ キューの特性は、その制御ブロックにあります。 これには、キュー内のメッセージの数などの興味深い情報が格納されます。 この構造体は tx_api.h ファイルで定義されています。
メッセージ キュー制御ブロックは、メモリ内の任意の場所に配置することもできますが、最も一般的なのは、制御ブロックを任意の関数のスコープ外に定義してグローバルな構造体にすることです。
メッセージ送信先の落とし穴
前述のとおり、メッセージはキュー領域とアプリケーション データ領域の間でコピーされます。 受信したメッセージの送信先がメッセージ全体を保持するのに十分な大きさであることを確認することが重要です。 そうでない場合は、メッセージの送信先に続くメモリが破損する可能性があります。
Note
これは、小さすぎるメッセージ送信先がスタック上にある場合には特に致命的です。関数の戻り先アドレスの破壊ほどひどいことはありません。
カウント セマフォ
ThreadX には、0 ~ 4,294,967,295 の値を範囲とする 32 ビットのカウント セマフォが用意されています。 カウント セマフォには 2 つの操作 tx_semaphore_get および tx_semaphore_put があります。 get 操作によってセマフォは 1 減ります。 セマフォが 0 の場合、get 操作は成功しません。 get 操作の逆が put 操作です。 これによってセマフォは 1 増えます。
各カウント セマフォはパブリック リソースです。 ThreadX では、カウント セマフォの使用方法に関する制約はありません。
通常、カウント セマフォは "相互排他" のために使用されます。 ただし、カウント セマフォは、イベント通知の方法としても使用できます。
相互排他
相互排他は、特定のアプリケーション領域 ("クリティカル セクション" や "アプリケーション リソース" とも呼ばれます) へのスレッドのアクセスを制御することに関連します。 相互排他のために使用する場合、セマフォの "現在のカウント" は、アクセスが許可されるスレッドの合計数を表します。 ほとんどの場合、相互排他のために使用されるカウント セマフォには初期値 1 が割り当てられます。これは、関連付けられたリソースに一度にアクセスできるスレッドは 1 つだけであることを意味します。 0 または 1 の値のみを取るカウント セマフォは、一般に "バイナリ セマフォ" と呼ばれます。
重要
バイナリ セマフォが使用されている場合、ユーザーは、同じスレッドが既に所有しているセマフォに対して get 操作を実行できないようにする必要があります。 2 番目の get は失敗し、呼び出し元スレッドが無期限に中断され、リソースが恒久的に利用不可になる可能性があります。
イベント通知
また、プロデューサー/コンシューマー形式でカウント セマフォをイベント通知として使用することもできます。 何かが使用可能になるたびにプロデューサーはカウント セマフォを増やし、コンシューマーはセマフォの取得を試みます。 通常、このようなセマフォの初期値は 0 であり、プロデューサーからコンシューマーに何かを提供できるようになるまでは増加しません。 イベント通知に使用されるセマフォでは、tx_semaphore_ceiling_put サービス呼び出しを使用することにもメリットがあります。 このサービスにより、セマフォのカウントが呼び出しで指定された値を超えないようにすることができます。
カウント セマフォの作成
カウント セマフォは、初期化中または実行時にアプリケーション スレッドによって作成されます。 セマフォの初期カウントは、作成時に指定されます。 アプリケーションでのカウント セマフォの数に制限はありません。
スレッドの中断
アプリケーション スレッドは、現在のカウントが 0 のセマフォで get 操作を実行しようとしている間、中断することがあります。
put 操作が実行されると、中断されたスレッドの get 操作が実行され、スレッドが再開されます。 同じカウント セマフォで複数のスレッドが中断されている場合、それらは中断されたのと同じ順序で再開されます (FIFO)。
ただし、アプリケーションでは、スレッドの中断を解除するセマフォ put 呼び出しの前に tx_semaphore_prioritize を呼び出すと、優先度による再開も可能です。 セマフォの優先度付けサービスでは、最も優先度の高いスレッドが中断リストの先頭に配置され、他の中断されたスレッドはすべて、同じ FIFO 順序のまま残ります。
セマフォ put 通知
アプリケーションによっては、セマフォが put されるたびに通知を受け取ると役立つ場合があります。 ThreadX では、tx_semaphore_put_notify サービスを介してこの機能が提供されます。 このサービスは、指定されたアプリケーション通知関数を指定されたセマフォに登録します。 その後、セマフォが put されるたびに、このアプリケーション通知関数が ThreadX によって呼び出されます。 アプリケーション通知関数内の正確な処理は、アプリケーションによって決定されます。ただし、通常は、新しいセマフォ put イベントを処理するための適切なスレッドを再開することから成ります。
セマフォ イベント チェーン™
ThreadX の通知機能を使用して、さまざまな同期イベントを連結できます。 これは通常、1 つのスレッドで複数の同期イベントを処理する必要がある場合に役立ちます。
たとえば、キュー メッセージ、イベント フラグ、セマフォに対して個別のスレッドを中断する代わりに、アプリケーションではオブジェクトごとに通知ルーチンを登録できます。 呼び出されると、アプリケーション通知ルーチンでは次に、1 つのスレッドを再開できます。これにより、各オブジェクトを問い合わせて新しいイベントを検索および処理できます。
一般的には、イベント連結によってスレッド、オーバーヘッド、RAM 要件が減少します。 より複雑なシステムの同期要件を処理するための、柔軟性が高いメカニズムも提供されます。
実行時のセマフォ パフォーマンス情報
ThreadX では、実行時のセマフォ パフォーマンス情報がオプションで提供されます。 ThreadX ライブラリとアプリケーションが TX_SEMAPHORE_ENABLE_PERFORMANCE_INFO を定義してビルドされている場合、ThreadX では次の情報が蓄積されます。
システム全体の合計数:
セマフォ put
セマフォ get
セマフォ get の中断
セマフォ get タイムアウト
セマフォごとの合計数:
セマフォ put
セマフォ get
セマフォ get の中断
セマフォ get タイムアウト
この情報は、実行時にサービス tx_semaphore_performance_info_get および tx_semaphore_performance_system_info_get を通じて取得できます。 セマフォ パフォーマンス情報は、アプリケーションが正しく動作しているかどうかを判断するのに役立ちます。 アプリケーションの最適化にも役立ちます。 たとえば、"セマフォ get タイムアウト" が比較的多い場合は、他のスレッドによってリソースが長時間保持されていることを示唆している可能性があります。
セマフォ制御ブロック TX_SEMAPHORE
各カウント セマフォの特性は、その制御ブロックにあります。 これには、セマフォの現在のカウントなどの情報が格納されます。 この構造体は tx_api.h ファイルで定義されています。
セマフォ制御ブロックは、メモリ内の任意の場所に配置できますが、最も一般的なのは、制御ブロックを任意の関数のスコープ外に定義してグローバルな構造体にすることです。
破壊的な支配
相互排他のために使用されるセマフォに関連する落とし穴のうち、最も興味深く危険なものの 1 つは、"破壊的な支配" です。 破壊的な支配、つまり "デッドロック" とは、2 つ以上のスレッドが、既に互いに所有されているセマフォを get しようとして、無期限に中断される状態です。
この状態は、2 つのスレッドと 2 つのセマフォの例で示すとよくわかります。 1 つ目のスレッドが 1 つ目のセマフォを所有し、2 つ目のスレッドが 2 つ目のセマフォを所有しているとします。 1 つ目のスレッドが 2 つ目のセマフォを get しようとしたときに、同時に 2 つ目のスレッドが 1 つ目のセマフォを get しようとすると、両方のスレッドがデッドロック状態になります。 さらに、これらのスレッドが無期限に中断されたままになると、関連付けられているリソースも無期限にロックアウトされます。 図 8 は、この例を示しています。
破壊的な支配 (例)
図 8. 中断されたスレッドの例
リアルタイム システムでは、スレッドによるセマフォ獲得の方法に特定の制限を設けることによって、破壊的な支配を回避できます。 スレッドで所有できるセマフォは一度に 1 つだけです。 または、スレッドで複数のセマフォを所有できるのは、それらを同じ順序で収集する場合です。 前の例では、1 つ目と 2 つ目のスレッドで、1 つ目と 2 つ目のセマフォを順に取得する場合、破壊的な支配は回避されます。
ヒント
get 操作に関連付けられた中断タイムアウトを使用して、破壊的な支配から回復することもできます。
優先度の逆転
相互排他セマフォに関連するもう 1 つの落とし穴は、優先度の逆転です。 このトピックについては、「スレッド優先度の落とし穴」で詳しく説明されています。
基本的な問題は、優先度の高いスレッドが必要とするセマフォが、優先度の低いスレッドに所有されている状況によって生じます。 このこと自体は正常です。 ただし、それらの中間の優先度を持つスレッドによって、優先度の逆転が不確定な時間にわたって続く場合があります。 これには、スレッドの優先度を慎重に選択すること、プリエンプションしきい値を使用すること、および、リソースを所有しているスレッドの優先度を優先度の高いスレッドのものまで一時的に上げることで対処できます。
ミューテックス
セマフォに加えて、ThreadX ではミューテックス オブジェクトも提供されています。 ミューテックスは基本的にはバイナリ セマフォです。したがって、一度に 1 つのスレッドのみがミューテックスを所有できます。 また、同じスレッドが、所有しているミューテックスに対してミューテックス get 操作を複数回、正常に実行することができます。正確には、4,294,967,295 回です。 ミューテックス オブジェクトには 2 つの操作 tx_mutex_get および tx_mutex_put があります。 get 操作は、他のスレッドによって所有されていないミューテックスを獲得し、put 操作は、以前に獲得したミューテックスを解放します。 スレッドでミューテックスを解放するには、put 操作の回数が前の get 操作の回数と同じである必要があります。
各ミューテックスはパブリック リソースです。 ThreadX では、ミューテックスの使用方法に関する制約はありません。
ThreadX のミューテックスは "相互排他" のためだけに使用されます。 カウント セマフォとは異なり、ミューテックスはイベント通知の方法としては使用されません。
ミューテックス相互排他
カウント セマフォのセクションの説明と同様に、相互排他は、特定のアプリケーション領域 ("クリティカル セクション" や "アプリケーション リソース" とも呼ばれます) へのスレッドのアクセスを制御することに関連します。 使用可能な場合、ThreadX のミューテックスの所有権カウントは 0 になります。 ミューテックスがスレッドによって獲得された後は、ミューテックスに対して get 操作が正常に実行されるたびに所有権カウントが 1 回インクリメントされ、put 操作が正常に実行されるたびにデクリメントされます。
ミューテックスの作成
ThreadX のミューテックスは、初期化中または実行時にアプリケーション スレッドによって作成されます。 ミューテックスの初期状態は常に "使用可能" です。また、"優先度の継承" を選択してミューテックスを作成することもできます。
スレッドの中断
アプリケーション スレッドは、別のスレッドによって既に所有されているミューテックスに対して get 操作を実行しようとしている間、中断することがあります。
所有側スレッドによって同じ数の put 操作が実行されると、中断されたスレッドの get 操作が実行され、ミューテックスの所有権が与えられ、スレッドが再開されます。 同じミューテックスで複数のスレッドが中断されている場合、それらは中断されたのと同じ順序で再開されます (FIFO)。
ただし、作成時にミューテックスの優先度の継承を選択した場合は、優先度による再開が自動的に行われます。 アプリケーションで、スレッドの中断を解除するミューテックス put 呼び出しの前に tx_mutex_prioritize を呼び出すと、優先度による再開も可能です。 ミューテックスの優先度付けサービスでは、最も優先度の高いスレッドが中断リストの先頭に配置され、他の中断されたスレッドはすべて、同じ FIFO 順序のまま残ります。
実行時のミューテックス パフォーマンス情報
ThreadX では、実行時のミューテックス パフォーマンス情報がオプションで提供されます。 ThreadX ライブラリとアプリケーションが TX_MUTEX_ENABLE_PERFORMANCE_INFO を定義してビルドされている場合、ThreadX では次の情報が蓄積されます。
システム全体の合計数:
ミューテックス put
ミューテックス get
ミューテックス get の中断
ミューテックス get タイムアウト
ミューテックス優先度逆転
ミューテックス優先度継承
ミューテックスごとの合計数:
ミューテックス put
ミューテックス get
ミューテックス get の中断
ミューテックス get タイムアウト
ミューテックス優先度逆転
ミューテックス優先度継承
この情報は、実行時にサービス tx_mutex_performance_info_get および tx_mutex_performance_system_info_get を通じて取得できます。 ミューテックス パフォーマンス情報は、アプリケーションが正しく動作しているかどうかを判断するのに役立ちます。 アプリケーションの最適化にも役立ちます。 たとえば、"ミューテックス get タイムアウト" が比較的多い場合は、他のスレッドによってリソースが長時間保持されていることを示唆している可能性があります。
ミューテックス制御ブロック TX_MUTEX
各ミューテックスの特性は、その制御ブロックにあります。 これには、ミューテックスを所有しているスレッドのポインターと共に、ミューテックスの現在の所有権カウントなどの情報が含まれます。 この構造体は tx_api.h ファイルで定義されています。 ミューテックス制御ブロックは、メモリ内の任意の場所に配置できますが、最も一般的なのは、制御ブロックを任意の関数のスコープ外に定義してグローバルな構造体にすることです。
破壊的な支配
ミューテックスの所有権に関連する落とし穴のうち、最も興味深く危険なものの 1 つは、"破壊的な支配" です。 破壊的な支配、つまり "デッドロック" とは、2 つ以上のスレッドが、他のスレッドによって既に所有されているミューテックスを get しようとしている間、無期限に中断される状態です。 "破壊的な支配" とその対処方法に関する説明は、ミューテックス オブジェクトに対しても完全に有効です。
優先度の逆転
前述のとおり、相互排他に関連する大きな落とし穴は、優先度の逆転です。 このトピックについては、「スレッド優先度の落とし穴」で詳しく説明されています。
基本的な問題は、優先度の高いスレッドが必要とするセマフォが、優先度の低いスレッドに所有されている状況によって生じます。 このこと自体は正常です。 ただし、それらの中間の優先度を持つスレッドによって、優先度の逆転が不確定な時間にわたって続く場合があります。 前述のセマフォとは異なり、ThreadX のミューテックス オブジェクトにはオプションの "優先順位継承" があります。 優先度継承の背後にある基本的な考え方は、優先度の低いスレッドによって所有されているミューテックスを必要としている優先度の高いスレッドがあるとき、その優先度まで優先度の低いスレッドを一時的に引き上げることです。 優先度の低いスレッドがミューテックスを解放すると、その優先度は元に戻り、優先度の高いスレッドにミューテックスの所有権が与えられます。 この機能により、優先度の逆転時間は、優先度の低いスレッドがミューテックスを保持している時間に限られるため、不確定な優先度の逆転が排除されます。 もちろん、この章の前半で説明した、不確定な優先度の逆転に対処する手法も、ミューテックスでも有効です。
イベント フラグ
イベント フラグは、スレッドの同期のための強力なツールです。 各イベント フラグは、1 つのビットで表されます。 イベント フラグは、32 個ずつのグループに編成されます。 スレッドでは、グループ内の 32 個のイベント フラグすべてを同時に操作できます。 イベントは tx_event_flags_set で設定し、tx_event_flags_get で取得します。
イベント フラグの設定は、現在のイベント フラグと新しいイベント フラグとの間で論理 AND/OR 演算を使用して行われます。 論理演算の種類 (AND または OR) は、tx_event_flags_set の呼び出しで指定します。
イベント フラグの取得についても、類似の論理オプションがあります。 get 要求では、指定したすべてのイベント フラグが必要であることを指定できます (論理 AND)。
また、get 要求では、指定したイベント フラグのどれであっても要求を満たすことを指定できます (論理 OR)。 イベント フラグの取得に関連する論理演算の種類は、tx_event_flags_get の呼び出しで指定します。
重要
get 要求を満たすイベント フラグは消費されます。つまり、要求で TX_OR_CLEAR または TX_AND_CLEAR が指定されている場合は 0 に設定されます。
各イベント フラグ グループはパブリック リソースです。 ThreadX では、イベント フラグ グループの使用方法に関する制約はありません。
イベント フラグ グループの作成
イベント フラグ グループは、初期化中または実行時にアプリケーション スレッドによって作成されます。 作成時に、グループ内のすべてのイベント フラグが 0 に設定されます。 アプリケーションでのイベント フラグ グループの数に制限はありません。
スレッドの中断
アプリケーション スレッドは、イベント フラグの論理的な組み合わせをグループから取得しようとしている間、中断することがあります。 イベント フラグが設定されると、すべての中断されたスレッドの get 要求が確認されます。 これで、必要なイベント フラグを取得できたすべてのスレッドが再開されます。
Note
イベント フラグ グループで中断されているスレッドはすべて、そのイベント フラグが設定されたときに確認されます。 もちろん、これには追加のオーバーヘッドが伴います。 そのため、同じイベント フラグ グループを使用するスレッドの数を、妥当な数に制限することをお勧めします。
イベント フラグ設定通知
アプリケーションによっては、イベント フラグが設定されるたびに通知を受け取ると役立つ場合があります。 ThreadX では、tx_event_flags_set_notify サービスを介してこの機能が提供されます。 このサービスは、指定されたアプリケーション通知関数を指定されたイベント フラグ グループに登録します。 その後、そのグループのイベント フラグが設定されるたびに、このアプリケーション通知関数が ThreadX によって呼び出されます。 アプリケーション通知関数内の正確な処理は、アプリケーションによって決定されます。ただし、通常は、新しいイベント フラグを処理するための適切なスレッドを再開することから成ります。
イベント フラグ イベント チェーン™
ThreadX の通知機能を使用して、さまざまな同期イベントを "連結" できます。 これは通常、1 つのスレッドで複数の同期イベントを処理する必要がある場合に役立ちます。
たとえば、キュー メッセージ、イベント フラグ、セマフォに対して個別のスレッドを中断する代わりに、アプリケーションではオブジェクトごとに通知ルーチンを登録できます。 呼び出されると、アプリケーション通知ルーチンでは次に、1 つのスレッドを再開できます。これにより、各オブジェクトを問い合わせて新しいイベントを検索および処理できます。
一般的には、イベント連結によってスレッド、オーバーヘッド、RAM 要件が減少します。 より複雑なシステムの同期要件を処理するための、柔軟性が高いメカニズムも提供されます。
実行時のイベント フラグ パフォーマンス情報
ThreadX では、実行時のイベント フラグ パフォーマンス情報がオプションで提供されます。 ThreadX ライブラリとアプリケーションが TX_EVENT_FLAGS_ENABLE_PERFORMANCE_INFO を定義してビルドされている場合、ThreadX では次の情報が蓄積されます。
システム全体の合計数:
イベント フラグ set
イベント フラグ get
イベント フラグ get の中断
イベント フラグ get タイムアウト
イベント フラグ グループごとの合計数:
イベント フラグ set
イベント フラグ get
イベント フラグ get の中断
イベント フラグ取得タイムアウト
この情報は、実行時にサービス tx_event_flags_performance_info_get および tx_event_flags_performance_system_info_get を通じて取得できます。 イベント フラグのパフォーマンス情報は、アプリケーションが正しく動作しているかどうかを判断するのに役立ちます。 アプリケーションの最適化にも役立ちます。 たとえば、tx_event_flags_get サービスでのタイムアウトが比較的多い場合は、イベント フラグの中断のタイムアウトが短すぎることを示唆している可能性があります。
イベント フラグ グループ制御ブロック TX_EVENT_FLAGS_GROUP
各イベント フラグ グループの特性は、その制御ブロックにあります。 これには、現在のイベント フラグの設定や、イベントのために中断されたスレッドの数などの情報が格納されます。 この構造体は tx_api.h ファイルで定義されています。
イベント グループ制御ブロックは、メモリ内の任意の場所に配置できますが、最も一般的なのは、制御ブロックを任意の関数のスコープ外に定義してグローバルな構造体にすることです。
メモリ ブロック プール
メモリを高速かつ決定論的な方法で割り当てることは、常に、リアルタイム アプリケーションの課題です。 これを念頭に置いて、ThreadX では、固定サイズのメモリ ブロックの複数のプールを作成および管理する機能が提供されています。
メモリ ブロック プールは固定サイズのブロックで構成されているため、断片化の問題は発生しません。 もちろん、断片化によって本質的に非決定論的な動作が発生します。 さらに、固定サイズのメモリ ブロックを割り当てて解放するために必要な時間は、単純なリンク リスト操作と同等です。 さらに、使用可能な一覧の先頭でメモリ ブロックの割り当てと割り当て解除が行われます。 これにより、リンク リストの処理が最速になり、実際のメモリ ブロックをキャッシュに保持するのに役立ちます。
固定サイズのメモリ プールの主な欠点は、柔軟性の欠如です。 プールのブロック サイズは、最悪ケースのユーザーのメモリ要件に対処できる十分な大きさにする必要があります。 もちろん、同じプールに対してさまざまなサイズのメモリ要求が行われた場合、メモリが浪費される可能性があります。 解決策としては、サイズの異なるメモリ ブロックを含む複数のメモリ ブロック プールを作成することが考えられます。
各メモリ ブロック プールはパブリック リソースです。 ThreadX では、プールの使用方法に関する制約はありません。
メモリ ブロック プールの作成
メモリ ブロック プールは、初期化中または実行時にアプリケーション スレッドによって作成されます。 アプリケーションでのメモリ ブロック プールの数に制限はありません。
メモリ ブロック サイズ
前述のとおり、メモリ ブロック プールには、いくつかの固定サイズのブロックが含まれています。 ブロック サイズは、プールの作成時にバイト単位で指定されます。
Note
ThreadX では、プール内の各メモリ ブロックに少量のオーバーヘッド (C ポインターのサイズ) が追加されます。 さらに、ThreadX では、各メモリ ブロックの先頭を適切なアラインメントに保つために、ブロック サイズのパディングが必要な場合があります。
プールの容量
プール内のメモリ ブロック数は、ブロック サイズと、作成時に指定されたメモリ領域の合計バイト数の関数です。 プールの容量は、指定されたメモリ領域の合計バイト数を、ブロック サイズ (パディングとポインターのオーバーヘッド バイトを含む) で割ることによって計算されます。
プールのメモリ領域
前述のとおり、ブロック プールのメモリ領域は作成時に指定されます。 ThreadX の他のメモリ領域と同様に、ターゲットのアドレス空間内のどこにでも配置できます。
非常に高い柔軟性が提供されるため、これは重要な機能です。 たとえば、I/O 用に高速メモリ領域がある通信製品を考えます。 このメモリ領域を ThreadX メモリ ブロック プール内に作成することで、この領域を簡単に管理できます。
スレッドの中断
空のプールからのメモリ ブロックを待機している間に、アプリケーション スレッドが中断する場合があります。 ブロックがプールに返されると、中断されているスレッドにこのブロックが割り当てられ、スレッドが再開されます。
同じメモリ ブロック プールで複数のスレッドが中断された場合、それらは中断された順序で再開されます (FIFO)。
ただし、アプリケーションでは、スレッドの中断を解除するブロック解放呼び出しの前に tx_block_pool_prioritize を呼び出すと、優先度による再開も可能です。 ブロック プールの優先度付けサービスでは、最も優先度の高いスレッドが中断リストの先頭に配置され、他の中断されたスレッドはすべて、同じ FIFO 順序のまま残ります。
実行時のブロック プール パフォーマンス情報
ThreadX では、実行時のブロック プール パフォーマンス情報がオプションで提供されます。 ThreadX ライブラリとアプリケーションが TX_BLOCK_POOL_ENABLE_PERFORMANCE_INFO を定義してビルドされている場合、ThreadX では次の情報が蓄積されます。
システム全体の合計数:
割り当てられたブロック
解放されたブロック
割り当て中断
割り当てタイムアウト
ブロック プールごとの合計数:
割り当てられたブロック
解放されたブロック
割り当て中断
割り当てタイムアウト
この情報は、実行時にサービス tx_block_pool_performance_info_get および tx_block_pool_performance_system_info_get を通じて取得できます。 ブロック プールのパフォーマンス情報は、アプリケーションが正しく動作しているかどうかを判断するのに役立ちます。 アプリケーションの最適化にも役立ちます。 たとえば、"割り当て中断" が比較的多い場合は、ブロック プールが小さすぎることを示唆している可能性があります。
メモリ ブロック プール制御ブロック TX_BLOCK_POOL
各メモリ ブロック プールの特性は、その制御ブロックに含まれています。 これには、使用可能なメモリ ブロックの数や、メモリ プールのブロック サイズなどの情報が格納されます。 この構造体は tx_api.h ファイルで定義されています。
プール制御ブロックは、メモリ内の任意の場所に配置することもできますが、最も一般的なのは、制御ブロックを任意の関数のスコープ外に定義してグローバルな構造体にすることです。
メモリ ブロックの上書き
割り当てられたメモリ ブロックのユーザーによって、その境界の外部に書き込まれないようにすることが重要です。 これが発生すると、隣接する (通常は後続の) メモリ領域で破損が発生します。 結果は予測不可能で、多くの場合、アプリケーションにとって致命的です。
メモリ バイト プール
ThreadX のメモリ バイト プールは、標準 C ヒープに似ています。 標準 C ヒープとは異なり、複数のメモリ バイト プールを持つことが可能です。 また、スレッドは、要求したメモリが使用可能になるまでプールで中断する可能性もあります。
メモリ バイト プールからの割り当ては、従来の malloc 呼び出しに似ています。これには、必要なメモリ量 (バイト単位) が含まれています。 プールからのメモリ割り当ては、"first-fit" の形式で行われます。つまり、要求を満たす最初の空きメモリ ブロックが使用されます。 このブロックの余剰メモリは、新しいブロックに変換され、空きメモリの一覧に戻されます。 このプロセスは "断片化" と呼ばれます。
以降の割り当てで十分な大きさの空きメモリ ブロックが検索されるときに、隣接する空きメモリ ブロックどうしが "マージ" されます。 このプロセスは "最適化" と呼ばれます。
各メモリ バイト プールはパブリック リソースです。 ThreadX では、ISR からメモリ バイト サービスを呼び出すことができない点を除いて、プールの使用方法に関する制約はありません。
メモリ バイト プールの作成
メモリ バイト プールは、初期化中または実行時にアプリケーション スレッドによって作成されます。 アプリケーションでのメモリ バイト プールの数に制限はありません。
プールの容量
メモリ バイト プールの割り当て可能なバイト数は、作成時に指定した値より若干少なくなります。 これは、空きメモリ領域の管理によってオーバーヘッドが若干発生するためです。 プール内の各空きメモリ ブロックに、2 つの C ポインターに相当するオーバーヘッドが必要です。 また、プールは 2 つのブロックで作成されます。大きな空きブロックと、メモリ領域の末尾に永続的に割り当てられた小さなブロックです。 この割り当てられたブロックは、割り当てアルゴリズムのパフォーマンスを向上させるために使用されます。 これにより、マージ中にプール領域の末尾を継続的にチェックする必要がなくなります。
実行時には、通常、プールのオーバーヘッドの量が増加します。 奇数のバイト数の割り当てでは、次のメモリ ブロックの適切なアラインメントを保つためにパディングが行われます。 さらに、プールの断片化が進むにつれて、オーバーヘッドが増加します。
プールのメモリ領域
メモリ バイト プールのメモリ領域は、作成時に指定されます。 ThreadX の他のメモリ領域と同様に、ターゲットのアドレス空間内のどこにでも配置できます。 非常に高い柔軟性が提供されるため、これは重要な機能です。 たとえば、ターゲット ハードウェアに高速メモリ領域と低速メモリ領域がある場合、ユーザーは、各領域にプールを作成することによって、両方の領域のメモリ割り当てを管理できます。
スレッドの中断
アプリケーション スレッドは、プールからのメモリ バイトを待機している間、中断することがあります。 十分な連続したメモリが使用可能になると、中断されているスレッドにその要求したメモリが与えられ、スレッドが再開されます。
同じメモリ バイト プールで複数のスレッドが中断された場合、それらは中断された順序でメモリを付与 (再開) されます (FIFO)。
ただし、アプリケーションでは、スレッドの中断を解除するバイト解放呼び出しの前に tx_byte_pool_prioritize を呼び出すと、優先度による再開も可能です。 バイト プールの優先度付けサービスでは、最も優先度の高いスレッドが中断リストの先頭に配置され、他の中断されたスレッドはすべて、同じ FIFO 順序のまま残ります。
実行時のバイト プール パフォーマンス情報
ThreadX では、実行時のバイト プール パフォーマンス情報がオプションで提供されます。 ThreadX ライブラリとアプリケーションが TX_BYTE_POOL_ENABLE_PERFORMANCE_INFO を定義してビルドされている場合、ThreadX では次の情報が蓄積されます。
システム全体の合計数:
割り当て
リリース
検索されたフラグメント
マージされたフラグメント
作成されたフラグメント
割り当て中断
割り当てタイムアウト
バイト プールごとの合計数:
割り当て
リリース
検索されたフラグメント
マージされたフラグメント
作成されたフラグメント
割り当て中断
割り当てタイムアウト
この情報は、実行時にサービス tx_byte_pool_performance_info_get および tx_byte_pool_performance_system_info_get を通じて取得できます。 バイト プールのパフォーマンス情報は、アプリケーションが正しく動作しているかどうかを判断するのに役立ちます。 アプリケーションの最適化にも役立ちます。 たとえば、"割り当て中断" が比較的多い場合は、バイト プールが小さすぎることを示唆している可能性があります。
メモリ バイト プール制御ブロック TX_BYTE_POOL
各メモリ バイト プールの特性は、その制御ブロックに含まれています。 これには、プールで使用可能なバイト数などの有用な情報が格納されます。 この構造体は tx_api.h ファイルで定義されています。
プール制御ブロックは、メモリ内の任意の場所に配置することもできますが、最も一般的なのは、制御ブロックを任意の関数のスコープ外に定義してグローバルな構造体にすることです。
不確定な動作
メモリ バイト プールでは、最も柔軟性の高いメモリ割り当てが提供されますが、やや不確定な動作にも悩まされます。 たとえば、メモリ バイト プールに 2,000 バイトのメモリがあっても、1,000 バイトの割り当て要求を満たすことができない場合があります。 これは、連続している空きバイトの数が保証されないためです。 1,000 バイトの空きブロックが存在する場合でも、そのブロックを見つけるのにどれだけの時間がかかるかは保証されません。 1,000 バイトのブロックを見つけるために、メモリ プール全体を検索する必要がある場合もあります。
ヒント
メモリ バイト プールの動作が不確定であるため、一般的には、確定的なリアルタイムの動作が必要な領域では、メモリ バイト サービスを使用しないようにすることをお勧めします。 多くのアプリケーションでは、初期化中または実行時の構成中に、必要なメモリが事前に割り当てられます。
メモリ ブロックの上書き
割り当てられたメモリのユーザーによって、その境界の外部に書き込まれないようにすることが重要です。 これが発生すると、隣接する (通常は後続の) メモリ領域で破損が発生します。 結果は予測不可能で、多くの場合、プログラムの実行にとって致命的です。
アプリケーション タイマー
非同期外部イベントへの迅速な対応は、リアルタイム埋め込みアプリケーションの最も重要な機能です。 ただし、これらのアプリケーションの多くは、事前に決められた時間間隔で特定のアクティビティを実行する必要もあります。
ThreadX のアプリケーション タイマーにより、特定の時間間隔でアプリケーション C 関数を実行する機能がアプリケーションに提供されます。 アプリケーション タイマーが 1 回だけ時間切れになる場合もあります。 この種類のタイマーは "ワンショット タイマー" と呼ばれ、繰り返すインターバル タイマーは "定期タイマー" と呼ばれます。
各アプリケーション タイマーはパブリック リソースです。 ThreadX では、アプリケーション タイマーの使用方法に関する制約はありません。
タイマー間隔
ThreadX の時間間隔は、周期的なタイマー割り込みによって測定されます。 各タイマー割り込みは、タイマー "刻み" と呼ばれます。 タイマー ティック間の実際の時間はアプリケーションによって指定されますが、ほとんどの実装では 10 ミリ秒が標準です。 定期タイマーの設定は、通常、tx_initialize_low_level アセンブリ ファイルにあります。
アプリケーション タイマーが機能するためには、基になるハードウェアが周期的な割り込みを生成する機能を備えている必要があることに注意してください。 場合によっては、プロセッサに周期的な割り込み機能が組み込まれていることもあります。 プロセッサがこの機能を備えていない場合は、周期的な割り込みを生成できる周辺機器がユーザーのボードに搭載されている必要があります。
重要
ThreadX は、周期的な割り込みのソースがなくても機能します。 ただし、タイマーに関連するすべての処理が無効になります。 これには、タイム スライシング、中断タイムアウト、およびタイマー サービスが含まれます。
タイマー精度
タイマーの有効期限は、ティックを単位として指定されます。 指定された有効期限の値は、タイマー ティックごとに 1 だけ減らされます。 タイマー割り込み (つまりタイマー ティック) の直前にアプリケーション タイマーを有効にすることもできるため、実際の期限切れは最大 1 ティック早くなる可能性があります。
タイマー ティックの速度が 10 ミリ秒である場合、アプリケーション タイマーは最大 10 ミリ秒早く期限切れになる可能性があります。 これには、1 秒タイマーよりも 10 ミリ秒タイマーの方が大きな影響を受けます。 もちろん、タイマー割り込みの頻度を高くすると、このような誤差の範囲は減少します。
タイマー実行
アプリケーション タイマーは、アクティブになった順序で実行されます。 たとえば、3 つのタイマーが同じ有効期限値で作成され、アクティブ化された場合、対応する有効期限関数は、アクティブ化された順序で実行されることが保証されます。
アプリケーション タイマーの作成
アプリケーション タイマーは、初期化中または実行時にアプリケーション スレッドによって作成されます。 アプリケーションでのアプリケーション タイマーの数に制限はありません。
実行時のアプリケーション タイマー パフォーマンス情報
ThreadX では、実行時のアプリケーション タイマー パフォーマンス情報がオプションで提供されます。 ThreadX ライブラリとアプリケーションが TX_TIMER_ENABLE_PERFORMANCE_INFO を定義してビルドされている場合、ThreadX では次の情報が蓄積されます。
システム全体の合計数:
アクティブ化
非アクティブ化
再アクティブ化 (定期タイマー)
expirations
有効期限調整
アプリケーション タイマーごとの合計数:
アクティブ化
非アクティブ化
再アクティブ化 (定期タイマー)
expirations
有効期限調整
この情報は、実行時にサービス tx_timer_performance_info_get および tx_timer_performance_system_info_get を通じて取得できます。 アプリケーション タイマーのパフォーマンス情報は、アプリケーションが正しく動作しているかどうかを判断するのに役立ちます。 アプリケーションの最適化にも役立ちます。
アプリケーション タイマー制御ブロック TX_TIMER
各アプリケーション タイマーの特性は、その制御ブロックにあります。 これには、32 ビットの有効期限識別値などの有用な情報が格納されます。 この構造体は tx_api.h ファイルで定義されています。
アプリケーション タイマー制御ブロックは、メモリ内の任意の場所に配置できますが、最も一般的なのは、制御ブロックを任意の関数のスコープ外に定義してグローバルな構造体にすることです。
過剰なタイマー
既定では、アプリケーション タイマーは、優先度 0 で実行される隠しシステム スレッド内から実行されます。これは通常、どのアプリケーション スレッドよりも高くなります。 このため、アプリケーション タイマー内の処理は最小限に抑える必要があります。
また、可能な限り、タイマー ティックごとに期限切れになるタイマーを回避することも重要です。 このような状況では、アプリケーションで過剰なオーバーヘッドが発生する可能性があります。
重要
前述のように、アプリケーション タイマーは、隠しシステム スレッドから実行されます。 したがって、アプリケーション タイマーの有効期限関数内から行われる ThreadX サービスの呼び出しでは、中断を選択しないことが重要です。
相対時間
前述のアプリケーション タイマーに加えて、ThreadX には、継続的にインクリメントする 32 ビットの刻みカウンターが 1 つ用意されています。 刻みカウンターつまり "時間" は、タイマー割り込みごとに 1 つずつ増加します。
アプリケーションでは、それぞれ tx_time_get および tx_time_set の呼び出しを通じて、この 32 ビット カウンターを読み取りまたは設定できます。 この刻みカウンターの使用は、全面的にアプリケーションによって決定されます。 ThreadX によって内部的に使用されることはありません。
割り込み
非同期イベントへの迅速な対応は、リアルタイム埋め込みアプリケーションの主要な機能です。 アプリケーションでは、そのようなイベントが存在することをハードウェア割り込みによって認識します。
割り込みは、プロセッサの実行の非同期的な変更です。 通常、割り込みが発生すると、"割り込み" プロセッサは、現在の実行の小さな部分をスタック上に保存し、該当する割り込みベクターに制御を移します。 割り込みベクターは、基本的に、特定の種類の割り込みを処理するルーチンのアドレスにすぎません。 正確な割り込み処理手順は、プロセッサに固有のものです。
割り込み制御
tx_interrupt_control サービスを使用すると、アプリケーションで割り込みを有効または無効にすることができます。 このサービスによって、前の割り込み有効/無効の状態が返されます。 割り込み制御は現在実行中のプログラム セグメントにのみ影響することに注意してください。 たとえば、あるスレッドで割り込みを無効にした場合は、そのスレッドの実行中にのみ無効のままになります。
Note
マスク不可能割り込み (NMI) は、ハードウェアによって無効にできない割り込みです。 このような割り込みは、ThreadX アプリケーションによって使用される場合があります。 ただし、アプリケーションの NMI 処理ルーチンは、ThreadX のコンテキスト管理または何らかの API サービスを使用することを許可されていません。
ThreadX で管理される割り込み
ThreadX により、アプリケーションに完全な割り込み管理が提供されます。 この管理には、割り込まれた実行のコンテキストの保存と復元が含まれます。 さらに、ThreadX では、割り込みサービス ルーチン (ISR) 内から特定のサービスを呼び出すことができます。 アプリケーションの ISR から許可されている ThreadX サービスの一覧を次に示します。
tx_block_allocate
tx_block_pool_info_get tx_block_pool_prioritize
tx_block_pool_performance_info_get
tx_block_pool_performance_system_info_get tx_block_release
tx_byte_pool_info_get tx_byte_pool_performance_info_get
tx_byte_pool_performance_system_info_get
tx_byte_pool_prioritize tx_event_flags_info_get
tx_event_flags_get tx_event_flags_set
tx_event_flags_performance_info_get
tx_event_flags_performance_system_info_get
tx_event_flags_set_notify tx_interrupt_control
tx_mutex_performance_info_get
tx_mutex_performance_system_info_get tx_queue_front_send
tx_queue_info_get tx_queue_performance_info_get
tx_queue_performance_system_info_get tx_queue_prioritize
tx_queue_receive tx_queue_send tx_semaphore_get
tx_queue_send_notify tx_semaphore_ceiling_put
tx_semaphore_info_get tx_semaphore_performance_info_get
tx_semaphore_performance_system_info_get
tx_semaphore_prioritize tx_semaphore_put tx_thread_identify
tx_semaphore_put_notify tx_thread_entry_exit_notify
tx_thread_info_get tx_thread_resume
tx_thread_performance_info_get
tx_thread_performance_system_info_get
tx_thread_stack_error_notify tx_thread_wait_abort tx_time_get
tx_time_set tx_timer_activate tx_timer_change
tx_timer_deactivate tx_timer_info_get
tx_timer_performance_info_get
tx_timer_performance_system_info_get
重要
ISR からの中断は許可されていません。 したがって、ISR から実行されるすべての ThreadX サービス呼び出しの wait_option パラメーターは、TX_NO_WAIT に設定する必要があります。
ISR テンプレート
アプリケーションの割り込みを管理するには、アプリケーションの ISR の先頭と末尾で、いくつかの ThreadX ユーティリティを呼び出す必要があります。 割り込み処理の正確な形式は、ポートによって異なります。
次の小さなコード セグメントは、ThreadX で管理される ISR のほとんどで典型的なものです。 ほとんどの場合、この処理はアセンブリ言語です。
_application_ISR_vector_entry:
; Save context and prepare for
; ThreadX use by calling the ISR
; entry function.
CALL _tx_thread_context_save
; The ISR can now call ThreadX
; services and its own C functions
; When the ISR is finished, context
; is restored (or thread preemption)
; by calling the context restore ; function. Control does not return!
JUMP _tx_thread_context_restore
高頻度の割り込み
一部の割り込みは非常に高頻度で発生するため、割り込みのたびに完全なコンテキストの保存と復元を行うと、処理帯域幅が過剰に消費されます。 このような場合、アプリケーションでは、このような高頻度の割り込みの大部分に対して限られた量の処理を実行する、アセンブリ言語の小規模な ISR を使用するのが一般的です。
特定の時点以降、この小規模な ISR では ThreadX との対話が必要になる場合があります。 これは、上記のテンプレートで説明されている entry 関数と exit 関数を呼び出すことによって実現されます。
割り込み待機時間
ThreadX では、短期間だけ割り込みがロックアウトされます。 割り込みが無効になる最大時間は、スレッドのコンテキストを保存または復元するために必要な時間とほぼ等しくなります。