Встроенные сценарии компоновщика - правильное размещение областей "стека" и "кучи"?

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

В качестве примера я рассмотрел файлы, представленные в пакете прошивки "CubeMX" ST для их чипов F0, которые имеют ядра ARM Cortex-M0. Я вставил бы целый сценарий, если бы лицензии файлов позволяли это, но вы можете загрузить весь пакет из ST бесплатно, если вам интересно 1. В любом случае, вот части, относящиеся к моему вопросу:

/* Highest address of the user mode stack */
_estack = 0x20001000;    /* end of RAM */
/* Generate a link error if heap and stack don't fit into RAM */
_Min_Heap_Size = 0x200;      /* required amount of heap  */
_Min_Stack_Size = 0x400; /* required amount of stack */

<...>

SECTIONS {
  <...>

  .bss :
  {
    <...>
  } >RAM

  /* User_heap_stack section, used to check that there is enough RAM left */
  ._user_heap_stack :
  {
    . = ALIGN(8);
    PROVIDE ( end = . );
    PROVIDE ( _end = . );
    . = . + _Min_Heap_Size;
    . = . + _Min_Stack_Size;
    . = ALIGN(8);
  } >RAM

  <...>
}

Итак, вот мое, вероятно, неправильное понимание поведения компоновщика:

  • Значение "_estack" устанавливается в конец ОЗУ - этот сценарий предназначен для чипа "STM32F031K6", который имеет 4 КБ ОЗУ, начиная с 0x20000000. Он используется в примерах векторных таблиц ST для определения указателя начального стека, поэтому кажется, что это должно пометить один конец блока памяти "Stack".

  • Значения "_Min_Heap_Size" и "_Min_Stack_Size" выглядят так, как будто они должны определять минимальный объем пространства, которое должно быть выделено для стека и кучи для использования программой. Программы, которые выделяют много динамической памяти, могут нуждаться в большем пространстве "кучи", а программы, которые вызывают глубоко вложенные функции, могут нуждаться в большем пространстве "стека".

У меня вопрос, как это должно работать? Являются ли специальные метки '_Min_x_Space' или эти имена могут слегка сбить с толку? Потому что похоже, что скрипт компоновщика просто добавляет сегменты памяти этих точных размеров в ОЗУ без учета фактического использования программы.

Кроме того, пространство, определенное для стека, по-видимому, не обязательно определяет непрерывный сегмент между его началом и значением '_estack', определенным выше. Если нет другой используемой оперативной памяти, nm показывает, что раздел "_user_heap_stack" заканчивается на 0x20000600, что оставляет кучу пустой оперативной памяти перед "_estack".

Единственное объяснение, которое я могу придумать, состоит в том, что сегменты 'Heap' и 'Stack' могут не иметь реального значения и определяются только как гарантия времени компиляции, так что компоновщик выдает ошибку, когда доступно значительно меньше динамической памяти, чем ожидается. Если это так, то должен ли я думать о нем как о минимальном размере "Объединенная куча / стек"?

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

2 ответа

Решение

Значение "_estack" устанавливается в конец ОЗУ - этот сценарий предназначен для чипа "STM32F031K6", который имеет 4 КБ ОЗУ, начиная с 0x20000000. Он используется в примерах векторных таблиц ST для определения указателя начального стека, поэтому кажется, что это должно пометить один конец блока памяти "Stack".

Поскольку стек здесь будет расти вниз (от высоких до низких адресов), это фактически начало области памяти стека.

Являются ли специальные метки '_Min_x_Space' или эти имена могут слегка сбить с толку?

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

Потому что похоже, что скрипт компоновщика просто добавляет сегменты памяти этих точных размеров в ОЗУ без учета фактического использования программы.

Это минимальный размер. И стек, и разрыв кучи могут расти.

Если не используется другая оперативная память, nm показывает, что раздел "_user_heap_stack" заканчивается на 0x20000600, что оставляет кучу пустой оперативной памяти перед "_estack"

Он оставляет ровно 0x400 байт, что _Min_Stack_Size, Стоп Remeber растет вниз здесь (и часто в других местах).

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

Не все критично для безопасности. Вы свободны не использовать кучу, если не хотите / нуждаетесь / имеете право. (Хорошо, не так бесплатно в последнем)

Вы задаете вопрос, где разместить стек и кучу. На uC ответ не так очевиден, как указано @a2f по многим причинам.

стек

Первый из многих ARM UC имеет два стека. Один называется Master Stack, а второй - Process Stack. Конечно, вам не нужно включать эту опцию.

Другая проблема состоит в том, что Cortex uC может иметь (например, STM32F3, много F4, F7, H7) много блоков SRAM. Разработчик должен решить, где разместить стек и кучу.

Где разместить стек? Я бы предложил разместить MSP в начале выбранного ОЗУ. Зачем? Если стек находится в конце, вы не можете контролировать использование стека. Когда стек переполняется, он может молча перезаписать ваши переменные, и поведение программы становится непредсказуемым. Это не проблема, если это мигание светодиода. Но представьте себе большой контроллер машины или поломку компьютера.

Когда вы помещаете стек в начало ОЗУ (в качестве начала я имею в виду начальный адрес ОЗУ + размер стека), когда стек собирается переполниться, генерируется аппаратное исключение. Вы полностью контролируете UC, вы можете видеть причину проблемы (например, поврежденный датчик, заполняющий UC данными) и запускать аварийную программу (например, остановить машину, перевести автомобиль в режим обслуживания и т. Д. И т. Д.), Переполнение стека не произойдет незамеченным.

Куча

Динамическое распределение должно использоваться с осторожностью для ОК. Первая проблема заключается в возможной фрагментации памяти доступной памяти, поскольку у uC очень ограниченные ресурсы. Использование динамически распределенной памяти должно рассматриваться очень осторожно, иначе это может стать источником серьезных проблем. Некоторое время назад библиотека USB HAL использовала динамическое выделение в подпрограмме прерывания - иногда доли секунды было достаточно, чтобы фрагментировать кучу, чтобы предотвратить дальнейшее распределение.

Другая проблема - неправильная реализация sbrk в большинстве доступных наборов инструментов. Единственное, что я знаю с правильным, - это набор инструментов BleedingEdge, поддерживаемый нашим коллегой с этого форума @Freddie Chopin. Проблема состоит в том, что реализации предполагают, что куча и стек растут навстречу друг другу и в конечном итоге могут встретиться - что, конечно, неправильно. Другая проблема - неправильное использование и инициализация статических переменных с адресами начала и конца кучи.

Другие вопросы по тегам