Визначення купи та розміру стека для мікроконтролера ARM Cortex-M4?


11

Я працював і вимикав і вимикав невеликі вбудовані системи. Деякі з цих проектів використовували базовий процесор ARM Cortex-M4. У папці проекту є файл startup.s . Всередині цього файлу я зазначив наступні два командні рядки.

;******************************************************************************
;
; <o> Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>
;
;******************************************************************************
Stack   EQU     0x00000400

;******************************************************************************
;
; <o> Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
;
;******************************************************************************
Heap    EQU     0x00000000

Як можна визначити розмір купи та стека для мікроконтролера? Чи є якась конкретна інформація у таблиці, яка допоможе досягти правильного значення? Якщо так, то що слід шукати у таблиці?


Список літератури:

Відповіді:


12

Стек та купа - це програмні концепції, а не апаратні. Те, що забезпечує обладнання, це пам'ять. Визначення зон пам'яті, одна з яких називається "стек", а одна з них - "купи", - це вибір вашої програми.

Апаратне забезпечення допомагає в стеках. Більшість архітектур мають спеціальний регістр, який називається покажчиком стека. Його призначення полягає в тому, що коли програма робить виклик функції, параметри функції та зворотна адреса висуваються в стек, і вони спливають, коли функція припиняється і повертається до її виклику. Натискання на стек означає писати за адресою, вказаною вказівником стека, і відповідно зменшувати покажчик стека (або збільшувати, залежно від того, в якому напрямку зростає стек). Popping означає збільшення (або зменшення) покажчика стека; зворотна адреса зчитується з адреси, заданої вказівником стека.

Деякі архітектури (але не ARM) мають інструкцію виклику підпрограми, яка поєднує стрибок із записом на адресу, вказану покажчиком стека, та інструкцію повернення підпрограми, що поєднує зчитування з адреси, вказаної вказівником стека, та перехід на цю адресу. На ARM збереження та відновлення адреси здійснюється в регістрі LR, в інструкціях виклику та повернення не використовується покажчик стека. Однак є вказівки для полегшення запису чи читання декількох регістрів на адресу, задану вказівником стека, для просування аргументів функцій push та pop.

Щоб вибрати розмір купи та стека, єдиною релевантною інформацією з обладнання є кількість загальної пам’яті. Потім ви робите свій вибір залежно від того, що ви хочете зберегти в пам'яті (з урахуванням коду, статичних даних та інших програм).

Програма зазвичай використовує ці константи для ініціалізації деяких даних у пам'яті, які будуть використовуватися рештою коду, наприклад, адреса у верхній частині стека, можливо, значення десь перевіряється на переповнення стека, межі для розподільника купи тощо.

У коді, який ви шукаєте , Stack_Sizeконстанта використовується для резервування блоку пам'яті в кодовій області (через SPACEдирективу в складі ARM). Верхня адреса цього блоку надається міткою __initial_sp, і вона зберігається у векторній таблиці (процесор використовує цей запис для встановлення SP після скидання програмного забезпечення), а також експортується для використання в інші вихідні файли. Heap_SizeКонстанта аналогічно використовується для резервування блоку пам'яті і мітку до її кордонів ( __heap_baseі __heap_limit) експортується для використання в інших вихідних файлах.

; Amount of memory (in bytes) allocated for Stack
; Tailor this value to your application needs
; <h> Stack Configuration
;   <o> Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>

Stack_Size      EQU     0x00000400

                AREA    STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem       SPACE   Stack_Size
__initial_sp


; <h> Heap Configuration
;   <o>  Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>

Heap_Size       EQU     0x00000200

                AREA    HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem        SPACE   Heap_Size
__heap_limit

…
__Vectors       DCD     __initial_sp               ; Top of Stack
                DCD     Reset_Handler              ; Reset Handler
                DCD     NMI_Handler                ; NMI Handler

…

                 EXPORT  __initial_sp
                 EXPORT  __heap_base
                 EXPORT  __heap_limit

