При использовании RTOS (например FreeRTOS) у нас организуются отдельные пространства стека для каждой задачи (task). А как насчет обработчиков прерывания (ISR, Interrupt Service Routine), есть ли у них отдельный стек? Или как это можно сконфигурировать? И если отдельного стека нет, то где сохраняются локальные переменные, которые определены в теле ISR?
Вопрос довольно сложный, и по сути он зависит от платформы, на которой работает embedded-приложение. Другими словами, от используемого микроконтроллера, от используемой RTOS и от того, как RTOS формирует среду выполнения кода в реальном времени.
Например, если рассмотреть один из популярных чипов на платформе ARM Cortex-M3 (для которой прерывания являются формой исключения, exception), то из документации можно почерпнуть следующее.
Рабочие режимы. Платформа Cortex-M3 поддерживает привелигированный (Privileged) режим выполнения кода, и пользовательский, не привилегированный (User) режим. Код, работающий в привилегированном режиме, получает полные права на выполняемые операции, в то время как на пользовательский код накладываются определенные ограничения. Это включает ограничения на использование таких инструкций, как MSR, ограничение на доступ к памяти и периферийным устройствам в соответствии с дизайном системы, а также ограничения, накладывамые конфигурацией MPU.
Процессор поддерживает два рабочих режима, Thread mode и Handler mode. Thread mode активируется сразу после сброса, и обычно каждый раз при возврате из исключения (exception, или прерывания). В Thread mode код может выполняться либо как привилегированный (Privileged), либо как не привилегированный (Unprivileged).
Handler mode активируется в результате возникновения exception. Код в Handler всегда работает как Privileged, поэтому при возникновении исключений ядро автоматически переключается в привилегированный режим. Можно переключаться между Privileged Thread mode и User Thread mode, когда происходит возврат из exception, путем модификации значения EXC_RETURN в link-регистре (R14). Также можно переключиться из режима Privileged Thread в режим User Thread очисткой CONTROL[0] с помощью инструкции MSR. Однако нельзя напрямую переключиться в привилегированный режим без вовлечения exception, например через инструкцию SVC.
Стеки Main и Process. Cortex-M3 поддерживает два разных стека - основной (main stack) и процесса (process stack). Для этого на платформе Cortex-M3 имеется два указателя стека (R13). Один из этих регистров отбрасывается, в зависимости от используемого стека. Это значит, что в любой момент времени только один указатель стека виден как R13. Однако к обоим этим указателям стека можно получить доступ с помощью инструкций MRS и MSR. Основной (main) стек используется после сброса, и всегда используется в Handler mode (при входе в обработчик исключения, exception handler). Указатель стека процесса (process stack) доступен только как текущий указатель стека, когда активен Thread mode. Вы можете выбрать, какой указатель стека (main или process) используется в Thread mode, с помощью одного из двух способов: либо использованием значения EXC_RETURN при выходе из Handler Mode, ли когда в Thread Mode записываете CONTROL[1] с помощью инструкции MSR.
Когда процессор получает exception (кроме случаев tail-chained или late-arriving exception), он проталкивает информацию о состоянии выполнения в текущий стек. Структура из 8 слов данных такой информации называется кадром стека (stack frame). Сразу после завершения операции проталкивания значение указателя стека (stack pointer, SP) соответствует самому малому адресу в этом кадре.
Из книжки "The Definitive Guide to the ARM Cortex-M3":
MSP, который также называется SP_main (основной стек) в документации ARM, является указателем стека по умолчанию (default SP) после включения питания (сброса). Этот SP используется кодом ядра (kernel code) и кодом обработчиков исключений (exception handlers). PSP, или SP_process (указатель стека процесса) в документации ARM, обычно используется процессамим потоков во встраиваемых операционных системах (OS или RTOS).
Из-за того, что обработчики исключений всегда используют MSP, область памяти основного стека должна быть достаточно большой, чтобы хранить в себе самое большое количество состояний вложенных прерываний и вложенных вызовов функций основного кода.
Когда возникает exception, в стек проталкиваются регистры R0, R1, R2, R3, R12, LR, PC и PSR (8 слов, кадр стека). Если выполняемый код использует Process Stack Pointer (PSP), то это будет стек процесса (process stack); если выполняемый код использует Main Stack Pointer (MSP), то используется основной стек (main stack). После этого основной стек всегда будет использоваться в обработчике исключения, поэтому все вложенные прерывания будут использовать основной стек.
[FreeRTOS и платформа Cortex]
MSP используется в прерываниях (и до начала работы планировщика). PSP используется задачами. Таким образом, независимо от того, используете ли вы варианты MPU или нет. Привилегированный или непривилегированный режим не связан с MSP и PSP. Прерывания всегда выполняются с использованием MSP (но будут стеком стека исключений на любом стеке, который в настоящее время используется перед прерыванием)
Стандартная версия FreeRTOS для Cortex-M3 по факту конфигурирует и использует как MSP, так и PSP. MSP используется в прерываниях (и до момента запуска планировщика), PSP используется задачами. Это верно независимо от того, используется ли MPU, или нет. Привилегированный или непривилегированный режим не связан с MSP и PSP. Прерывания всегда выполняются с использованием MSP (однако это может быть стек exception, или он использовался перед прерыванием).
Когда запускается самая первая задача, она модифицирует MSP на самый первый адрес в таблице векторов прерываний (0x00000000). Далее запускается системный вызов обработчика исключения, который устанавливает PSP на место стека следующей задачи, затем модифицирует значение exception LR таким образом, что возврат происходит в thread mode, и при возврате используется стек процесса.
Это означает, что в обработчике исключения (exception handler, или обработчике прерывания ISR, для Cortex это же самое) стек растет в сторону меньших адресов от адреса, указанного в таблице векторов прерываний.
Можно настроить linker и код startup так, чтобы область стека обработчика исключения находилась в нужном месте, и чтобы эта область была достаточного размера.
Чтобы помочь разобраться, какое требуется в приложении пространство для стека ISR (MSP), можно добавить дополнительный код в порт FreeRTOS (port.c [2]).
Имейте в виду, что для других чипов и операционных систем организация стеков может полностью отличаться.
[Словарик]
EXC_RETURN регистр, используемый для получения дополнительной информации о том, в какое состояние следует вернуться после обработки исключения, и какие регистры должны быть извлечены из стека. EXC_RETURN не является фактическим регистром, который можно прочитать.
ISR Interrupt Service Routine, обработчик прерывания.
MPU Memory Protection Unit, блок защиты памяти.
MSP Main Stack Pointer, указатель основного стека.
MRS инструкция, которая переместит содержимое PSR в регистр общего назначения. Использование MRS в комбинации с инструкцией MSR является частью последовательности read-modify-write для обновления PSR, например для смеры режима процессора, или для очистки флага Q. В коде переключения между процессами необходимо сохранить состояние программной модели процесса, включая соответствующее содержимое PSR. Подобным образом состояние вытесненного процесса также должно быть восстановлено. Эти операции использут последовательности инструкций MRS/store и load/MSR.
MSR инструкция, загружающая регистр сопроцессора из регистра ARM. См. также MRS.
PSP Process Stack Pointer, указатель стека процесса.
PSR Program Status Register, регистр состояния программы.
SVC SuperVisor Call, инструкция вызова режима супервизора. Использование инструкции SVC приводит к исключению (exception). Это значит, что режим процессора меняется на Supervisor, CPSR сохраняется в Supervisor mode SPSR, и ветвление переходит на вектор SVC.
[Ссылки]
1. Does ISR (Interrupt Service Routine) have a separate stack? site:stackoverflow.com. 2. FreeRTOS Helpers site:github.com. 3. Использование стека в IAR и файлы управления стеком. 4. Проектирование стека и кучи в IAR. |