Код запуска (startup)
Функция main — не первое, что выполняет микроконтроллер после сброса. До неё работает код запуска: он готовит указатель стека и память так, чтобы программа на C увидела глобальные переменные с правильными начальными значениями.
Обновлено Редакция GAW
Код запуска (startup code, crt0) — короткая программа, которая выполняется после сброса до функции main. Её задача — подготовить среду, которую ожидает программа на C: стек, глобальные переменные с начальными значениями, нулевые переменные, конструкторы. Обычно она приходит готовой: файл startup_stm32f103xb.s у STM32 или crt1 из avr-libc у AVR.
Cortex-M (шаблон ST для STM32F103)
- Ядро само загружает указатель стека из слова 0 таблицы векторов и переходит по адресу из слова 1 — на
Reset_Handler. SystemInit— начальная настройка тактирования.- Копирование секции
.dataиз флеш-памяти в ОЗУ. - Обнуление
.bss. __libc_init_array— конструкторы C++ и функции инициализации библиотеки.- Переход в
main.
Про .data и .bss подробно — в термине «Секции .data и .bss». Стандарт вызова AAPCS32 требует, чтобы на границе вызова функций указатель стека был кратен 8 (SP mod 8 = 0). В шаблоне ST для STM32F103 из cmsis-device-f1 стоит _estack = 0x20004FFF: ядро обнулит два младших бита и получит 0x20004FFC, что не кратно 8 (наш расчёт по правилу B1.5.5). Надёжнее задавать вершину стека адресом конца ОЗУ, кратным 8, например 0x20005000 — это наша рекомендация, а не значение из шаблона.
AVR (avr-libc)
Код запуска разбит на секции .init0–.init9, которые выполняются по порядку: .init2 ставит указатель стека на __stack (по умолчанию последний адрес ОЗУ, RAMEND), .init4 инициализирует .data и .bss, .init9 вызывает main. Свою функцию можно поместить в свободную секцию — .init1, .init3, .init5, .init7 или .init8 — с атрибутами naked, used и section: avr-libc требует, чтобы такой код не содержал возврата и никогда не вызывался явно, а порядок нескольких функций в одной секции не определён. Код на C ставят не раньше .init3.
Не путать код запуска прошивки с кодом запуска отдельной периферии — например, последовательностью включения сторожевого таймера, которую генерирует калькулятор IWDG и WWDG STM32. И не путать с загрузчиком: загрузчик — отдельная программа, которая принимает и записывает прошивку, а код запуска — часть каждой прошивки. Как символы _estack, _sidata, _sbss попадают в код запуска из скрипта компоновщика — в статье «Скрипт компоновщика GNU ld», типы и переменные в C для микроконтроллеров — в статье «Язык C для микроконтроллеров».
Частые вопросы
Что делает Reset_Handler у STM32?
В шаблоне ST для STM32F103: вызывает SystemInit (начальная настройка тактирования), копирует начальные значения .data из флеш-памяти в ОЗУ, заполняет .bss нулями, вызывает __libc_init_array — конструкторы C++ и функции инициализации — и переходит в main. Указатель стека к этому моменту уже загружен ядром из слова 0 таблицы векторов.
Кто устанавливает указатель стека на Cortex-M?
Само ядро: по руководству ARMv7-M при сбросе оно берёт начальное значение SP из первого слова таблицы векторов. В шаблоне ST туда записан символ _estack — вершина ОЗУ. У AVR указатель стека ставит код запуска в секции .init2, по умолчанию на последний адрес ОЗУ.
Можно ли выполнить код до обнуления .bss?
Да, но осторожно: глобальные переменные в этот момент ещё не инициализированы. У STM32 для этого есть SystemInit. У AVR код помещают в свободные секции .init1, .init3, .init5, .init7 или .init8: функция с атрибутами naked, used и section, без возврата, и её никогда не вызывают явно; код на C — не раньше .init3, потому что указатель стека и нулевой регистр готовит .init2. Так, по FAQ avr-libc, рано настраивают MCUCR или сторожевой таймер.
Источники
- STMicroelectronics. cmsis-device-f1, коммит c8e9a4a: Source/Templates/gcc/linker/STM32F103XB_FLASH.ld и Source/Templates/gcc/startup_stm32f103xb.s — Reset_Handler: вызов SystemInit, копирование .data словами, обнуление .bss, __libc_init_array, переход в main; g_pfnVectors: слово 0 — _estack, слово 1 — Reset_Handler; _estack = 0x20004FFF в шаблоне Ac6
- Arm. ARMv7-M Architecture Reference Manual, DDI 0403E.e, 2021 — B1.5.5: при сбросе SP_main = слово 0 таблицы векторов с обнулёнными двумя младшими битами
- Arm. Procedure Call Standard for the Arm Architecture (AAPCS32), github.com/ARM-software/abi-aa, коммит 7850367 — Stack constraints at a public interface: SP mod 8 = 0, стек выровнен на двойное слово
- AVR-LibC 2.3.2: Memory Sections (doc/api/sections.dox), crt1/gcrt1.S — секции .init0–.init9: .init2 — указатель стека = __stack (по умолчанию RAMEND), .init3 — раскраска ОЗУ (если используется), .init4 — инициализация .data и .bss, .init9 — переход в main
Нашли ошибку?
Смотрите также
- Скрипт компоновщика GNU ldMEMORY, SECTIONS, VMA и LMA, KEEP, стек и куча, map-файл, переполнение
- Таблица векторов прерыванийАдреса обработчиков прерываний в начале флеш-памяти
- Секции .data и .bssГде лежат глобальные переменные: копирование и обнуление при старте
- C для микроконтроллеровТипы, stdint, volatile, атомарность, регистры, константы во Flash