学校では教えてくれないこと

Printf()が使えない

2006.7

どこに出力する?

みんなは作ったプログラムをどうやってデバッグする?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()ひとつとっても、結構たいへんなのがわかってもらえたかな?

ページのトップへ