Pull to refresh

Comments 19

эта часть не слишком сложна и я сам могу предлоюить пару простеньких вариантов


исправьте пожалуйста.
Вопрос дилетанта: чем еще отличаются MAIN и Process?
Может MAIN это все же пользовательский?
В том то и дело что MAIN системный.
Здесь же прерывание — вот и обсчитывают стек.
> Ну зачем разрывать вычисление значения вычислением адреса?
Обычная оптимизация?
Насколько я помню, у Cortex-M 3хстадийный конвейер с тремя «EU». Причём LDR-операции идут в 2 фазы, загрузки адреса и загрузки данных.
Конкретно с Cortex-M не работал в плане оптимизаций, но думаю что вполне логично перемешивать инструкции «вручную», ведь OoO-то нет

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

PS: У меня есть контакты Richard Barry, основного разработчика RTOS, если интересно, можно напрямую его спросить.
Ок, если сократить, то вопрос на самом деле звучит просто:
«Почему нельзя из Handler mode писать напрямую в process stack.».

Несогласен немного с тем, что требуется дополнительная аппаратура для реализации этой фичи, скорее наоборот, ибо сейчас можно сделать простую проверку:
if (SPCR[1] && mode == ThreadMode)
  sp = processSP
else
  sp = mainSP


Думаю что так сделано просто исходя из задач этих двух типов стека. Process_SP в Handler mode может понадобиться как правило только для планировщика.
На это ясно намекают строки в мануале:
Using the process stack for the Thread mode and the main stack for exceptions supports
Operating System (OS) scheduling. To reschedule, the kernel only requires to save the
eight registers not pushed by hardware, r4-r11, and to copy SP_process into the Thread
Control Block (TCB). If the processor saved the context on the main stack, the kernel
would have to copy the 16 registers to the TCB
Ну так в планировщике то он напрямую не доступен, поскольку планировщик работает в Handler режиме.
Дык я о том и пишу, что в Handler mode нужен доступ к process_sp только в одном случае — планировщик.
А в нём уже можно и напрямую к памяти обратиться, а городить ради этого вырожденного случая доступ к PSP в Handler mode не обязательно.
Здесь вопрос не к RTOS, а к ядру МК.
Подумаю вслух :)

Допустим в режиме прерывания можно было бы переключать стеки.
Допустим код планировщика переключил стек с Main на Process.
В этот момент происходит другое прерывание, с более высоким приоритетом, оно отработает на стеке Process.
Казалось бы — что в этом плохого, ведь прерывание подчистит за собой и все будет хорошо? Нет, не будет. При таком раскладе придется в размер стек каждого потока закладывать не только его потребности, но и учитывать сколько съедят все прерывания — теряется вся прелесть двух указателей. Либо в начале каждого обработчика добавлять пролог переключающий стек на Main — и именно это инженеры ARM сделали за нас на аппаратном уровне.
Ну вот тут, пожалуй, соглашусь, что размер стека процесса существенен. Я остановился на фазе, что все подчистит и успокоился.
Конечно, можно запретить перед переключением стека прерывания, тем более что это делается несколькими командами ниже, но это как то не очень хорошо.
Но все равно на вопрос не отвечает — согласен, что нам не следует напрямую переключать стек в данном случае, особого выигрыша не будет, но почему нам это ЗАПРЕТИЛИ делать в режиме супервизора и оставили в пользовательском? Вот что непонятно.
А если подойти «с другой стороны» — запрещали-ли? Просто не предусмотрели возможность, т.к. не посчитали нужным. Прерывания всегда работают на стеке Main — это удобно. Основной код приложения может работать на стеке Main при отсутствии ОС, либо на стеке Process при наличии OS. Переключение кода приложения на стек Process — задача загрузчика ОС.

Почему регистр Control в режиме прерывания не пишется? Потому что их два и доступный в данный момент привязан к текущему режиму (приложение/обработчик).

PS Тут нельзя повернуть не потому что запрещено, а потому что дороги нет.
Если предположение выше верно — то вполне объясняет, почему. Если требовалось обеспечить, чтобы в режиме супервизора всегда стек указывал на Main — то все логично выглядит. Похоже, что при переходе из пользовательского режима в режим супервизора стек переключается принудительно, а вот при переходе из супервизова в супервизора (вложенные прерывания) — нет. А раз нет, значит, нужно обеспечить, чтобы стек оставался неизменным, пока мы в супервизоре.
Ну зачем разрывать вычисление значения вычислением адреса?

Чтобы скрыть load-to-use латентность LDR. Но в случае Cortex-M это не нужно.

Почему же используются не команда PUSH, а команда длинной пересылки, причем с пересылками регистр-регистр

1) PUSH работает с регистром R13. Который в данном случае указывает не на пользовательский стек.
о чём как бы намекают push {r3, r14} / bl vTaskSwitchContext

2) Как вы видите, сначала сохраняются младшие регистры (ldmia), потом старшие. В случае PUSH нужно делать наоборот.
FreeRTOs весьма не оптимально это делает — они же под несколько архитектур заточены и этот код у них во много содран со старых ARM-7. И про переключение стеков — в user mode же эта команда запрещена. Так что в ногу выстрелить нельзя — единственный способ переключить стек и режим — SVC.
И это совершенно правильно — для переключения стека надо обратиться к супервизору.
Но когда супервизору это запретили, а юзеру — нет, я перестаю что либо пониматью
Возможно, это сделано для одностороннего вызова из пользовательского кода в код ядра: например, вы записываете аргументы в системный стек и вызываете программное прерывание — таким образом происходит системный вызов. Обратный же вызов должен быть запрещен — переход от ядра к пользовательскому коду должен быть только по команде reti — возврат из прерывания. Причина тому — возврат из пользовательской функции, который, в таком случае, должен был бы вернуться в ядро, а это плохая идея, поскольку, установка адреса возврата стала бы доступна пользовательскому коду.
А по мне так всё логично. Переключение на работу в своём стеке должно производиться не в прерывании (в 90% случаев). Обычно это делается при инициализации. А значит переключать должен Thread, а не Handler. Поэтому ему и дали эту функцию. Предполагается, что переключение стека должно производиться один раз при инициализации.

А теперь посмотрим на мой любимый Cortex-M3. Там есть привилегированный режим. Включаем его и Handler, пусть и неудобно, но может всё (и правильно, что неудобно, раз неудобно — значит что-то делается неправильно), а вот Thread может быть ограничен в своей песочнице контроллером MMU. Поменять стек он больше не может, т.к. в непривилегированном режиме ему это запрещается. Вот вам и главенство Handler'а (ядра) над Thread'ом (приложением).

Cortex-M0/M1 разрабатывались не для исполнения ОС. Они под firmware сделаны с одним фоновым процессом и обработчиками. Просто там ко всему прочему дали возможность иметь разные стеки для обработчиков и фонового процесса — и всё. Остальных элементов — привилегированного режима и MMU -, присущих процессорам под ОС, там нету. Т.ч. это не MPU, а MCU и, строго говоря, ОС на них крутить — значит использовать не совсем по назначению. А раз так, то и не надо удивляться, что задачи приходится решать «криво».
Sign up to leave a comment.

Articles