この記事について
Mastering the FreeRTOS Real Time Kernel-A Hands-On Tutorial Guildの日本語訳
重要と思われるポイント
Chapter 12 トラブルシューティング (Trouble Shooting)
12.1 イントロダクションとスコープ
このチャプターでは、FreeRTOSを初めて使う人が遭遇しがちな問題について扱う。初めに、最もサポートリクエストの多かった3つの問題について扱う。
間違った割り込みの優先度設定、スタックオーバーフロー、不適切なprintfの使用である。
また、簡単に、FAQスタイルで、他の問題の考えられる原因、解決法にも言及する。
configASSERT()の仕様は、多くのエラーを検出できるので、すぐに生産性を高めることができる。FreeRTOSアプリケーションのデバッグでは、定義することを強く推奨する。
configASSERT()については11.2で説明している。
12.2 割り込み優先度
FreeRTOSポートが割り込みのネストをサポートしている場合、ISRがFreeRTOS APIを使うと、section6.8で説明したとおり、configMAX_SYSCALL_INTERRUPT_PRIORITYより低い優先度で設定することが必要になる。設定をしていないと、非効率なクリティカルセクションを作って、間欠的なエラーを生じさせる。
あるプロセッサ上でFreeRTOSを動作させるには、以下をケアすべき
- 割り込み優先度のデフォルトは、いくつかARMプロセッサや可能であれば他でも、最も高い優先度とする。このようなプロセッサでは、FreeRTOS APIを使う割り込みが、初期化されない状態では残らない。
- Numericalな優先度とLogicalな優先度が逆の場合、直感に反しているのでエラーを生じさせやすい。ARMプロセッサやほかのプロセッサで生じる。
- たとえば、優先度5のISRは優先度4のISRに割り込まれる。そのためconfigMAX_SYS_CALL_INTERRUPT_PRIORITYは5の場合、FreeRTOS APIを使うどの割り込みも数値的に5以上にアサインすることになる。このケースでは優先度5, 6は有効だが、3,4は無効となる。
- 異なるライブラリの実装は、異なる方法で割り込みの優先度が設定されることを期待している。ARM Cortexをターゲットとしているライブラリは特に、ハードウェアレジスタに書き込まれる前に、割り込み優先度ビットシフトされる。いくつかのライブラリは、自身でビットシフトが、ライブラリに渡される前にビットシフトを期待しているライブラリもある。
- 同じアーキテクチャの異なる実装は、割り込み優先度のビットが異なっている。たとえば、ある製造者のCortex-Mでは3bitで、他の製造者は4bitであることがある
- 割り込み優先度を決めるビットは、pre-emption priorityと、sub-priorityに分割されることがある。すべてのbitがpre-emption priorityにアサインされていることを確認して、sub-proirityは使わない。
いくつかのFreeRTOS portsでは、configMAX_SYSCALL_INTERRUPT_PRIORITYがconfigMAX_API_CALL_INTERRUPT_PRIORITYとなっている。
12.3 スタックオーバーフロー
スタックオーバーフロー2つ目にサポートリクエストが多い。FreeRTOSはスタックに関する問題を検出するためのサポート機能がいくつかある。
uxTaskGetStackHighWaterMark
各タスクはそれぞれスタックを持っていて、タスク生成時に設定される。uxTaskGetStackHighWaterMarkはどれだけスタック領域があふれそうなのかをクエリーできる。この値は"high water mark"と呼ばれる。
UBaseType_t uxTaskGetStackHighWaterMark( TaskHandle_t xTask );
パラメータ | 説明 |
---|---|
xTask | クエリーしたいタスクのhandle。NULLを設定すると自分自身のクエリーができる |
戻り値 | タスク、割り込みが動作して使われたタスク総量。uxTaskGetStackHighWaterMarkはタスクが動作してからもっとも最小になったスタックサイズを返す。もっともスタックを消費したときの、使っていないスタックの量になる。ゼロに近いとスタックオーバーフローが近いことになる |
ランタイムスタック確認 - 概要
FreeRTOSはランタイムのスタック確認メカニズムとして2つの方法を含んでいる。これらはコンパイル時にconfigCHECK_FOR_STACK_OVERFLOWでコントロールされる。両者ともにコンテキストスイッチの時間が長くなる。
stack overflow hookはスタックオーバーフロー時に呼び出される関数である。関数を使うには
- configCHECK_FOR_STACK_OVERFLOWを1 or 2に設定する。次のサブセクションで説明される。
- Listihg174のフォーマットで関数を実装する
void vApplicationStackOverflowHook( TaskHandle_t *pxTask, signed char *pcTaskName );
stack overflow hookは、スタックエラーを検出してデバッグを容易にする。しかし、スタックオーバーフローが発生したときにリカバーする方法はない。この関数のパラメータでオーバーフローしたタスクの名前を渡す。
stack overflow hookは割り込みからも呼ぶことができる。
いくつかのマイコンでは、メモリの不正アクセスを検出するとexceptionを発生させる。カーネルがスタックオーバーフローのhookを呼ぶ前に、トリガーをかけることが可能。
ランタイムスタック確認 - 方法1
方法1はconfigCHECK_FOR_STACK_OVERFLOWを1にする。
タスクのすべての実行コンテキストは、スワップアウトするたびに、スタックに保存される。これは、スタック使用量がピークとなった時となる。カーネルは、そのコンテキストの保存の後に、スタックポインタがスタック領域内にあることをチェックする。stack overflow hookはスタックポインタが有効な領域外に入った時に呼び出される。方法1は実行しやすいが、コンテキストスイッチ間のスタックオーバーフローを見逃すことがある。
ランタイムスタック確認 - 方法2
方法2はconfigCHECK_FOR_STACK_OVERFLOWを2にする。
タスクが生成されるときに、スタック領域は既知のパターンで埋められる。メソッド2ではスタック領域の最後20byteが上書きされていないか確認する。stack overflow hook関数は、20byteが期待値ではなかった場合に呼び出される。
方法2は方法1より、すぐに実行できないが、20byteしかチェックしないので高速である。すべてのスタックオーバーフローを検出する可能性が高いが、いくつかのオーバーフローは見逃すことがある。(とても起こりそうにはない)
12.4 printf, sprintfの不適切な使用
printf()の不適切な使用はよくあるエラーで、認識しにくい。デバッグのために開発者はprintfを入れることがよくるので、問題を悪化させることがある。
多くのクロスコンパイラベンダーは、小規模な組み込み向けのprintf実装を提供する。このような場合でも、その実装はスレッドセーフでなかったり、ISRから呼び出すのに適していなかったり、どこに出力するかに依存していたり、実行に比較的時間がかかったりすることがある。
printfが小規模組み込みシステム用でない場合にはとくに注意が必要となり、generic printf実装を代わりに使うべきである。以下の理由から、、
- printf, sprintfをincludeすることで、実行ファイルのサイズを増加させる
- printf, sprintfはmallocを呼ぶ可能性があり、heap_3以外のスキームを使っていると、それが無効になっていることがある。詳細はsection2.2参照
- printf, sprintfはスタックを使用する。
Printf-stdarg.c
多くのFreeRTOSデモプロジェクトでは、printf-stdarg.cを使っている。これは、標準ライブラリのsprintfを軽く、効率的なスタック消費の実装で置き換える。多くの場合、sprintfや関連する関数を呼ぶと、小さいスタック消費となる。
printf-stdargs.cは、printf()の出力方向をport characterへ向ける仕組みを提供する。遅いが、スタック消費量を低減させる。
printf-stdarg.cのすべてで、snprintfを実装しているわけではない。snprintfを単純にバッファサイズを無視して、sprintfにダイレクトにつなぐような実装はしないこと。
printf-stdarg.cはサードパーティー提供なので、それぞれライセンス形態が異なる。
12.5 他のよくあるエラー
demoにシンプルなタスクを追加すると、demoがクラッシュする
タスク生成はヒープが必要になる。多くのデモアプリケーションはデモのタスク生成にちょうど必要なヒープサイズを設定している。そのため、カーネルオブジェクトを追加で生成するとヒープが足りなくなることがある。
アイドルタスク、デーモンタスクは、vTaskStartSchedulerで自動生成される。vTaskStartSchedulerはこれらのタスク生成に必要なヒープがない場合のみリターンされる。for(;;)の無限ループを含ませておくと、このエラーがすぐに検出できる。
タスクを追加したいのであれば、ヒープを増やすか、既存のタスクを減らすことで可能となる。section2.2でメモリアロケーションの詳細を記載している。
ISRでAPIを使うとクラッシュする
FromISRがないAPIをISR内で使ってはならない。特に、割り込みセーフマクロを使わずに、ISR内でクリティカルセクションを作ってはならない。section 6.2に詳細情報がある。
FreeRTOSポートでは割り込みネストをサポートしている場合、どのAPI関数もconfigMAX_SYSCALL_INTERRUPT_PRIORITYより上の優先度をもつISRで呼び出してはならない。section 6.8に詳細を説明している。
ISR内で時々アプリケーションがクラッシュする
初めに確認することはISRがスタックオーバーフローを起こしていないこと。 いくつかのポートではスタックオーバーフローの確認はタスクだけで、割り込みではチェックしていない。
ポートとコンパイラの間で割り込みの定義、使われ方が異なる。そのため、2つ目に確認することは、ISR内のsyntax, macro, 呼び出し規約が、各ポートのドキュメント通りになっているのかである。デモアプリケーションで提供された方法と一致しているか。
割り込み優先度がnumeric, logicalで逆転しているプロセッサの場合は、デフォルト優先度が最優先になっているか?デフォルト値のままになっていないか?section6.8で詳細を説明している。
スケジューラが初めのタスクをスタートするとクラッシュする
FreeRTOS割り込みハンドラがインストールされているか。各ポートのドキュメントやデモアプリケーションを参照する。
いくつかのプロセッサは、スケジューラがスタートできる前に、特権モードにいれる必要がある。設定する簡単な方法は、Cスタートアップコード(mainの前)で特権モードにすることである。
割り込みが意図せずdisableになる。クリティカルセクションが正しくネストされない。
スケジューラーが動く前にFreeRTOS APIが呼ばれた場合、割り込みは意図的にdisableとされ、最初のタスクが実行されるまで再度enableにはしない。これは、スケジューラーがスタートして一貫性のない状態の間、システム初期化中にFreeRTOS APIを割り込みが使おうとすることが原因のクラッシュを防ぐためである。
taskENTER_CRITICAL(), taskEXIT_CRITICAL()以外で割り込みenable bitや優先度フラグを変えないこと。これらのマクロはネスティングのカウンタを管理している。
いくつかのライブラリ関数は自身の割り込みをdisable, enableとすることがある。
スケジューラーがスタートする前にクラッシュする
潜在的にコンテキストスイッチを発生させるISRは、ISRはスケジューラがスタートする前に実行を許可するべきではない。FreeRTOSオブジェクトからsend/receiveするISRにも適用されることである。コンテキストスイッチはスケジューラがスタートするまでは発生しない。
多くのAPIはスケジューラがスタートしてから呼ばれる。API使用を制約する方法として、オブジェクト使用ではなく生成をスケジューラが呼ばれたあとにするのがBestである。
スケジューラがサスペンド時にAPIを呼ぶ、または、クリティカルセクションから呼ぶと、アプリケーションがクラッシュする
スケジューラはvTaskSuspendAllでsuspendして、xTaskResumeAllでresumeする。クリティカルセクションは、 taskENTER_CRITICAL(), taskEXIT_CRITICAL()で入る。
スケジューラがsuspendまたは、クリティカルセクションの中から、API関数を呼んではならない。