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

StackOverflow https://stackoverflow.com/questions/960389

Вопрос

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

Есть ли какой-нибудь способ, которым я могу визуализировать использование SRAM стеком и кучей?

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

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

Это было полезно?

Решение

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

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

Следует иметь в виду два сценария:

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

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

РЕДАКТИРОВАТЬ:

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

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

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

char *buffer;

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

новый код:

char buffer[BUFFSIZE];

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

Другие советы

Вы можете проверить статическое использование оперативной памяти с помощью 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:В коде:: Блоки, Проект -> Параметры сборки -> Шаги до / после сборки -> Шаги после сборки, включают:

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 все еще не поврежден.То, как вы «проверяете», зависит от вашего целевого оборудования.Предполагая, что у вас подключен отладчик, вы можете просто остановить работу микроконтроллера и прочитать память.

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

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

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top