FreeRTOSのセマフォーの不具合がやっと解決
雑誌付録ARM(LPC2388)基板のFreeRTOS7.0.0で、ChaNさんのバッファー付きUARTを動かすことには成功したが、セマフォーを使った受信処理はうまく動かず後味の悪い結果が残った。
前記事にあるように、UART出力が数十分後にフリーズするという不具合がどうしても解決できない。結局この不具合究明は一時あきらめ、次の課題を、
・ChaNさんのFatFSをFreeRTOSで動かしてSDカードが読み書きできるようにする。
・UARTをすべてOSのキューを使って入出力し、マルチタスクに対応させる。
ということに決めた。このあたりまで動けば、この基板のOSはロボットなどの多重同時制御のプラットホームとして十分な機能を持つことになり、ネットを使ったカメラ制御などのアプリケーションが簡単に開発できるようになる。
ただ、FatFSを完全なマルチタスクで動くようにするのは、そう簡単ではない。コードそのものはリエントラントで書かれているが、これをいくつものタスクが同時に動いても良いようにするには、コードだけでなくディスクなどの単一リソースの排他制御を完全に行わないと、デタラメな書き込みでディスクは、あっという間に破壊される。
しかし、検討に力が入らない。最初はシングルタスクで動くだけで良いと思っていても、頭の中で、何かがひっかかって先に進まない。そう、セマフォー不具合の問題が頭から離れないのである。
それにしても、このしつこさは何だろう。もし、これが仕事だったら、こんな瑣末なことにこだわっていたら仕事にならない。上司から大目玉を喰らうか、プロジェクトだったら大迷走、中小企業だったら間違いなく倒産である。やっぱり、これは現役時代の反動ではないかと思う。
人は笑うかもしれないが、不思議なことに、こういう下らないことにこだわってあれこれ悩んでいる時の方が、逆に自分が自由な気分になっていることに気づく。仕事の時はこんな悠長なことを考えている余裕はなかった。一時(いっとき)でも全体から見て効果、実効のある方向を模索することが求められ、始終あせっていた。
その焦燥感が、現役を退くと消えた。まわりを気にせず解決を求めてさ迷っていても、誰もとがめる人はいない。いろいろ気配り心配りする方がストレスは溜まるのだ。単純に思考しているほうがむしろ幸せなときなのである。それに悩んだことが大きければ大きいほど、それが解決した時には大きなご褒美が待っている。
セマフォーを解析して不具合を追及する(6/19/2011)
そんなことで、次の課題はぶちあげたものの、実は、あまりやる気が起こらず、暇さえあればセマフォーのトラブルシューティングをやっていた。
まず、不具合の状況を整理する。セマフォーを導入する前は、リアルタイマークロック(RTC)からの1秒毎のUARTのメッセージを何時間出していても全くトラブルがないのに対し、受信割り込みを待つためにセマフォーを使うと、20分前後で全タスクがフリーズする。
セマフォーが動くUART受信部を全く動かさなくても、不思議なことにフリーズする。ここが謎である。ただし、時刻を表示しなければ、セマフォーを入れていても症状は発生しない。フリーズするタイミングは時間ではない。UARTから出力するデータの数である。
セマフォーを組み込むことで、時刻表示のUART出力に不具合が生じ、時限爆弾のように、何千回かあとで問題が顕在化する(そう見えるだけなのかもしれないが)。フリーズするまでの時間は最初10分程度だったのが、いつのころからか20~30分程度に伸びた。正確な時間をとってみると、最初は13分で、増えたときが26分。時間がちょうど倍と言うのが悩ましい。
セマフォーの中のソースコードを解析する。セマフォーは独自のソースではなく、キューの関数を流用している。サイズが0でキューの数が1のキューをハンドリングする。mallocでメモリ(わずかだが)を取っていることがわかった。
そういえば、時刻を表示するのに使うprintfは、mallocを使っている。printfはリエントラントではないので、受信のエコーバックと被ればおかしくなる可能性はあるが、別に被っているわけではなく(受信を一切しなくても起きる)、これがフリーズの原因とは考えにくい。しかし、念のため、printfをやめて自前の送信関数を作って時刻表示させてみる。やっぱりフリーズすることに変わりがない。
FreeRTOSのメモリ管理は、いくつか種類があって、良く見たら、ねむいさんが公式デモソースのメモリ管理の設定をスタティック管理(heap_2.c)から、gcc標準のmallocを使ったダイナミック管理(heap_3.c)に換えていた。もしや、これかと、heap_2.cに戻してテストしてみた。
しかし、フラッシュサイズが20%近く増えただけで、結果は全く変わらなかった。いやあ、難しいものだ。JTAGなどのデバッガーも考えたが、フリーズした地点のアドレス(恐らくBad Interruptで不正番地)がわかるだけで、このあとの解析はARMやFreeRTOSの内部環境を熟知していなければ、簡単には手をつけられない。
最初から徹底的に調べるも万策がつきる(6/20/2011)
だいたい、最初にセマフォーが動き始めた時から動きが不審である。1回空振りをしないとセマフォーが働かなかった。何か、セマフォーに関して不具合報告はないかと、ウェブでキーワードを換えて何度も検索するが、それらしい記述は、前のSTマイクロのときのようにヒットしてくれない。お、似たようなことをしていると良く見ると、自分のブログの記事だったりして苦笑いである。
しかも悔しいことに、このデモソースの中の別のセマフォーは何の問題なく動いている。他の例でも問題があるような話は何もない。それなのに、自分のだけがおかしいのである。不愉快なこと極まりない。
こうなったら意地になるのがいつもの癖である。いちからセマフォーを少しづつ設定して動きを探ることにした。まず、セマフォーハンドルのメモリ上の定義だけをして、他はすべてコメントアウトしてビルドしテストする。これは問題ない(まあ、当たり前か)。
次は、セマフォーハンドルの初期化。 これは以前、タスクでやって問題を起こしているが、もう一度、main()と、vUartTask()でそれぞれ初期化してみる。これもどちらも問題なし。
続いて、セマフォーのTakeステートメント(待つ方)を入れる。Give(許可する方)が動いていないので、キーボード入力は出来ないが、そのまま動かしてみる。これも問題なく時刻を吐き続ける。
今度は、セマフォーのGiveFromISRステートメントを割り込みルーチンに組み込み、テストする。キーボード入力しなければここは通らないので問題ないはずだ。ややや、違う。これだけでフリーズした。ここを一回も通っていないのは、ロジックアナライザーで確かめてある。何ということだ。通っていないルーチンの組み込みでトラブルが起きる。???である。
これは一体どうしたことだ。セマフォーを入れたことによって、割込みルーチンの構造が変わったのか。コードを入れただけで命令を実行しなくてもトラブルが起きる。うーむ、根が深そうだ。
フリーズの原因と見られる、RTCタスクのprintfの出力は、最終的にはUARTタスクの中の関数、uart0_putを使う。セマフォーを動かしているのはuart0_getという全く関係のない関数だ。何故それで無関係のuart0_putを使うタスクがフリーズするのか理解できない。ただ割り込みルーチンが共通なので、このあたりが臭いことは確かだ。
セマフォーを入れることで、何かコンテキストスイッチのルーチンが組み込まれたのか。まさか、そんなわけはない。Takeなら、コンテキストスイッチが起きて、状態が変わる可能性があるが、GiveFromISRは、単にキューエリアに何か書くだけだ。状態が変わるとは思えない。しかも、その発行もしていないのだ。
キューを調べ始めて意外なものを発見(6/23/2011)
悔しいけれどセマフォーの不具合究明はこれ以上は諦めることにする。諦めるのは2回目である。さすがにもう無理だ。調べるところがない。
気を取り直して、前に決めた課題にとりかかる。FatFSは少し重いので、UARTのキュー化の方から始める。本来マルチタスクOSでUARTを使おうというときは、キューでメッセージを一箇所のUARTタスクに送り、ここで一手に出力するというのが正式な導入である。このデモソースでも、LCDがゲートキーパーとして、この方法で動いている。
セマフォーで受信割り込みを監視するなどというのは、小手先のOSの利用に過ぎない。本当はキューからやるべきだったのだ。キューを勉強しながら、セマフォーを諦めた自分を一生懸命慰める。
ただ、UARTをキューで取り扱うのは、そう簡単ではない。printfなどリエントラント化されていないサービス関数をどう取り扱うかが課題である。それでもこの方法は、幸い、ウェブにお手本がある。PICがターゲットだが、このあたりは余り変わりはない。これを少し真面目に読み込む。
読んでいるうちに、FreeRTOS7のデモソースにも、キューで動くUARTの見本があることがわかった。そうだ、以前、ARM7_LPC2106_GCCのフォルダーでserial.cとして、UARTソースを見たけれど、それらしい。
始め見たときは、FreeRTOS特有の長い長い変数で、あまりにも読みにくく、敬遠していたのだが、今読み返してみると、大分読めるようになっていた。FreeRTOSの理解が進んだのだろう。おおよそ何をやっているのかわかるようになってきた。
そのうち、意外な部分を発見した。割り込みルーチンが違う形になっている。ありゃあ、これまでのものと大分違うぞ。ラッパーが作ってあり、コンテキストのセーブ/リストアをこのルーチンでやっていて、本来の割り込みルーチンはここから呼ばれるだけである。
つまり、これまでは、割り込みルーチンのコンパイルモードをARMモードにし、関数の前後に、SAVE_CONTEXT();とRESTORE_CONTEXT();を挟むだけだったが、ここでは、インラインアセンブラーの、
__asm volatile ("bl 割り込みエントリ"); // 本来の割り込みエントリーにリンクする
と、この前後をSAVE_CONTEXT();とRESTORE_CONTEXT(); ではさんだラッパー(Wrapper)関数を作り、この2つの関数に対し、
void 割り込みラッパーエントリ( void ) __attribute__ ((naked)); void 割り込みエントリ( void ) __attribute__ ((noinline))
というコンパイラーオプションをつけている。割り込みラッパーエントリが実際の初期化のときに、VICテーブルに定義する割り込みエントリとなる。
ふーむ、何か閃いたぞ。現在の割り込み処理とやっていることとあまり大きな変化があるとは思えないし、現在のデモソースの他の割り込みルーチンも、この方式は採っていなくて問題なく動いている。
しかし、わざわざラッパーまで作って分けてあることには何か理由があるのだろう。セマフォーの不具合と関係しているのかもしれない。これは試してみる価値があるのではないか。
遂に成功。1時間経っても止まらない(6/24/2011)
大した手間ではない。UARTとRTCの割り込みルーチンに上記のラッパーをそれぞれ組み込む。セマフォーを全部戻して、26分でフリーズする元の形にしてビルドする。フラッシュサイズの増加は、殆どなかった。
テストする。こいつは、すぐに結果が出ない。UARTコンソールを立ち上げ、これを横目にウェブブラウザーを見ながら、時間が経つのを待つ。フリーズする26分が近づいてくる。胸の鼓動が段々高くなって、その場にいたたまれなくなる。端末は黙々と時間を刻んでいく。
このRTCは結構精度が良く、日差数秒で数週間経つがまだ分の遅れも出ていない。いや、そんなことを言っている場合ではない。27分を越えた。まだフリーズしない。30分を過ぎた。うまく行ったのではないか。ただ、この不具合、26分のその倍、52分で止まったこともあるし、一度はフリーズせずにリセットだけして先に進んだこともある。安心はできない。
1時間半が経った。まだ時刻表示は止まらない。歓喜がじわじわとこみあげてくる。どうも解決したようだ。他の割り込みが、この方法を使わずに何故上手く行っているのかは説明できないが、少なくとも、UART割込み、セマフォーの組み合わせでの不具合は、ラッパー関数の追加で解決した。
いやあ、今度も長くかかった。結局、不具合がわかってから解決まで、ほぼ2週間かかっている。何度も書いているけれど、この解決した時の達成感は何物にも替えがたい。しつこく追いかけた甲斐があった。これまでの悩みが吹き飛んで、無上の喜びになる。電子工作の明らかに、変な楽しみ方のひとつである。わざわざ難問を設けて、その解決を追求するのだから。
その後、一晩、動かしっぱなしにして、フリーズしないことを確かめた。これで心置きなく、次の課題に取り組める。なぜそんなにOSにこだわるのか。ロボット制御には不可欠な機能だから、と、ちょっと偉そうに答えてみる。そろそろmbed(LPC1768)も触ってあげないといけないな。
ソースコードの公開については迷った。こちらが付け加えたコードは少なく、ChaNさんや、ねむいさんのコードが多い。公開するのは正直言って気が引ける。まあ、でも少しは、みなさんの役に立つこともあるかと思い、アップすることにする。少なくとも、セマフォー付きUARTは完全に動く。モニターとしてはまだ未完成で、入力した文字はリターンキーで戻ってくるだけである。
ビルドするためには、FreeRTOSの適切なディレクトリーチェーンの中におく必要がある。詳しくは当ブログのバックナンバー記事、ねむいさんの書いたreadme.txt等を参照されたい。
ここに、Eclipseのプロジェクトとしてのソースフォルダーをzipで固めたものを置きます。
解凍したフォルダーをFreeRTOSの適切なディレクトリに置き、makefileのあるディレクトリを
Eclipseのwork spaceにしてください。
| 固定リンク
「ARM」カテゴリの記事
- 心電計プロジェクト:スケールが出ると心電計らしくなる(2015.01.08)
- 心電計プロジェクト:TFT液晶に念願の心電波形が出た(2014.12.18)
- 心電計プロジェクト:STM32F103の心電波形表示で悪戦苦闘(2014.12.03)
- 心電計プロジェクト:CooCoxでARMの表示系ソフトを開発する(2014.10.16)
- 心電計プロジェクト:表示部のARM基板の開発環境を一新する(2014.09.19)
「FreeRTOS」カテゴリの記事
- WiFiモジュールESP32で画像付きサーバーの開発に成功(2017.09.28)
- FreeRTOSマルチタスク対応UARTとガイガーカウンターキット(2011.07.08)
- FreeRTOSのセマフォーの不具合がやっと解決(2011.06.25)
- FreeRTOSでバッファー付きUARTを動かす(2011.06.18)
- FreeRTOSでユーザー割り込み環境が動いた(2011.06.04)
コメント
そら。さん、ご迷惑かけてすみません。
ココログのバグだと思います。前日、ソースコードの部分をテストのため少しいじったのですが、今日、
気がついたら最新の日付になってしまっていました。
(「公開日時」を元へ戻したら直りました)
投稿: がた老 | 2013年10月12日 (土) 20時57分
あれ、この記事は意図せず最新になっていたのでしょうか?
投稿: そら。 | 2013年10月11日 (金) 18時45分
別の方法だとFatFsのAPIを扱うタスクを1つに限定して、FatFSアクセスはそのタスクとタスク間通信して、他のタスクはFatFs APIを直に使わないようにする方法ですかね。
投稿: そら。 | 2013年10月11日 (金) 18時03分
ねむいさん、この間はお世話になりありがとうございました。
え、そちらも動いていなかったのですか。uIPのセマフォーはうまく動いているのに、こちらでは動かず、苦労しましたね。
近々、新記事公開の予定とのこと、楽しみにしています。
投稿: がた老 | 2011年6月27日 (月) 22時39分
Sourcery G++ Lite 2011.03-42でビルドするとFreeRTOS
が動かなくなる原因をやっと突き止めましたが、
がた老さんに先を越されてしまいましたね。
私も近日中に自分なりにまとめた記事と最新のプログラム
公開する準備進めてます。
投稿: ねむい | 2011年6月27日 (月) 13時29分
あんまりいい表現ではありませんでした。
楽しみと苦しみが表裏一体とでも言うべきでした。
投稿: きゅうる村 | 2011年6月26日 (日) 21時14分
きゅうる村さん、いつもコメントありがとう。
私は、自虐的とは思いませんね。推理小説の謎解きとか、
知らない街で迷って帰り道を探しているときの感じかな。
自分をいじめているという意識は余りありませんね。だって、楽しいもの。
投稿: がた老 | 2011年6月26日 (日) 16時08分
ご苦労様。
電子工作の趣味は、なかば自虐的ですが、
理解が進んだ時が快感です。
投稿: きゅうる村 | 2011年6月26日 (日) 11時02分
おめでとさん。
ぼくなんかも、低レベルですが、同じように、
新しい計画→動かない→あきらめ→再起→発見・解決
のくりかえしです。
電子工作の醍醐味って、半ば自虐なんでしょうかね。
投稿: きゅうる村 | 2011年6月26日 (日) 08時17分