
【組込みシステム編】第2回 CPUに不具合なし?
こんにちは、南角です。
今回は、前回(第1回 パイプラインストール)の課題を解説します。
上のようなケースで、動作がおかしくなる場合があるが、その原因を考えてくださいという課題でした。
おかしくなるのはタスクBでデータがセットされていないにも関わらず、データを読みに行く場合があり、さらに調べてみるとデータがセットされていないにも関わらず、フラグがセットされていました。
実は前回あえて書かなかったのですが、命令の流れが変わる命令には、ジャンプ命令、サブルーチン呼び出し命令のほかにも、サブルーチンや割り込みからの復帰命令があります。
サブルーチンから、呼び出し元に復帰する場合、スタックや専用のレジスタに保存している再開アドレスを、PCやIPと呼ばれる次に実行する命令のアドレスを保持するレジスタに格納する必要がありますが、これも命令実行の通常の流れを乱す命令です。
今回のケースでは、フラグをONする命令の直後が関数からの復帰命令だったため、コンパイラの最適化処理により、そのまた前にあったデータセットをする命令が、関数からの復帰命令の後に再配置されたために発生しました。
つまりデータをセット→フラグをセットのはずが、コンパイラの最適化処理のため、フラグをセット→データをセットの順番になってしまい、その間でタスクBに処理が切り替わったため、タスクBはフラグがセットされているので、データもセットされていると思い、データを読み込んだが、まだデータはセットされていなかったことが、不具合の原因でした。
当時はまだvolatileも知らず、ずっとCISCを使っていたため、命令の再配置も知らなかったため、最初は戸惑いましたが、テストして状況から判断するとそれしかありえなかったため、実機にローディングした機械語オブジェクトを逆アセンブルして原因がわかりました。
これはC言語をアセンブルした、機械語ニーモニックを見てもわかりません。
命令の再配置はC言語がアセンブラに変換された後のアセンブラから機械語オブジェクトへの変換時に実施されるからです。つまり、この命令の再配置は、命令をアセンブラで記述しても発生します。
そのため最終的に生成された機械語オブジェクトを見ないと原因はわかりませんでした。
原因が判明した後は、対策を立てるだけでしたが、これは容易でした。
関数を超えての再配置はしないことは、すぐに確認できたので、flgをONする処理をflgのアドレスを与えて処理を行う関数に変更して修正しました。
あるいは、バリアー命令ではないのですが、データをセットする処理とフラグをセットする処理の間に関数を置けば、その関数の前後の処理が入れ替わることはないこともすぐに確認できたので、それでもよかったと思います。この関数は特に何らかの処理をしなくても大丈夫でした。
もちろん今ならそんなことをしなくても、変数x、flgにvolatile宣言を追加するだけで済ませます。
ちなみにアセンブラによっても異なりますが、アセンブラで再配置を禁止するには、アセンブラ疑似命令で例えば.noreorderとすれば同じ効果が得られます。
とにかく、Cなどで書いた命令が、必ずしも書いた順番に実行されない場合があるということには、注意してください。
今回、コラムが多くなったので本文は解説だけにしました。
今回のタイトルの意味はコラムを参照してください。
来月は処理が何も存在しないときの、RTOSのスケジューラについて考えてみたいと思います。
組込みシステム編 第2回おわり
コラム1 - パイプラインと割り込み
命令の流れが変わる命令の中には、callやjsrなどのサブルーチン呼び出し命令があると書きました。
それではある意味、現実世界と組み込みソフトウェアをつなぐ存在でもある外部割込みはどうでしょう?
まず割り込み自体もある意味、関数呼び出しです。
ソフトウェア割り込みに関しては、もちろんソフトウェアによる、サブルーチン(割り込みハンドラ、ISR)の呼び出しですので、本文に書いたコンパイラの最適化の対象ですが、ハードウェアからのサブルーチン呼び出しといえる、外部割込みはどうでしょうか?
外部割込みはソフトウェアの実行とは関係ないため、その発生するタイミングはソフトウェアの実行とは全く関係ありません。
別の言い方をすると、ソフトウェアのどの部分が実行されているタイミングで割り込みが発生するかわからないので、コンパイラはどうしようもない気がしますが、どうなのでしょうか?
しかし、問題なのは関数呼び出し命令の、あとの命令が実行されるのはまずいのですが、この場合は関数呼び出し命令そのものが存在しないため、実行されるとまずい命令そのものが存在しないため問題はないと思います。
ただ厳密にいうと、外部割込み発生時、発生時に実行していた命令の実行終了後に、ISRに制御が移るのではなく、割り込み発生時に実行していた命令とそれに続く数命令を実行した後に、ISRに制御が移るといわなければならないのかもしれませんね。
このあたりに興味がある方は、自分の使用しているCPUのハードウェアマニュアルを眺めてみてください。何か新しい発見があるかも知れません。
コラム2 - CPUに不具合なし
直前のコラムに書いたように、割り込み発生時のCPUの動きは興味深いものがあります。
CPUは状態マシンFSMであり、さまざまな状態を持っており、割り込み発生時も割り込みという状態に切り替わるのが普通です。
そしてCPUも割り込み時に固有の動作をします。キャッシュミスの時も同じです。
昔使用したCPUは、キャッシュミス時にキャッシュラインを満たすために、バーストモードに移行しますが、その最中に割り込みが発生して割り込みハンドラに制御が移り、その割り込みハンドラ(ISR)の中である命令を実行すると、CPUがダウンしました。
もちろん、マニュアルにはそんなことは全く書いて無く、それを突き止めるのには大変苦労しました。
原因を突き止め、そのCPUメーカーに問い合わせとCPUの修正依頼をしたのですが、最終的には制約事項、つまりそのCPU仕様になりました。そうなると当然ソフトウェアで対処しなければなりません。
また、別のCPUでクロックを落とした省電力モード中にある命令を実行すると、CPUがダウンする場合もありました。
現在ではCPUも様々な機能が追加され、今やハードウェア屋さんも、すべてのケースの試験を行うことは難しくなっています。しかもCPUに何らかの不具合があったとしても、簡単には直せないのが普通だと思います。
製品開発時、製品が何か不具合を起こして、原因がソフトウェアっぽいと、まずアプリケーションソフトウェア屋さんが調べ、原因が不明の場合はOS屋さんにお鉢が回って来ます。
その当時私はOS屋さんを率いていたので、私のチームがソフトウェアの最後の砦だったわけです。
OS屋が、不具合の原因がソフトウェアなのかハードウェアなのかの切り分けも行わねばなりませんでした。
よくハードウェア屋さんのチームとは協力して、時には議論(喧嘩)しながら、不具合の原因を調べたものです。
しかし原因がソフトウェアでもなく、ハードウェアでもない場合、 CPUの不具合である場合もかなりありました。製品の性格上、最新のCPUを使う機会が多かったせいかもしれませんが。
みなさんも、もし何らかの不具合に悩まされていて、その原因が不明(ソフトウェアでもハードウェアでもない)の場合は、CPU自体の不具合かもしれないということは心の片隅にでも置いておいてください。
ソフトウェアでも不具合は何らかの境界で発生することが多いため、境界条件での試験を多く行うと思います。CPUでも一緒なのではないかと想像しています。例えば、今回のコラムに書いた、キャッシュミスをして、CPUがバーストモードに移行して、キャッシュラインを埋めようとした時に、外部割込みが発生した場合などがまさしくそれで、さまざまな条件の組み合わせ時における、CPUの検証を行うエンジニアの方も大変だろうと思います。
このコラムは昔のCPU屋さんへの恨みを思い出して、過剰な表現になっているかもしれません。CPU屋さんの苦労もわかります。CPU屋さんごめんなさい。
コラム3 - オブジェクト指向とキャッシュミス
実は前のコラムには、もう一つの展開があります。
はっきりと検証したわけではなく状況証拠しかないため、今回はここまででとどめておきますが、実は個人的には、ある状況証拠から、少なくとも当時のコンパイラのレベルでは、同じ機能を従来方式の機能ベースのCで作成した場合と、オブジェクト指向ベースでC++で作成した場合を比較すると、 C++で作成したほうが、従来方式よりもキャッシュミスが約800倍発生したのではないかという疑いを持っています。
これ以上は、興味を持った方がおられれば、調べていただければ幸いです。
その時は連絡いただければ、その時の状況証拠もお話しします。
■著者プロフィール
南角 茂樹(なんかく しげき)
大阪電気通信大学 総合情報学部 メディアコンピュータシステム学科 准教授、同大学院総合情報学研究科(コンピュータサイエンス専攻)、エイシップ・ソリューションズ株式会社 研究顧問。
慶應義塾大学工学部数理工学科卒業後、大手電機会社を経て現職。組み込みシステムおよびリアルタイムOSを専門とし、著書、解説記事、発表・講演、登録特許等多数。
ページの先頭へ戻る »


