本文聊聊临界区,以及RT-Thread对临界区的处理,
通过源码分析一下 RT-Thread 对临界区保护的实现以及与 FreeRTOS 处理的不同。
コンテンツ
序文
なぜクリティカルセクションについて話すのですか?
RT-Threadのクリティカルセクションは、スレッドの順次実行、つまりスレッド同期の問題に関連しているためです。
RTOSを使用する場合、複数の実行中のスレッドがグローバル変数などの重要なリソースにアクセスする必要があることがよくあります。特定の保護対策を講じないと、プログラムの動作で予期しない結果が発生する可能性があります。
RT-Threadは、クリティカルセクションを保護するためのさまざまな方法を提供します。この記事では、主にシステムスケジューリングを閉じて割り込みを無効にする方法について説明します。
1.クリティカルエリア
クリティカルセクションやクリティカルリソースなどの用語をよく耳にしますが、クリティカルセクションやクリティカルリソースとは何ですか?
1.1クリティカルセクションとは
Baidu百科事典の説明を見てみましょう
。簡単な要約は写真の2つの文です。
- 重要なリソース
一度に1つのプロセスでのみ使用できる共有リソース - クリティカルセクション
クリティカルリソースにアクセスする各プロセスのコードは、クリティカルセクションと呼ばれます。
1.2RTOSのクリティカルセクション
マルチタスクRTOSの場合、外部割り込み、独自のマルチスレッド、システムスケジューリングメカニズムに加えて、複数のスレッドが共有リソースにアクセスする可能性があります。データの信頼性と整合性を確保するには、共有リソースを相互に排他的に保護する必要があります。アクセス(グローバル変数など)。
1つ目は、最も基本的な例である外部割り込みです。これはRTOSだけでなく、フロントエンドシステムとバックエンドシステムにも存在します。
上記の例では、クリティカルセクション保護がスレッド機能に追加されている場合、スレッドはクリティカルの動作まで割り込みに応答しません。リソースaが完成していれば問題ありません。
スレッド間のクリティカルリソースへのアクセスの例を見てみましょう。
上の図の例では(delay(1)
クロックビートと同じかもしれませんが、少し問題があるかもしれません。もう少し遅延が必要かもしれません。ここでは問題ありません。もつれないでください==!)、クリティカルセクション保護がない場合に発生する問題を分析しました(問題がある場合は指摘してください)。実際のプログラム結果は、プログラムが本来意図した結果ではない可能性があります。この種のエラーは回避する必要があります!
この要約の次の内容には、以下のクリティカルセクションの保護のソースコード分析が含まれています。これは詳細な説明です。理解しているかどうかは、RTOSスケジューリングが関係しているため、RT-Threadクリティカルセクション保護の使用方法の学習には影響しません。原則、PendSVの例外、およびその他の知識には特定の基礎が必要です。ここでは、RTOSを学びたい友人は、ドキュメント「Cortex-M3およびCortex-M4権威ガイド」をよく読むことをお勧めします。
上記の例を理解することは、RTOSのスケジューリングの原則に関連しています。上記の説明で使用される割り込みは、スレッドを中断してサイトに保存し、サイトに復元して、スレッドをスケジュールします。RTOSのスケジューリング原理をある程度理解する必要があります。RTOSでは、スレッドの実行を中断する外部割り込みに加えて、Systick割り込みと重要なPendSV例外もあります。
PendSVは、一時停止可能なシステムコールとも呼ばれます。これは、通常の割り込みのように一時停止できる例外であり、オペレーティングシステムのコンテキスト切り替えを支援するために特別に使用されます。PendSV例外は、最も優先度の低い例外に初期化されます。コンテキストスイッチを実行する必要があるたびに、PendSV例外が手動でトリガーされ、コンテキストスイッチはPendSV例外処理関数で実行されます。
詳細な理解については、他のブログ投稿を参照してください:
FreeRTOS Records(3. RTOS Task Scheduling Principles_Systick、PendSV、SVCの分析)
これは、テキストのスクリーンショットを使用した簡単な説明です。
つまり、RTOSの場合、クリティカルリソースにアクセスするときは、クリティカルセクションを保護するために特別な注意を払う必要があります。
上記の問題を回避するために、RTOSはクリティカルセクションに対応する保護方法をいくつか採用しています。一般的には、
システムスケジューリングの終了、割り込みの終了、セマフォの使用、ミューテックスです。
RTスレッドのセマフォとミューテックスについては、次のブログ投稿で説明します。この記事では、主に割り込みを閉じる操作とシステムのスケジューリングに焦点を当てています。
2.RTスレッドクリティカルセクション保護
2.1スケジューリングの無効化
RTスレッドスケジューラのロックとスケジューラのロック解除の機能は次のとおりです。
void rt_enter_critical(void);//调度器上锁,进入调度临界区,不再切换线程
void rt_exit_critical(void);//调度器解锁,退出调度临界区
スケジューリングロックは、システムが割り込みに応答することを妨げませんが、割り込み処理が完了して終了した後、ロックされたスレッドの実行を継続することに注意してください。割り込みで重要なリソースにアクセスできる場合、この方法は適用されません。!
スケジューラロック機能とスケジューラロック解除機能はペアで使用されます。覚えておいてください。
使用例:
スケジューリングを禁止するためのソースコードの簡単な分析
関数を見つけrt_enter_critical
て、それがどのように実装されているかを見てみましょう。
しかし、上記の関数rt_scheduler_lock_nest
は変数を自動インクリメントするだけで、他の操作はありません。この変数はスケジューラーにどのように影響しますか?
変数が使用されている場所をrt_scheduler_lock_nest
見つけて、次のコードを見つけます。
次に、同じように、rt_exit_critical
もちろん、関数では変数がデクリメントされます。
このコードを注意深く見ると、詳細もわかります。つまり、このクローズスケジューリングとオープンスケジューリングはネストをサポートしています。スケジューラーを1回ロックする場合は、1回ロック解除する必要があり、2回ロックする場合は、2回ロック解除する必要があります。
これはまた、ロジックの説明を直接見るよりも、ソースコードを見る方が直感的である場合があることも示しています。
2.2割り込みのマスキング
RTOSのすべてのスレッドスケジューリングは割り込みに基づいています。割り込みを閉じると、外部割り込みをシールドできるだけでなく、スケジューリングを無効にすることもできます。上記の無効なスケジューリングよりもクリティカルセクションを「保護」できます。
RTスレッドマスキング割り込みと割り込みの有効化の機能は次のとおりです。
/*
返回值:
中断状态 rt_hw_interrupt_disable 函数运行前的中断状态
*/
rt_base_t rt_hw_interrupt_disable(void);//屏蔽中断
/*
参数:
level 前一次 rt_hw_interrupt_disable 返回的中断状态
*/
void rt_hw_interrupt_enable(rt_base_t level);//中断使能
なお、上記の端子割り込みロックが最も強力で効率的な同期方法ですが、この方法の主な問題点は、割り込み応答遅延が長くなることです。リアルタイム性能が極端に極端な場合に注意が必要です。 、実際の使用は、アプリケーションに応じて合理的に使用する必要があります。
割り込みマスクと割り込みイネーブル機能もペアで使用されます。覚えておいてください。
使用例:
割り込みロックのソースコード分析
上記の関数は宣言を見つけますが、関数プロトタイプにジャンプすることはできません
。関数の実装はどこにありますか?以下に示すように:
gccコンパイラが使用されるためcontext_gcc.S
、ファイル内の関数本体の前後のステートメントはMDKのステートメントとは異なりますが、関数によって実装されるアセンブリ言語は同じです。
/*
* rt_base_t rt_hw_interrupt_disable();
*/
/*
.global关键字用来让一个符号对链接器可见,可以供其他链接对象模块使用
前面两句意思就类似于定义了一个全局可调用的函数rt_hw_interrupt_disable
*/
.global rt_hw_interrupt_disable //告诉编译器rt_hw_interrupt_disable 是一个全局可见的
.type rt_hw_interrupt_disable, %function//告诉编译器rt_hw_interrupt_disable是一个函数
rt_hw_interrupt_disable:
MRS R0, PRIMASK //读取PRIMASK寄存器的值到r0寄存器
CPSID I //关闭全局中断,具体原因见博文后续说明
BX LR //函数返回,通过LR 连接寄存器 返回
/*
* void rt_hw_interrupt_enable(rt_base_t level);
*/
.global rt_hw_interrupt_enable //与上面类似
.type rt_hw_interrupt_enable, %function
rt_hw_interrupt_enable:
MSR PRIMASK, R0 //将 r0 的值寄存器写入到 PRIMASK 寄存器
BX LR //函数返回,通过LR 连接寄存器 返回
コメントを書いて上記のコードの意味を教えても問題はありますCPSID I
が、グローバル割り込みがオフになっているのですか?
ドキュメント「Cortex-M3およびCortex-M4DefinitiveGuide」をよく見ると、すべてを理解できます。
PRIMSK:割り込みマスク特殊レジスタ。PRIMSKを使用すると、HardFaultとNMIを除くすべての例外を抑制できます。上記の推奨ドキュメントに記載されています。
CPSID I
つまり、割り込みを無効にすること、CPSIE I
つまり割り込みを有効にすることです。
詳細、なぜrt_hw_interrupt_enable
関数は、CPSIE I
割り込みを再開しないのですか?
答えはCPSIE I
、有効な割り込みが使用されている場合、割り込みロックをネストできないということです。レジスタを使用しR0
て現在の状態を保存しPRIMASK
ます。これにより、必要な数の割り込みをオンにする必要があるのと同じ数の割り込みをオフにする必要があります。
上記の例R0
のrt_base_t level
レジスタがこの変数であることも言及する価値があります。
上記の分析を通じて、RT-Threadの割り込みロックがどのように実装されているかを完全に理解する必要があります。他のRTOSもこのようになっていますか?FreeRTOSがどのように割り込みロックを実装するかを見てみましょう。
FreeRTOSとの違い
私のブログ投稿で紹介されたFreeRTOSのクリティカルセクション:
FreeRTOSレコード(4つ、FreeRTOSタスクスタックオーバーフローの問題とクリティカルセクション)
ここでは、彼の実装コードを見て、RT-Threadと比較します(M3を例にとると、M0とM3は異なります):
ここでは、タスクの割り込みをシールドする関数を分析します。割り込みマスクの分析は似ていますが、少しですより複雑。
マスク割り込み:
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
/*----------------------------------------------*/
/*只需要注意操作的寄存器为 basepri*/
/*----------------------------------------------*/
portFORCE_INLINE static void vPortRaiseBASEPRI( void )
{
uint32_t ulNewBASEPRI;
__asm volatile
(
" mov %0, %1 \n" \
" msr basepri, %0 \n" \
" isb \n" \
" dsb \n" \
:"=r" (ulNewBASEPRI) : "i" ( configMAX_SYSCALL_INTERRUPT_PRIORITY ) : "memory"
);
}
割り込みを有効にするには:
//...
#define portENABLE_INTERRUPTS() vPortSetBASEPRI(0)
/*只需要注意操作的寄存器为 basepri*/
portFORCE_INLINE static void vPortSetBASEPRI( uint32_t ulNewMaskValue )
{
__asm volatile
(
" msr basepri, %0 " :: "r" ( ulNewMaskValue ) : "memory"
);
}
ここで、FreeRTOS割り込みロックのコードから、basepri
レジスタではなくPRIMSK
レジスタを操作していることがわかります。レジスタとは何basepri
ですか?答えは、ドキュメント「Cortex-M3およびCortex-M4権威ガイド」にあります
。FreeRTOSは、basepri
レジスタます。この優先度の設定は、ユーザーが設定できます。これは非常に緊急の中断への道を残します。
しかし、とにかく、いつでも、クリティカルセクション処理のコードはもちろん可能な限り短いです!!
2.3実用的なアプリケーション
クリティカルセクションの保護の実際のアプリケーションで必要となる可能性のある状況を簡単に要約すると、次のようになります。
- パブリック関数(非再入可能関数)を呼び出すコード
- 変数(グローバル変数)の読み取りまたは変更
- ハードウェアリソースを使用する(メモリまたはフラッシュを操作する場合)
- 正確なタイミング要件のある動作(I2C通信ですが、サイスティックを使用した遅延機能やドライの遅延などは通信に使用できませんのでご注意ください)
- ユーザーが中断されたくないコード(printf printsなど)
一般に、通常のクリティカルセクションの保護は、割り込みでクリティカルリソースにアクセスできない限り、スケジューリングを禁止することで満たすことができます。
もちろん、絶対的なものはありません。割り込みの発生は、一部の一般的なタスク(ADCサンプリングなど)にも影響を与える可能性があるため、実際の状況に応じて、クリティカルセクション保護を合理的に使用する必要があります。
エピローグ
この記事の内容は、RTスレッドのクリティカルセクション保護の使用法を学ぶのは比較的簡単で、いくつかの関数呼び出しをマスターする必要があります。ただし、実装の原則を理解することは比較的複雑であり、カーネルとオペレーティングシステムの基本原則をある程度理解する必要があります。
これらの関数のソースコードを簡単に分析することで、その原則の実装をより直感的に理解できます。学習に役立つソースコードを読むことを開発するのは良い習慣です。
次のRT-Threadレコードでは、RT-Threadのスレッド間同期に関連するセマフォとミューテックスについて学習します。これは、RT-Threadがクリティカルセクションを保護するもう1つの方法です。
ありがとう!