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

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

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

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

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

Ответ 1

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

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

Существует два сценария:

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

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

EDIT:

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

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

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

char *buffer;

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

новый код:

char buffer[BUFFSIZE];

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

Ответ 2

Вы можете проверить статическое использование ОЗУ с помощью утилиты avr-size, как описано в
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-size documentation: http://ccrma.stanford.edu/planetccrma/man/man1/avr-size.1.html)

Далее следует пример того, как установить это в среде IDE: На Code:: Blocks, Project → Параметры сборки → Шаги Pre/post build → Шаги после сборки включают:

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

Вы можете проверить возврат этой функции вокруг кода, который, как вы подозреваете, может вызвать столкновение с стеком/кучей.

Ответ 3

Не используйте кучу/динамическое распределение для встроенных целей. Особенно с процессором с такими ограниченными ресурсами. Скорее измените свое приложение, потому что проблема будет повторяться по мере роста вашей программы.

Ответ 4

Обычный подход состоял в том, чтобы заполнить память известным шаблоном, а затем проверить, какие области перезаписаны.

Ответ 5

Если вы используете стек и кучу, то это может быть немного сложнее. Я объясню, что я сделал, когда куча не используется. Как правило, все компании, над которыми я работал (в области встроенного программного обеспечения C), избегали использования кучи для небольших встроенных проектов, чтобы избежать неопределенности доступности памяти кучи. Вместо этого мы используем статически объявленные переменные.

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

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

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

Ответ 6

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

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

Чтобы узнать максимальный размер стека, вы можете заполнить свою память шаблоном, запустить приложение и посмотреть, как далеко он прошел, см. также ответ от Craig.

Ответ 7

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

Ответ 8

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

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

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