Важно также отметить, что возможность «успеть среагировать на событие» вовсе не означает высокую скорость работы. Система может работать относительно медленно, и всё же являться системой реального времени. Главное отличие ОСРВ от ОС общего назначения - это некий фиксированный промежуток времени, в течение которого система гарантированно среагирует на событие и выполнит его обработку. Величина этого промежутка времени определяется решаемой задачей и является одним из требований к разрабатываемой системе. Он может быть очень коротким, но может быть и длинным, важно лишь то, что он фиксирован и известен заранее.
Применение систем реального времени может быть самым разнообразным. Рассмотрим, например, работу сотового телефона. Его процессор должен выполнять одновременно довольно много задач: приём и кодирование речи при разговоре, отправку закодированного звука на ретрансляционную станцию, приём входящего закодированного звукового потока, раскодирование и воспроизведение его; плюс к этому необходимо обмениваться со станцией всякого рода служебной информацией - такой как переход из зоны в зону и переключение на другую станцию, отслеживание уровня сигнала, при необходимости - усиление его и так далее. Причём многие из этих задач должны выполняться в реальном времени, без задержек. Например, задержка в обработке сигнала с микрофона приведёт к тому, что часть фразы будет утеряна; запаздывание с переключением на другую ретрансляционную станцию может привести к потере связи и разрыву соединения. Таким образом, применение операционной системы реального времени в данной ситуации не только оправдано, но и необходимо.
В данной статье мы рассмотрим операционную систему реального времени, разработанную в ИСП РАН для частной «системы на чипе» (System-On-Chip) на базе цифрового сигнального процессора MicroDSP 1.1, когда на одном общем кристалле размещаются сам процессор, модули расширения, программная память и два банка памяти данных. Размещение их на одном кристалле позволяет обеспечить очень быстрый доступ к ячейкам памяти (обращение к памяти занимает один такт). Размер банков памяти данных может меняться от 0 до 65536 16-битных слов; они независимы, и к ним можно обращаться одновременно. Программная память может составлять до 256К слов (4 страницы по 64К слова), размер слова составляет 24 бита (длина инструкций процессора). Стек организуется программно, при помощи трёх специальных регистров, содержащих границы стека и текущее положение указателя стека. Процессор поддерживает до 15 программируемых прерываний с индивидуальной настройкой приоритетов и маскированием, а также доступны три таймера.
Предполагалось, что система будет работать одновременно не более, чем с 64 задачами. Каждая задача имеет свой статический приоритет, причём двух задач с одинаковыми приоритетами быть не может. Планировщик задач выбирает для запуска задачу с наивысшим приоритетом из тех, что находятся в состоянии готовности (то есть, в принципе, допустима ситуация, когда какая-то задача ни разу не получит управления). Процессорное время выделяется задачам квантами, длительность кванта может варьироваться. Увеличение длительности кванта ухудшает параллелизм, но снижает затраты, связанные с переключением процессов; уменьшение длительности, соответственно, - наоборот. Для каждой задачи оптимальное значение длительности кванта будет своим, поэтому возможность настраивать длительность кванта времени весьма полезна. Также ОС должна предоставлять базовые функции по управлению процессами и реализацию основных примитивов синхронизации и межзадачного взаимодействия.
В данной статье мы рассмотрим функциональность разработанной системы и её возможности. В разделе 2 будет описана собственно сама операционная система и предоставляемые ей функции. Раздел 3 описывает доработки в интерфейсе интегрированной среды и отладчика MetaDSP, позволяющие создавать и отлаживать многозадачные приложения.
Всего в системе может присутствовать максимум 63 пользовательских задачи. Сами задачи являются обычными функциями без параметров, чаще всего представляющими собой бесконечный цикл. Каждой задаче соответствует приоритет от 0 до 62, задаваемый при подключении, причём не может существовать двух задач с одинаковыми приоритетами. Системное время квантуется, и в каждый квант времени выполняется задача, имеющая наивысший приоритет (самый высокий приоритет соответствует значению 0) среди тех, которые не находятся в состоянии ожидания. На приведённой ниже схеме (Рис. 1) можно видеть основные состояния, в которых может находиться задача, и возможные переходы между состояниями.
Рис.1. Схема переключения состояний задач
После истечения каждого кванта времени (system tick) вызывается функция обработки прерывания таймера. Эта функция выполняет следующие действия:
В системе всегда присутствует одна внутренняя задача, называющаяся background и имеющая самый низкий возможный приоритет - 63. Это значение ниже приоритета любой из пользовательских задач, и поэтому эта задача выполняется только тогда, когда все пользовательские задачи находятся в состоянии ожидания; таким образом, задача background является индикатором простоя системы. В начальной реализации эта задача представляла собой цикл, состоящий из нескольких инструкций NOP. В дальнейшем туда была добавлена инструкция IDLE, которая останавливает процессор до тех пор, пока не появится запрос на прерывание. Тем самым было снижено энергопотребление процессора на время простоя.
Двоичный семафор может принимать значения 1 или 0, что означает, соответственно, доступность и недоступность ресурса. Если задаче требуется доступ к ресурсу, она должна вызвать системную функцию, которая проверяет, доступен ли ресурс. Если он уже занят, то задача переводится в состояние ожидания, а управление передаётся следующей наиболее приоритетной задаче. Если же ресурс доступен (значение семафора равно 1), то он блокируется (значение устанавливается в 0), и управление возвращается в задачу. Когда работа с ресурсом завершена, его нужно освободить вызовом соответствующей функции. При этом из списка всех задач, ожидающих данный ресурс, выбирается наиболее приоритетная и переводится в состояние готовности (а если она имеет более высокий приоритет, чем текущая задача, то происходит переключение). Если таких задач нет, то ресурс просто помечается как свободный (значение устанавливается в 1), и управление возвращается в выполняющуюся задачу.
Отличие семафоров со счётчиком от нескольких двоичных семафоров состоит только в том, что ресурсы могут использоваться несколькими задачами одновременно. Например, если есть канал передачи данных, состоящий из четырёх параллельных линий, работающих независимо, то для работы с ним может использоваться семафор со счётчиком, изначально равным 4. Когда задаче требуется линия передачи данных, она делает системный запрос. В результате, если количество доступных линий больше нуля, оно уменьшается на 1; в противном случае задача переводится в режим ожидания до тех пор, пока одна из задач, блокирующих ресурсы, не освободит используемую линию. Стоит отметить, что данный подход существенно отличается от использования нескольких двоичных семафоров. Семафор со счётчиком не делает различия между контролируемыми им ресурсами. В вышеприведённом примере задача, ожидающая ресурс, переводится в состояние готовности при освобождении любой из четырёх линий. При использовании же четырёх двоичных семафоров пришлось бы ждать освобождения какой-то одной конкретной линии, даже если все остальные уже свободны.
В конфигурационных файлах RTOS-проекта задаётся общее количество динамической памяти, которое будет использоваться программой. Пользователь должен оценить, сколько потребуется памяти, учитывая расход памяти на служебную информацию. Динамическая память организована в виде набора областей (пулов) памяти, каждая из которых состоит из некоторого количества буферов одинакового размера. Для работы необходимо предварительно создать пул, указав количество буферов в нём и их размер (разумеется, суммарный размер не должен превышать количество свободной динамической памяти), после чего системными вызовами можно выделять буфера и возвращать их обратно в пул, помечая их, тем самым, как свободные. При такой функциональности дефрагментировать память нет необходимости, поскольку работа ведётся только с буферами одинакового размера.
В первую очередь, это возможность запрещать прерывания на некоторое время. Это бывает необходимо в случае, когда программе требуется исключительный доступ к данным, и никакое постороннее вмешательство недопустимо. Например, в большинстве системных функций RTOS в начале кода прерывания запрещаются, а в конце - разрешаются. Это сделано для того, чтобы в процессе изменения внутренних системных данных не могло произойти переключение задачи, что привело бы к ошибкам. Однако этой возможностью не следует злоупотреблять, и крайне желательно, чтобы прерывания не были запрещены в течение длительного времени, поскольку это существенно снижает время реакции системы на внешние события.
Важным моментом является также то, что внутри процедур обработки прерываний невозможен вызов функций ожидания. При попытке вызвать такую функцию будет возвращен код ошибки. Посылка же сигналов и сообщений, а также освобождение семафоров внутри обработчиков прерываний допустимо, хотя и требует некоторых дополнительных действий, а именно: в начале процедуры обработки необходимо сохранить контекст текущей задачи путем вызова соответствующей системной функции, а в конце вместо обычной инструкции возврата из прерывания (RETI) нужно выполнить вызов системной функции возврата, присутствующей в RTOS API. Это всё требуется для того, чтобы обеспечить корректную обработку прерывания. В обычной ситуации вызов функции отправки сообщения может вызвать переключение на более приоритетную задачу. В случае же обработки прерывания такое поведение недопустимо, и поэтому вместо немедленного переключения функция отправки сообщения просто выставляет флаг переключения. После того как обработка прерывания будет завершена, можно выполнять переключение задач, что и делает системная функция возврата из прерывания, если обнаруживает, что флаг выставлен (именно для этого в начале требуется сохранить контекст задачи).
На Рис. 2 показана вкладка Tasks.
Рис. 2. Вид окна RTOS Illuminator, управление задачами
Она позволяет просматривать текущее состояние процессов: имя процесса (имя подключаемой функции, поле Name), приоритет (поле Priority), текущий статус процесса (Status). Если задача приостановлена или находится в состоянии ожидания, для неё отображается значение таймаута (Delay), а для задач, ожидающих наступления некоторого события, дополнительно указывается, какое именно это событие (Event). Для выполняемой в данный момент задачи также указываются количество тактов, прошедшее с момента последнего переключения на эту задачу (Resumed Cycles), и текущий адрес выполнения (Run Address). Для неактивных задач в поле Run Address выводится адрес программной памяти, с которого будет продолжено выполнение задачи. Двойным щелчком мыши по этому полю можно перейти к тому месту исходного кода, которое соответствует указанному адресу, т. е. по сути, к той точке выполнения, в которой задача была прервана.
Помимо этого в этой вкладке можно изменять состояние задач, а именно:
Слева от имени каждой задачи присутствует флажок, включив который, можно установить точку останова, срабатывающую в момент переключения RTOS на эту задачу. Эта функция значительно расширяет возможности отладки многопоточных приложений.
На Рис. 3 показана вкладка Events.
Рис. 3. Вид окна RTOS Illuminator, управление объектами синхронизации
На этой вкладке отображаются все объекты межзадачного взаимодействия и синхронизации, созданные программой. Для каждого объекта выводятся его адрес (в поле Name), тип объекта (сигнал, семафор, почтовый ящик и т. п., поле Type) и список задач, ожидающих данный объект синхронизации (поле Waiting Tasks). Для сигналов и семафоров дополнительно выводится счётчик, представляющий собой текущее состояние объекта (Counter), а для почтовых ящиков и очередей сообщений - указатель на сообщение, если оно присутствует (Message).
Так же, как и во вкладке Tasks, слева от каждого объекта присутствует флажок, который включает/отключает точку останова, выполняющуюся при наступлении отмеченного события.
Вкладка Stack Info предоставляет информацию о текущем состоянии стека для каждой задачи. Для текущей задачи это будет просто значение стековых регистров, а для всех остальных задач выводятся значения, сохранённые в контексте при переключении. На этой вкладке также отображаются размер стека, процентное соотношение его использования и максимальный процент использования, который был за всё время работы данной задачи.
Вкладка RTOS Info отображает сведения о системе RTOS в целом: количество тактов, прошедшее с момента последнего срабатывания таймера, длительность кванта времени, общее число квантов времени, прошедшее с момента старта системы, и версию RTOS.
Рис. 4. Вид окна RTOS Profiler, последовательность задач
Изначально, до разработки MicroDSP-RTOS, в MetaDSP присутствовал встроенный профилировщик, предоставляющий информацию о распределении процессорного времени между различными функциями внутри программы, а также собирающий статистику по количеству выполненных процессорных инструкций разного типа. С появлением MicroDSP-RTOS были добавлены два новых типа профилировки.
Рис. 5. Вид окна RTOS Profiler, распределение времени по задачам
В этой вкладке окна профилировщика отображается, какую часть процессорного времени занимала каждая задача. Опционально можно также отобразить суммарное время выполнения для системной фоновой задачи (background), для процедуры обработки таймерного прерывания (RTOS ISR - Interrupt Service Routine), по которому RTOS выполняет переключение задач, и процедуры начальной инициализации (Bootstrap).
RTOS-профилировщик позволяет оценить различные показатели разрабатываемого приложения, связанные с мультизадачностью, например, насколько правильно выбран размер кванта времени, выяснить, в течение какого времени система находилась в простое и так далее. Определение значений этих характеристик даёт возможность сравнивать эффективность системы при различных значениях её параметров, что в свою очередь облегчает создание эффективного продукта.
Разработанная система имеет следующие характеристики (для времени выполнения указывается максимально возможное время; для перевода в микросекунды рассматривается процессор с частотой 200 МГц):
| размер ядра | 829 слов |
| полный размер системы (включая опциональные модули) | 1957 слов |
| время сохранения/восстановления контекста | 65 тактов (0,33 мкс) |
| длительность ISR (8 задач) | 474 такта (2,37 мкс) |
| длительность ISR (63 задачи) | 2290 тактов (11,5 мкс) |
К настоящему моменту работа над MicroDSP-RTOS завершена, результаты внедрены в производство заказчика; в частности, известно о сотовом телефоне, в котором используется данная система.