こちらの記事の後編として、SDカード上のFATが壊れるという問題に対するトラブルシューティングの事例を解説していきます。
ベテランの先輩サポートエンジニアが立てた仮説は、SDコントローラからの転送データをDMAコントローラがメモリ上に書き終わる前にファイルシステムがメモリ上から読み出しを始めているというものでした。
図1:ベテランエンジニアの仮説(※画像をクリックすると拡大します)
それを聞いても私は半信半疑でした。その仮説では観測されている症状の説明がつきそうになかったためです。
まず、SDドライバの受信処理にはトラップコードを仕込んでいます。もし仮説のように受信した時点でデータが壊れていたのであれば、そのトラップコードで引っかかりそうなものです。この点を確認してみると、新たな仮説では受信データは壊れるものの、予想していたものとは違う壊れ方になるため、トラップコードには引っかからないことがわかりました。観測されていた症状は連続しているFATの途中32バイト分だけが壊れて前後は壊れていないという点が特徴的でした。そのため、前と後ろが両方FATとして使用されていて、途中だけ空いていることをトラップ条件としていました。一方、仮説の通りであれば、SDドライバの受信時点では、前がFATとして使用されていて後ろは空いている状態になります。そのため、トラップコードはすり抜けてしまいます(図1の②)。
他にも疑問があります。FATを更新した後で、DMAから受信データが届いたならば、更新されたFATのデータもDMAからの受信データで上書きされるのではないでしょうか。これについては、キャッシュの存在を考慮に入れると説明がつきます。CPUからのメモリへのアクセスはキャッシュを介します。そのため、受信データをCPUで読み出した時点でデータはキャッシュに乗り(図1の②)、更新したFATの内容もまずはキャッシュ書かれます(図1の③)。その後DMAから受信データが到着します。一般に、DMAからのメモリへのアクセスはキャッシュを介しません(厳密にはハードウェアの仕様やキャッシュの段数によります)。つまり、DMAからの受信データはメモリに書き込まれるため、キャッシュに書かれたFATの更新データは上書きされません(図の④)。
それでもまだ、疑問が残ります。キャッシュに書かれているFATの更新データの最後の32バイトは0です。一方、SDカードに書き戻しているデータの最後の32バイトにはFATの続きのデータが入っています。キャッシュをFlash (メモリへの書き戻し) してメモリの内容をSDカードに書き込んだ場合、最後の32バイトも0が書き込まれるのではないでしょうか。この点は、キャッシュのDirtyビットの特性により説明がつきます。Dirty ビットとは、Write Back形式のキャッシュにCPUから書き込みをした際、データがキャッシュだけに書かれていて、メインメモリにまだ書き込まれていない状態であることを示すハードウェア的なフラグです。キャッシュの書き戻しを行うと、キャッシュコントローラは Dirty ビットが立っているキャッシュラインは書き戻しますが、 Dirty ビットが立っていないキャッシュラインは書き戻しません。CPUから書き込みは行わず、DMAがメモリを更新したことによってキャッシュとメモリの内容が不一致になっている状態ではDirtyビットが立たないため、キャッシュの書き戻しを行ってもキャッシュの内容はメモリに反映されません。そのため、図1の⑤のようにCPUから見えている値(キャッシュ)としては0になっていますが、SDカードには「4444」が書き込まれる結果となります。
結局、この仮説に沿って調べた結果、DMA転送が完了する前に転送完了の割り込みが上がることがあるというハードウェアの問題が見つかりました。いかがでしょうか。皆さんは症状から推測してこの仮説にたどり着けたでしょうか。
さて、今回このベテランエンジニアはなぜこの仮説を立てることができたのか、ポイントを分析して、今後のトラブルシューティングの参考にしたいと思います。振り返りを行ってみると、この仮説には以下の3つのポイントがあると思います。
1. 過去の類似事例から推測している
実は、その先輩エンジニアは、過去に類似の問題に遭遇したことがあったそうです。
今回は結果的にハードウェアの障害が原因でしたが、その先輩が以前に遭遇した問題はソフトウェアの実装ミスによるものでした。その際はSDコントローラがSDカードからFIFOにデータを転送し、さらにFIFOからメモリにDMAで転送するという構成で、誤ってSDコントローラによるFIFOへの転送完了をトリガにして受信APIからリターンしてしまっていたというものです。メモリへの転送はDMAで行っているので、本来であればDMAの転送完了をトリガにしなければいけません。受信完了前に受信データを読み始めてしまっているという仮説を挙げることができたのは、一度類似の問題を経験していたことが大きかったと思います。つまり、初見の問題の原因特定はやはり難しく、類似の問題の原因特定のほうが簡単ということです。これは、今回私がこの事例を皆さんに共有しようと思い立ったきっかけでもあります。過去事例を知るほど、将来発生するトラブルで仮説を立てやすくなると思いますので、今後も事例の共有は行っていきたいと思います。
2. CPUとDMA、キャッシュとメモリの関係をよく理解している
この仮説は CPU、DMA、キャッシュの関係性を理解していないと導き出すことはできません。
SDカード上のデータとメモリ上のデータだけを考えているとこの仮説の説明はつかず、キャッシュも考慮に入れることで説明がつきます。CPUから見えているキャッシュの内容と、DMAが書き込んだメモリの内容が一致しない状態となることはあり、図1の①~④はこれを念頭に置いて立てられた仮説となっています。CPUとDMA、キャッシュとメモリの関係は、今回の件に限らずよく問題になるところですので、組込みソフトウェアのエンジニアはよく理解しておく必要がある部分だと思います。
3. キャッシュの仕様に精通している
途中までしか転送されていない状態でファイルシステム受け取ってしまったのであれば 図1で「4444」のデータがあるところも0に書き換わってしまいそうなものですが、実際にはSDカードから読み出された「4444」がそのまま書き戻されていました。これを説明するのが図1の⑤で、これはキャッシュのDirtyビットについて理解していなければ説明がつかない部分になります。ハードウェア的な機構の中でも、特にキャッシュについてはデバイスドライバの実装に深く関わるため、組込みエンジニアはDirtyビットの役割など、詳細な仕様まで理解しておく必要があると思います。なお、昨今のほとんどのCPUで採用されているArmアーキテクチャのキャッシュについては、イーソルで提供している「Arm社認定 Armアーキテクチャトレーニング」にて詳細に解説します。キャッシュについて実践的な情報もご説明していますので、ぜひ受講をご検討ください。
今回は、ベテランのカスタマサポートエンジニアによるトラブルシュートを、事例を挙げてご紹介しました。さらに、なぜそのエンジニアがこの問題を解決できたのか、ポイントを3つ挙げて解説しました。これをお読みいただいたエンジニアの皆さんが、この疑似体験を通じてトラブルシュートの引き出しを増やす一助となれば幸いです。
また今回の事例のように、イーソルの保守サポートサービスでは、製品をご購入いただいたお客様が製品開発の過程でさまざまな問題に直面した場合、イーソルの技術者にご相談いただけます。
イーソルでは製品を自社開発しているため、製品を熟知した技術者がお客様のスムーズな開発を支援します。こちらのサポート体制は、お客様に高い評価を頂いています。