どこに出力する?
みんなは作ったプログラムをどうやってデバッグする?printf()を使ってログを表示させて、設定値やシーケンスが設計どおりになっているかチェックするのが、一番手っ取り早いよね。
でも実は、組込み開発では、そんなに簡単にはprintf()は使えないんだ。まず、関数自体が用意されていないケース。例えば日本で非常によく使われているµITRONというOSの仕様には含まれていないんだ。最近では、組込み向けのOSでもC標準ライブラリをサポートしていたり、OS提供メーカがオプションツールとしてサポートしていたりするから、比較的使える場合が多くはなってきてるけどね。
仮にprintf()の関数が使えたとしよう。組込み開発の相手は、たいていICチップがむき出しのハードウエア基板だよね。ここで問題になるのは「どこに出力されるのか?」という点。ハードウエアにデバッグ目的のRS-232Cシリアルポートが用意されていれば、これを利用すればいい。シリアルケーブルで接続した開発ホストPCで、ターミナル端末ソフトを起動すれば、出力されたログを見ることができるというわけだ。
では、「printf()が呼び出されたら、このシリアルポートに出力してね」という繋ぎの部分が必要になるんだけど、これって誰が用意するんだろうか。実際は、誰かが用意してくれるわけではなくて、自分でシリアルポートを制御するデバイスドライバを作成しなければならないケースが多いんだ。
通常、こういう作業は「OSの移植」といわれる初期フェーズで行われる。シリアルにログが出るようになると第一段階突破、という感じだね。開発環境によっては、シリアルではなくて、ネットワーク(Ethernet)や専用インターフェース(JTAG)で接続したホストPCツール上に出力できるものもある。しかし、いずれにしても、その開発環境の構成(シリアルならポートのボーレート設定や信号線の結線、ネットワークならIPアドレス設定など)を理解して、正しく設定を行わないとログを出すこともできないんだ。やってみると意外に苦労が多くて、実際にシリアル端末に一文字出ただけでも嬉しいもんだよ。(^-^)
printf()でデッドロック
さて、ようやくシリアルにログを出すことができるようになり、順調にデバッグが進んで・・・!?「あれ、もう少しログが出るはずなんだけどな・・・?」ときどきprintf()で最後の数行が表示されないことってないかな?こういうときはfflush(stdout)をするとちゃんと表示されるよね。
printf()で渡された文字列は、いったん標準ライブラリ内部のストリームバッファに蓄えられて、まとめて標準出力(stdout)に渡されるんだ。このため、タイミングによってストリームバッファに残るんだよ。
fflush(stdout)を呼び出すと、これが吐き出されるわけ。これはよく知られているprintf()の仕様上の副作用で、これが組込みでは重大な問題を引き起こすことがあるんだ!組込みの鉄則として「割り込み処理は、できるだけ短時間で終了させる」というルールがあるんだ。
OSカーネルはシステムタイマの周期割り込みを基本にしてスケジューリングを管理していて、ハードウエアの制御をするデバイスドライバは個々のデバイスに対応した割り込みを使って動作している。ひとつの割り込み処理が長時間動き続けてしまうと、システム全体のリアルタイム動作に影響を与えちゃうことになるわけだ。
特に「待ち状態になる可能性のある処理」を呼び出すのは厳禁なんだ。ここでの、「待ち状態」とは、他のタスクから起こしてもらう必要がある状態を言うんだ。割り込みは一般タスクよりも高い優先度で処理されるから、もし待ちに入っちゃったら、誰も起こしてくれないという「デッドロック」に陥っちゃう可能性があるからなんだよ。
割り込み処理のログ出力
さて、printf()に話を戻そう。printf()の呼び出しでは、標準ライブラリ内部でストリームバッファの排他制御のために、待ち状態になる可能性があるんだ。つまり「割り込み処理からprintf()を使ってはならない」ということになる。でも、割り込み処理でもログを出してデバッグしたいよね。どうやるのだろう。よくやるのは、以下の2種類。
(1) 出力タスク方式
ログ出力専用のタスクを起動しておき、そこにメッセージとして送信する。ログ出力タスクに処理が回って来た時に、たまったログが出力される。
(2) メモリログ方式
メモリ上にリングバッファを構成し、そこに格納する。ログを標準出力にダンプする関数を用意する。
どちらの方法も「待ち状態にならないログ出力機構」で、高機能なOSは、標準機能として用意してくれているんだ。当社が提供している「ミドルウエア共通環境(KSLLIB)」というライブラリでは(1),(2)の両方をサポートしているよ。
このライブラリは、もともとミドルウェアから見たOSの違いを吸収するために作ったんだけど、こういう、OSをサポートするような機能もいろいろと用意されているんだ。話を戻そう。(1)や(2)のやり方にも、もちろんそれぞれデメリットもあるんだ。
出力タスク方式のデメリット
- 出力されるタイミングが遅れることがある。
- 大量にログを出すと、タスクの処理が間に合わずログが破棄される。
メモリログ方式のデメリット
- ひととおり実行後でないとログが確認できない。
- 時間軸のデバッグがしにくい。(ログ表示の間隔などはわからない)
- リングバッファが一杯になったら古いログは上書きされる。
でも、「割り込み処理からログを出せる」というメリットには勝るものはないんだ。組込みでは、このような工夫されたログ出力関数を使ってデバッグしているんだ。printf()ひとつとっても、結構たいへんなのがわかってもらえたかな?
-
- 第1回 組込みシステムのこれから
- 第2回 IoTの成功はセキュリティ次第
- 第3回 組込みでもGPUやFPGAと早めに親しんでおこう
- 第4回 電子産業の紅白歌合戦、CEATECで垣間見えた未来
- 第5回 小口開発案件の集合市場、IoTの歩き方(上)
- 第6回 小口開発案件の集合市場、IoTの歩き方(下)
- 第7回 徹底予習:AI時代の組込みシステム開発のお仕事
- 第8回 いまどきのセンサー(上):ありのままの状態を知る
- 第9回 いまどきのセンサー(下):データを賢く取捨選択する
- 第10回 組込みブロックチェーンの衝撃(上)
- 第11回 組込みブロックチェーンの衝撃(下)
- 第12回 エネルギーハーベスティングの使い所、使い方
- 第13回 「人を育てる」から「道具を育てる」へ、農業から学ぶAI有効活用法
- 第14回 CPS時代に組込みシステム開発に求められることとは
- 第15回 次世代車のE/Eアーキテクチャに見る組込みの進む道
- 第16回 RISC-Vが拓く専用プロセッサの時代
- 第17回 振動計測の大進化で、熟練エンジニアのスキルを広く身近に
-
- 零の巻:組込みというお仕事
- 壱の巻:2進数と16進数を覚えよう!
- 弐の巻:割り込みとポーリング
- 参の巻:printf()が使えない?
- 四の巻:これにもIntelが入ってるの?
- 五の巻:Endianってなに?
- 六の巻:マルチタスクとは
- 七の巻:スタックってなあに?(1)
- 七の巻:スタックってなあに?(2)
- 八の巻:メモリを壊してみましょう
- 九の巻:コードが消える?~最適化の罠~
- 拾の巻:例外が発生しました
- 拾壱の巻:コードサイズを聞かれたら
- 拾弐の巻:キャッシュは諸刃の剣
- 拾参の巻:デバイスにアクセスするには
- 拾四の巻:セキュリティってなに?(1)
- 拾四の巻:セキュリティってなに?(2)
- 拾四の巻:セキュリティってなに?(3)
- 拾五の巻 :DMA対応と言われたら(1)
- 拾五の巻 :DMA対応と言われたら(2)
- 拾六の巻:ヒープとスタック
- 拾七の巻:フラグメンテーション
- 拾八の巻:CPU起動とブートローダ
- 拾九の巻:kmとKByteの「kとK」
- ビリーへの質問:DMAとキャッシュの関係
- ビリーへの質問:スタックオーバーフローについて
- ビリーへの質問:CPUレジスタについて