Миро Самек, Роберт Вард "Построение наипростейшего диспетчера задач" (перевод)


Сравнение SST с ядрами традиционных операционных систем

Работая с рабочими контекстами всех задач и прерываний в единственном стеке SST расходует заметно меньше оперативной памяти, чем обычные ядра с блокировкой. Задачи не имеют индивидуальных стеков и не тратят память на неиспользуемые стеки спящих процессов. Переключение задач требует меньше вычислительных затрат. Традиционные операционные системы не делают различий между синхронным и асинхронным вытеснением и заставляют платить стандартную цену за все переключения контекста. Наконец, SST использует более простой и компактный формат блока описания задачи (task control blocks - TCB).

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

В момент прерывания традиционное ядро должно завести жёстко определённый стековый кадр в каждом индивидуальном стеке, в котором будут сохранены все регистры процессора [* зачем в каждом? прерывается только одна задача, активная в данный момент - все остальные уже спят с сохранённым стеком]. В отличие от SST, который может воспользоваться встроенным в компилятор алгоритмом работы с регистрами, традиционные операционные системы должны быть подготовлены к восстановлению всех регистров в процессе возобновления работы прерванного процесса. Часто создание и ликвидация таких стековых кадров должна проводиться с помощью ассемблерного кода, так как обычные операционные системы не могут полагаться на компилятор в вопросах низкоуровневого переключения задач. У SST, напротив, нет нужды заботиться о формате стековых кадров или о методах сохранения регистров процессора после вхождения в обработчик прерывания. Единственным значимым фактором является точное соответствие восстановленных параметров процессора тому состоянию, которое предшествовало прерыванию, а как это сделано - неважно. Это означает, что встроенных в большинство компиляторов языка Си механизмов работы с прерываниями достаточно для SST, но часто недостаточно для обычных операционных систем. Программирования на ассемблере не требует не только сам SST, но и код пролога и эпилога, вставленный в обработчик прерывания компилятором. Это происходит, потому что код компилятора гораздо лучше соответствует задачам оптимизации стековых кадров ISR. Отсюда видно, что SST позволяет использовать перимущества, предоставляемые компиляторами, на что часто не способны традиционные операционные системы.

Последнюю особенность лучше проиллюстрировать примером. Все компиляторы языка Си для процессоров ARM придерживаются стандарта вызовов процедур фирмы ARM (ARM Procedure Call Standard - APCS), определяющему те регистры, содержимое которых подпрограмма изменять не может, и те, которые можно перезаписывать. Пролог обработчика прерывания, создаваемый компилятором, сохраняет только те регистры, которые могут быть изменены, а они составляют приблизительно половину всех регистров процессора. Остальные регистры будут сохранены позднее внутри Си-функции, вызываемой из обработчика, и только в том случае, когда они и в самом деле используются. Данный пример поэтапного сохранения контекста хорошо соответствует концепции построения SST. В противоположность такому сценарию обычные операционные системы обязаны сохранять ВСЕ регистры в единой технологической операции в начале обработчика прерывания. Если при этом ассемблерная оболочка ISR вызывает Си-функции (что обычно и происходит), многие регистры сохраняются повторно. Вряд ли нужно говорить, что по сравнению с SST такой подход требует больше оперативной памяти и больше тактов процессора (возможно двухкратное увеличение).

На врезке подчёркивается, что описанная проблема наличествует во всех обычных невытесняющих операционных системах. Читателю следует обратиться к книге "MicroC/OS-II" [3] не только потому что в ней есть пояснения по данной теме, но и потому что книга содержит отличное описание обычных операционных систем и знакома многим разработчикам встраиваемых приложений.

Продолжительность прерываний SST и традиционных операционных систем

Если вы знакомы с обычными вытесняющими операционными системами, то, возможно, обратили внимание на то, что SST поощряет программиста нарушать общепринятую рекомендацию по сокращению времени работы обработчиков прерываний.

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

На самом деле проблема в прояснении разницы между прерыванием и обычной задачей. SST заставляет пересмотреть обычный взгляд на длительность прерывания как на момент времени между сохранением контекста в стеке и восстановлением его непосредственно перед инструкцией IRET, так как определить этот момент затруднительно даже для традиционных операционных систем.


Рисунок 4. Контекстная информация, создаваемая традиционными операционными системами
Рисунок 4.
          Система с фоновой и приоритетными задачами

На рисунке 4 показаны структуры данных, создаваемые вытесняющей операционной системой, в которой рабочий контекст каждой задачи состоит из её собственного стека и блока описания процесса (TCB) [3]. В общем случае обработчик прерывания сохраняет контекст прерывания в стеке одной задачи, а восстанавливает его из стека другой. После восстановления регистров процессора диспетчер традиционной операционной системы выполняет инструкцию IRET. Основная особенность ситуации состоит в том, что контекст прерывания остаётся сохранённым в стеке передавшей управление задачи и, в результате, контекст "живёт" дольше, чем время работы самого обработчика. Таким образом, установление длительности работы по моментам между сохранением и восстановлением контекста выглядит довольно сомнительно.

В ядре с одним стеком, подобном SST, ситуация развивается сходным образом. Обработчик сохраняет контекст в общем для всех задач и прерываний стеке. После проведения некоторых действий обработчик вызывает диспетчер, который разрешает прерывания. Если задач с более высоким приоритетом нет, то диспетчер тут же возвращает управление, и в этом случае обработчик восстанавливает контекст из стека и возвращает управление в ту самую точку прерванной задачи, в которой произошёл перехват. При наличии более приоритетных задач диспетчер SST передаёт управление им, а контекст прерванного процесса остаётся в стеке, так же как и в обычных операционных системах.

Главной особенностью такого сценария является определение времени работы ISR с момента сохранения контекста прерванной задачи до момента выдачи контроллеру прерывания конанды EOI (End Of Interrupt), сопровождающейся вызовом диспетчера, который разрешает прерывания, причём это может происходить раньше, чем восстанавливается сохранённый контекст. Такое определение точнее и универсальнее, потому что в любой операционной системе контекст прерванной задачи сохраняется в том или ином стеке и остаётся там дольше, чем обрабатывается прерывание. Вы по-прежнему можете блюсти компактность кода обработчика, но в случае SST следует помнить, что концептуально итоговый вызов диспетчера выполняется на уровне задачи и не является частью обработчика прерывания.

Определение длительности обработки прерываний не является чисто академическим вопросом и имеет важное практическое применение, а именно: отладка на уровне прерывания гораздо сложнее, чем на уровне задач. На самом деле отлаживать ISR в момент отправки команды EOI в контроллер прерываний и вызова диспетчера SST так же сложно, как и в других операционных системах, особенно когда отладка проводится через программу-монитор, расположенную в программной памяти. Отладка же задачи, написанной для SST, столь же несложна, сколь и отладка основного цикла "main()", даже если она активируется через косвенный вызов из обработчика прерывания. Это происходит, потому что диспетчер SST запускает каждую задачу с разблокированными на уровне приоритетного контроллера и процессора прерываниями всех уровней.

ПредпросмотрAttachmentSize
super_simple_tasker.zip293.38 КБ
sst_code.zip43.69 КБ