AVRマイクロコントローラー(ATMega328P)で実行されているCプログラムで問題が発生しました。スタック/ヒープの衝突によるものと思われますが、確認したいと思います。
スタックとヒープごとにSRAMの使用状況を視覚化する方法はありますか?
注:プログラムはavr-gccでコンパイルされ、avr-libcを使用します。
更新:私が抱えている実際の問題は、mallocの実装が失敗していることです(NULL
を返します)。すべてのmalloc
ingは起動時に発生し、すべてのfree
ingはアプリケーションの終了時に発生します(実際には、アプリケーションの主要部分が無限ループにあるため、これは発生しません)。したがって、断片化は問題ではないと確信しています。
あなたはmallocが失敗してNULLを返していると言います:
最初に確認する必要がある明らかな原因は、ヒープが「いっぱい」であるということです。つまり、mallocに要求したメモリは使用できないため、割り当てることができません。
覚えておくべき2つのシナリオがあります。
a:16 Kのヒープがあり、すでに10 Kをmallocし、さらに10Kをmallocしようとします。ヒープが小さすぎます。
b:より一般的には、16 kのヒープがあり、malloc/free/realloc呼び出しを何度も実行していて、ヒープが50%未満です。「フル」:mallocを1Kで呼び出しても、失敗します。どうしたのですか。回答-ヒープの空き領域が断片化されています-返される可能性のある連続した1Kの空きメモリがありません。 Cヒープマネージャは、これが発生したときにヒープを圧縮できないため、一般的に悪い方法です。断片化を回避するための手法はありますが、これが本当に問題であるかどうかを知ることは困難です。どの動的メモリ操作が実行されているかを把握できるように、mallocとfreeにロギングシムを追加する必要があります。
編集:
すべてのmallocは起動時に発生すると言うので、断片化は問題ではありません。
その場合、動的割り当てを静的割り当てに置き換えるのは簡単です。
古いコード例:
char *buffer;
void init()
{
buffer = malloc(BUFFSIZE);
}
新しいコード:
char buffer[BUFFSIZE];
これをどこでも実行すると、すべてが使用可能なメモリに収まらない場合、リンカーは警告を表示するはずです。ヒープサイズを減らすことを忘れないでください。ただし、一部のランタイムioシステム関数は引き続きヒープを使用する可能性があるため、ヒープを完全に削除できない場合があることに注意してください。
で説明されているように、_avr-size
_ユーティリティを使用してRAM静的使用量を確認できます
http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=62968 、
http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=82536 、
http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=95638 、
および http://letsmakerobots.com/node/27115
_avr-size -C -x Filename.elf
_
(avrサイズのドキュメント: http://ccrma.stanford.edu/planetccrma/man/man1/avr-size.1.html )
IDEでこれを設定する方法の例に従います。Code:: Blocksで、プロジェクト->ビルドオプション->ビルド前/ビルド後の手順->ビルド後の手順には、次のものが含まれます。
avr-size -C $(TARGET_OUTPUT_FILE)
またはavr-size -C --mcu=atmega328p $(TARGET_OUTPUT_FILE)
ビルド終了時の出力例:
_AVR Memory Usage
----------------
Device: atmega16
Program: 7376 bytes (45.0% Full)
(.text + .data + .bootloader)
Data: 81 bytes (7.9% Full)
(.data + .bss + .noinit)
EEPROM: 63 bytes (12.3% Full)
(.eeprom)
_
データはSRAMの使用量であり、コンパイル時にコンパイラが認識している量のみです。また、実行時に作成されるもの(特にスタックの使用)のためのスペースも必要です。
スタックの使用状況(ダイナミックRAM)を確認するには、 http://jeelabs.org/2011/05/22/atmega-memory-use/
RAMが現在使用されていない量を決定する小さなユーティリティ関数は次のとおりです。
_int freeRam () {
extern int __heap_start, *__brkval;
int v;
return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
}
_
そして、そのコードを使用したスケッチは次のとおりです。
_void setup () {
Serial.begin(57600);
Serial.println("\n[memCheck]");
Serial.println(freeRam());
}
_
FreeRam()関数は、ヒープの終わりとスタックに最後に割り当てられたメモリの間に存在するバイト数を返すため、スタック/ヒープが衝突する前に実際にどれだけ大きくなるかを示します。
スタック/ヒープの衝突を引き起こしていると思われるコードの前後で、この関数の戻り値を確認できます。
通常のアプローチは、メモリを既知のパターンで埋めてから、どの領域が上書きされているかを確認することです。
スタックとヒープの両方を使用している場合は、もう少し注意が必要です。ヒープを使用しない場合の動作について説明します。原則として、(組み込みCソフトウェアのドメインで)私が働いてきたすべての企業は、ヒープメモリの可用性の不確実性を回避するために、小さな組み込みプロジェクトにヒープを使用することを避けてきました。代わりに、静的に宣言された変数を使用します。
1つの方法は、起動時にスタック領域の大部分を既知のパターン(0x55など)で埋めることです。これは通常、ソフトウェア実行の早い段階で、main()の開始直後、またはおそらくmain()が開始する前に、起動コード内の小さなコードによって実行されます。もちろん、その時点で使用中の少量のスタックを上書きしないように注意してください。次に、ソフトウェアをしばらく実行した後、スタックスペースの内容を調べて、0x55がまだ損傷していない場所を確認します。 「検査」の方法は、ターゲットハードウェアによって異なります。デバッガーが接続されていると仮定すると、マイクロの実行を停止してメモリを読み取ることができます。
メモリアクセスブレークポイント(通常の実行ブレークポイントよりも少し凝ったもの)を実行できるデバッガーがある場合は、スタックスペースの最も遠い制限など、特定のスタック位置にブレークポイントを設定できます。これは、スタック使用量の範囲に達したときに実行されているコードのビットを正確に示すため、非常に便利です。ただし、デバッガーでメモリアクセスブレークポイント機能をサポートする必要があり、「ローエンド」デバッガーにはないことがよくあります。
ヒープも使用している場合は、スタックとヒープが衝突する場所を予測できない可能性があるため、少し複雑になる可能性があります。
埋め込みターゲットでヒープ/動的割り当てを使用しないでください。特に、そのような限られたリソースを持つプロセッサでは。プログラムが大きくなるにつれて問題が再発するため、アプリケーションを再設計してください。
スタックを1つだけ使用していて(RTOSなどではない)、スタックがメモリの最後にあり、BSS/DATAの後にヒープが開始している間に、成長していると仮定します。リージョン、成長しています。実際にスタックポインターをチェックし、衝突で失敗するmallocの実装を見てきました。そうすることを試みることができます。
Mallocコードを適応させることができない場合は、(リンカーファイルを使用して)メモリの先頭にスタックを配置することを選択できます。一般に、スタックの最大サイズを知っている/定義することは常に良い考えです。先頭に置くと、RAMの先頭を超えて読み取るときにエラーが発生します。ヒープは最後にあり、それが適切な実装である場合、おそらく最後を超えて成長することはできません(代わりにNULLを返します)。良いことは、2つの別々の問題に対して2つの別々のエラーケースがあることを知っていることです。
最大スタックサイズを見つけるには、メモリをパターンで埋め、アプリケーションを実行して、それがどこまで進んだかを確認します。Craigからの返信も参照してください。
Unixライクなオペレーティングシステムでは、パラメータが0のsbrk()という名前のライブラリ関数を使用すると、動的に割り当てられたヒープメモリの最上位アドレスにアクセスできます。戻り値はvoid *ポインターであり、任意のスタック割り当て変数のアドレスと比較できます。
この比較の結果を使用する場合は注意が必要です。 CPUとシステムアーキテクチャによっては、スタックが任意の上位アドレスから減少し、割り当てられたヒープが下限メモリから増加する場合があります。
オペレーティングシステムには、メモリ管理に関する他の概念(OS/9など)があり、ヒープとスタックを空きメモリの異なるメモリセグメントに配置する場合があります。これらのオペレーティングシステム(特に組み込みシステムの場合)では、システムが一致するサイズのメモリセグメントを割り当てられるように、アプリケーションの最大メモリ要件を事前に定義する必要があります。
ヒープのコードを編集できる場合は、メモリの各ブロックに2、3バイト(このような低リソースでは扱いにくい)を追加することができます。これらのバイトには、スタックとは異なる既知のパターンが含まれている可能性があります。これにより、スタック内に表示されることでスタックと衝突した場合、またはその逆の場合に手がかりが得られる可能性があります。