Как я могу визуализировать использование памяти (SRAM) программы AVR?

я столкнулся с проблемой в программе C, работающей на микроконтроллере AVR (ATMega328P). Я считаю, что это связано с столкновением стека/кучи, но я хотел бы подтвердить это.

есть ли способ визуализировать использование SRAM стеком и кучей?

Примечание: программа компилируется с avr-gcc и использует avr-libc.

обновление: фактическая проблема, с которой я сталкиваюсь, заключается в том, что реализация malloc терпит неудачу (возвращаясь NULL). Все!--1-->рый происходит при запуске и все freeing происходит в конце приложения (что на практике никогда не происходит, так как основная часть приложения находится в бесконечном цикле). Поэтому я уверен, что проблема не в фрагментации.

8 ответов


вы говорите, что malloc терпит неудачу и возвращает NULL:

очевидная причина, на которую вы должны посмотреть сначала, заключается в том, что ваша куча "полна" - i.e, память, которую вы попросили malloc, не может быть выделена, потому что она недоступна.

есть два сценария, чтобы иметь в виду:

a: у вас есть куча 16 K, вы уже malloced 10 K, и вы пытаетесь malloc еще 10K. Ваша куча просто слишком мала.

b: чаще всего у вас есть куча 16 k, вы делали кучу вызовов malloc/free/realloc, и ваша куча меньше, чем 50% "полная": вы вызываете malloc для 1K, и он терпит неудачу - что случилось? Ответ - свободное пространство кучи фрагментировано - нет смежного 1K свободной памяти, которая может быть возвращена. C менеджеры кучи не могут компактировать кучу, когда это происходит, поэтому вы, как правило, в плохом состоянии. Существуют методы, позволяющие избежать фрагментации, но трудно понять, действительно ли это проблема. Вам нужно будет добавить регистрационные прокладки в malloc и бесплатно, чтобы вы могли получить представление о том, какие операции динамической памяти выполняются.

EDIT:

вы говорите, что все mallocs происходят при запуске, поэтому фрагментация не проблема.

в этом случае должно быть легко заменить динамическое распределение статическим.

старый код примера:

char *buffer;

void init()
{
  buffer = malloc(BUFFSIZE);
}

новый код:

char buffer[BUFFSIZE];

Как только вы сделали это везде, ваш компоновщик должен предупредить вас, если все не может вписаться в доступная память. Не забудьте уменьшить размер кучи - но остерегайтесь, что некоторые системные функции среды выполнения io все еще могут использовать кучу, поэтому вы не сможете удалить ее полностью.


вы можете проверить статическое использование ОЗУ с помощью avr-size утилита, как описано in
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: В коде:: блоки, проект - > параметры сборки - > шаги до / после сборки - > шаги после сборки, включают в себя:

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, и это только количество, которое компилятор знает во время компиляции. Вам также нужно комната для вещей, созданных на время выполнения (особенно использование стека).

для проверки использования стека (динамическая ОЗУ), от http://jeelabs.org/2011/05/22/atmega-memory-use/

вот небольшая функция полезности, которая определяет, сколько ОЗУ в настоящее время не используется:

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), избегали использования кучи для небольших встроенных проектов-чтобы избежать неопределенности доступности кучи памяти. Вместо этого мы используем статически объявленные переменные.

один из методов-заполнить большую часть области стека известным шаблоном (например, 0x55) при запуске. Это обычно это делается небольшим количеством кода в начале выполнения программного обеспечения, либо прямо в начале main (), либо, возможно, даже до начала main() в стартовом коде. Позаботьтесь о том, чтобы не перезаписать небольшое количество стека, используемого в этот момент, конечно. Затем, после запуска программного обеспечения на некоторое время, проверьте содержимое пространства стека и посмотрите, где 0x55 все еще цел. Как вы "проверяете", зависит от вашего целевого оборудования. Предполагая, что у вас подключен отладчик, вы можете просто остановить микро бегу и читаю память.

Если у вас есть отладчик, который может сделать точку останова доступа к памяти (немного более причудливую, чем обычная точка останова выполнения), то вы можете установить точку останова в определенном месте стека-например, самый дальний предел вашего пространства стека. Это может быть очень полезно, потому что он также показывает, какой бит кода выполняется, когда он достигает такой степени использования стека. Но для поддержки функции точки останова доступа к памяти требуется отладчик, и это часто не встречается в отладчиках "low-end".

Если вы также используете кучу, то это может быть немного сложнее, потому что может быть невозможно предсказать, где стек и куча столкнутся.


предполагая, что вы используете только один стек (так что не RTOS или что-то еще) и что стек находится в конце памяти, растет, в то время как куча начинается после области BSS/DATA, растет. Я видел реализации malloc, которые фактически проверяют stackpointer и терпят неудачу при столкновении. Ты можешь попытаться сделать это.

Если вы не можете адаптировать код malloc, вы можете поместить свой стек в начало памяти (используя файл компоновщика). В общем, это всегда полезно знать / определять максимальный размер стека. Если вы поместите его в начале, вы получите ошибку при чтении за пределами начала ОЗУ. Куча будет в конце и, вероятно, не может расти дальше конца, если это приличная имплантация (вместо этого вернет NULL). Хорошо, что у вас есть 2 отдельных случая ошибок для 2 отдельных проблем.

чтобы узнать максимальный размер стека, вы можете заполнить свою память шаблоном, запустить приложение и посмотреть, насколько оно пошел, см. Также ответ от Крейга.


Если вы можете отредактировать код для своей кучи, вы можете заполнить его несколькими дополнительными байтами (сложно на таких низких ресурсах) на каждом блоке памяти. Эти байты могут содержать известный шаблон, отличный от стека. Это может дать вам ключ, если он столкнется со стеком, увидев его внутри стека или наоборот.


в Unix-подобных операционных системах библиотечная функция sbrk () с параметром 0 позволяет получить доступ к самому верхнему адресу динамически выделенной памяти кучи. Возвращаемое значение является указателем void * и может быть сравнено с адресом произвольной переменной, выделенной стеком.

использование результата этого сравнения следует использовать с осторожностью. В зависимости от CPU и архитектуры системы стек может расти с произвольного высокого адреса, в то время как выделенный куча будет двигаться вверх из памяти с низкой границей.

иногда операционная система имеет другие концепции управления памятью (например, OS/9), которые помещают кучу и стек в разные сегменты памяти в свободную память. В этих операционных системах-особенно для встроенных систем - необходимо определить максимальные требования к памяти приложения заранее, чтобы позволить системе выделять сегменты памяти соответствующих размеров.