Как я могу визуализировать использование памяти (SRAM) программы AVR? - PullRequest
19 голосов
/ 06 июня 2009

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

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

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

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

Ответы [ 8 ]

21 голосов
/ 25 августа 2011

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

Ниже приведен пример того, как установить это в IDE: В Code :: Blocks, Project -> Параметры сборки -> Шаги до / после сборки -> Шаги после сборки, включайте:

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

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

11 голосов
/ 22 июня 2009

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

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

Необходимо помнить о двух сценариях:

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

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

EDIT:

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

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

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

char *buffer;

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

новый код:

char buffer[BUFFSIZE];

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

3 голосов
/ 07 июня 2009

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

2 голосов
/ 11 июня 2009

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

2 голосов
/ 07 июня 2009

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

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

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

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

1 голос
/ 11 июня 2009

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

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

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

0 голосов
/ 22 июня 2009

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

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

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

0 голосов
/ 09 июня 2009

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

...