Чи знаєте ви, як визначаються ці значення 0x00200 та 0x000400
Mahendra Gunawardena,

@MahendraGunawardena Ви самі визначаєте їх, виходячи з того, що потребує ваша програма. Відповідь Найла дає кілька порад.
Жил "ТАК - перестань бути злим"

7

Розміри стеку та купи визначаються вашою програмою, а не де-небудь у таблиці даних мікроконтролера.

Стек

Стек використовується для зберігання значень локальних змінних всередині функцій, попередніх значень регістрів процесора, які використовуються для локальних змінних (щоб їх можна було відновити при виході з функції), адреси програми для повернення при виході з цих функцій, плюс деякі накладні витрати для управління самим стеком.

Розробляючи вбудовану систему, ви оцінюєте максимальну глибину виклику, яку ви очікуєте мати, додаєте розміри всіх локальних змінних функцій у тій ієрархії, а потім додаєте трохи прокладки, щоб врахувати вищезазначені накладні витрати, а потім додайте ще кілька для будь-які переривання, які можуть статися під час виконання вашої програми.

Альтернативний метод оцінки (де оперативна пам'ять не обмежений) полягає в тому, щоб виділити набагато більше місця для стека, ніж вам коли-небудь знадобиться, заповнити стек дозорним значенням, а потім відстежити, скільки ви фактично використовуєте під час виконання. Я бачив версії налагодження мов виконання C, які роблять це для вас автоматично. Потім, закінчивши розробку, ви можете зменшити розмір стека, якщо хочете.

Купа

Розрахувати розмір потрібної купи може бути складніше. Купа використовується для динамічно розподілених змінних, тому якщо ви використовуєте malloc()і free()в мовній програмі C, або newі deleteна C ++, саме там знаходяться ці змінні.

Однак, особливо в C ++, може відбуватися деякий прихований динамічний розподіл пам'яті. Наприклад, якщо у вас є статично виділені об'єкти, мова вимагає викликати їх деструктори, коли програма закінчується. Мені відомо хоча б один час виконання, де адреси деструкторів зберігаються в динамічно виділеному пов'язаному списку.

Отже, щоб оцінити необхідний розмір купи, подивіться на весь динамічний розподіл пам’яті в кожному шляху через ваше дерево викликів, обчисліть максимум та додайте трохи прокладки. Мовна тривалість мовлення може забезпечити діагностику, яку ви можете використовувати для моніторингу загального використання купи, фрагментації тощо.


Дякую за відповідь, мені подобається, як визначити конкретну кількість, наприклад 0x00400 і так далі
Mahendra Gunawardena

5

На додаток до інших відповідей, я хотів би додати, що під час збирання оперативної пам’яті між стеком та купою простору вам також потрібно враховувати простір для статичних непостійних даних (наприклад, файлові глобали, статистика функцій та загальна програма глобалістів з точки зору С та, ймовірно, інших для С ++).

Як працює розподіл стека / купи

Варто зазначити, що файл збірки запуску - це один із способів визначення регіону; ланцюжок інструментів (як середовище збирання, так і середовище виконання) в основному дбають про символи, що визначають початок простору стека (використовується для зберігання початкового вказівника стека у векторній таблиці) та початок і кінець простору купи (використовується динамічним розподільник пам'яті, як правило, надається вашим libc)

У прикладі ОП визначено лише 2 символи, розмір стека в 1кіБ та розмір купи в 0В. Ці значення звикають в іншому місці, щоб фактично створити простір стека та купи

У прикладі @Gilles розміри визначаються та використовуються у складальному файлі для встановлення місця стеку, починаючи звідки і триваючи розмір, ідентифікований символом Stack_Mem та встановлює мітку __initial_sp в кінці. Так само і для купи, де пробілом є символ Heap_Mem (розміром 0,5кіБ), але з мітками на початку та в кінці (__heap_base та __heap_limit).

Вони обробляються лінкером, який не виділяє нічого в просторі стека та купі, оскільки ця пам'ять зайнята (символами Stack_Mem та Heap_Mem), але вона може розмістити ці пам’яті та всі глобальні місця, де це потрібно. Мітки в кінцевому підсумку є символами без довжини за вказаними адресами. __Initial_sp використовується безпосередньо для векторної таблиці під час посилання, а __heap_base та __heap_limit за вашим кодом виконання. Фактичні адреси символів присвоює лінкер залежно від місця їх розміщення.

Як я вже згадував вище, ці символи насправді не повинні надходити з файлу startup.s. Вони можуть виходити з конфігурації вашого лінкера (файл Scatter Load у Keil, linkerscript у GNU), а також у тих, у кого ви можете мати більш тонкий контроль над розміщенням. Наприклад, ви можете змусити стек бути на початку або в кінці оперативної пам’яті, або тримати ваш глобус подалі від купи, або чого завгодно. Ви навіть можете вказати, що HEAP або STACK просто займають будь-яку оперативну пам'ять, що залишилася після розміщення глобальних точок. Зауважте, що вам потрібно бути обережними, якщо додавати більше статичних змінних, що зменшиться у вашій іншій пам'яті.

Однак, кожен ланцюжок інструментів відрізняється, і те, як записати файл конфігурації та які символи використовуватиме ваш динамічний розподільник пам'яті, має виходити з документації вашого конкретного середовища.

Розміщення стека

Щодо того, як визначити розмір стека, багато ланцюжків інструментів можуть дати вам максимальну глибину стека, проаналізувавши дерева функцій викликів вашої програми, ЯКЩО ви не використовуєте рекурсії чи покажчики функцій. Якщо ви користуєтесь ними, оцінюючи розмір стека та попередньо заповнюючи його кардинальними значеннями (можливо, за допомогою функції введення перед головним), а потім перевіряйте, чи не запустилася програма на деякий час там, де була максимальна глибина (де де знаходяться кардинальні значення) кінець). Якщо ви повністю застосували програму до її меж, ви досить точно знаєте, чи можете ви зменшити стек або, якщо ваша програма виходить з ладу або не залишилося кардинальних значень, вам потрібно збільшити стек і спробувати ще раз.

Купівля розмірів

Визначення розміру купи трохи більше залежить від програми. Якщо ви робите динамічний розподіл лише під час запуску, ви можете просто додати простір, необхідний у вашому стартовому коді (плюс деякі накладні витрати для управління пам'яттю). Якщо у вас є доступ до джерела диспетчера пам’яті, ви можете точно знати, що таке накладні витрати, і, можливо, навіть написати код, щоб пройти пам’ять, щоб дати вам інформацію про використання. Для додатків, які потребують динамічної пам’яті виконання (наприклад, виділення буферів для вхідних кадрів Ethernet), найкраще, що я можу запропонувати, - це ретельно відточити розмір стека і надати Heap все, що залишилося після стека та статики.

Заключна примітка (RTOS)

Питання OP було позначене як голий метал, але я хочу додати примітку для RTOSes. Часто (завжди?) Кожному завданню / процесу / потоці (я просто напишу тут завдання для простоти) призначається розмір стека, коли завдання створюється, окрім стеків завдань, ймовірно, буде невелика ОС стек (використовується для переривань тощо)

Структури обліку завдань та стеки мають бути розподілені звідкись, і це часто буде залежати від загального простору вашої програми. У цих випадках ваш початковий розмір стека часто не має значення, оскільки ОС буде використовувати його лише під час ініціалізації. Я бачив, наприклад, вказуючи, що ВСЕ залишився простір під час посилання буде виділено HEAP і розмістити початковий покажчик стека в кінці купи, щоб перерости в купу, знаючи, що ОС виділить, починаючи з початку купи і виділить стек ОС безпосередньо перед відмовою від стеку початковий_sp. Потім весь простір використовується для розподілу стеків завдань та іншої динамічно розподіленої пам'яті.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.