![](https://habrastorage.org/webt/fe/zo/8m/fezo8mtb5js9kb_juvdmxayq1fa.jpeg)
Основные принципы работы планировщиков ОСРВ были рассмотрены в статье «Задачи и планирование». В настоящей статье мы рассмотрим возможности, которые предлагает Nucleus RTOS, а также более подробно те, которые предоставляет Nucleus SE.
Предыдущие статьи серии:
Статья #8. Nucleus SE: внутреннее устройство и развертывание
Статья #7. Nucleus SE: введение
Статья #6. Другие сервисы ОСРВ
Статья #5. Взаимодействие между задачами и синхронизация
Статья #4. Задачи, переключение контекста и прерывания
Статья #3. Задачи и планирование
Статья #2. ОСРВ: Структура и режим реального времени
Статья #1. ОСРВ: введение.
Планирование в Nucleus RTOS
Поскольку Nucleus RTOS — это полноценная, хорошо зарекомендовавшая себя коммерческая ОСРВ, можно смело предположить, что планировщик был разработан в соответствии с требованиями такого продукта. Эта сложная и гибкая операционная система, предоставляющая разработчику широкий спектр возможностей для решения практически любых мыслимых задач программирования в реальном времени.
Планировщик может поддерживать неограниченное количество задач (ограниченное лишь доступными ресурсами) и работать с управлением по приоритетам. Задаче может быть назначен приоритет от 0 до 255, где 0 — наивысший приоритет, а 255 — самый низкий. Задача обладает динамическим приоритетом, то есть он может быть изменен во время выполнения либо самой задачей, либо другой. Нескольким задачам может быть присвоен одинаковый уровень приоритета. В крайнем случае всем задачам может быть назначен один и тот же приоритет, что дает возможность реализовать политику планирования по принципу Round Robin и Time-Slice.
Если существует несколько задач с одним и тем же приоритетом, они будут запланированы по алгоритму Round Robin, в том порядке, в котором были подготовлены. Задаче нужно приостановиться или передать управление, чтобы запустилась следующая задача. Задачам также могут быть назначены временные интервалы, которые обеспечивают более контролируемое разделение доступного времени процессора.
Планирование задач на 100% детерминировано, чего и следовало ожидать от подобного ядра. Задачи также могут быть динамически созданы и уничтожены, что, благодаря планировщику, происходит незаметно для пользователя.
Планирование в Nucleus SE
Я разработал все аспекты Nucleus SE, чтобы они были в целом совместимы с Nucleus RTOS, но при этом были более простыми и эффективными, с точки зрения памяти. Планировщик не является исключением. Он предоставляет многие возможности планировщика Nucleus RTOS, но несколько ограничен. Гибкость достигается за счет конфигурации во время сборки.
Приложение Nucleus SE может иметь максимум 16 задач (и, как минимум, одну). Хотя теоретически это число может быть увеличено, но эффективность алгоритмов будет под угрозой; ряд структур данных полагается на сохранение номера индекса задачи (от 0 до 15) в полубайте (четыре бита), и их необходимо будет перерабатывать вместе с соответствующим кодом.
Чтобы добиться баланса между гибкостью и простотой (и размером), вместо того чтобы иметь один планировщик с несколькими возможностями, Nucleus SE предлагает на выбор один из четырех типов планировщика: Run to Сompletion (RTC), Round Robin (RR), Time-Slice (TS) и Priority. Планировщик выбирается статически, в момент сборки. Подробные сведения о каждом типе планировщиков описаны ниже в разделе «Типы планировщиков».
Как и любые другие аспекты Nucleus SE, задачи – это статические объекты. Они определяются во время конфигурации, и их приоритет (индекс) не может быть изменен.
Планировщики Nucleus SE
Как указано выше, Nucleus SE предлагает на выбор один из четырех типов планировщиков. Как и большинство аспектов конфигурации Nucleus SE, этот выбор определяется записью в nuse_config.h, параметр NUSE_SCHEDULER_TYPE должен быть установлен соответствующим образом, как показано в этом фрагменте из конфигурационного файла:
![](https://habrastorage.org/webt/u8/ln/su/u8lnsu8ido7iqwna4a_nw1tblqc.jpeg)
Независимо от того, какой выбран планировщик, его код запуска вызывается сразу после инициализации системы. Полная информация об инициализации Nucleus SE будет представлена в следующей статье.
Планировщик Run to Completion
Планировщик RTC является самым простым и подходящим решением, если соответствует требованиям приложения. Каждая задача должна завершить свою работу перед выполнением функции возврата и предоставлением планировщику возможности выполнить следующую задачу.
Необязательно, чтобы для каждой задачи был отдельный стек. Весь код написан на C, язык ассемблера не требуется. Ниже — код планировщика RTC целиком.
![](https://habrastorage.org/webt/lk/vf/z4/lkvfz4furcpo5bxyuwojbnmzkpq.jpeg)
Код — это просто бесконечный цикл, который по очереди вызывает каждую задачу. Массив NUSE_Task_Start_Address[] содержит указатели на внешнюю функцию каждой задачи. Макрос PF0 — это простое преобразование void указателя в указатель на void функцию без параметров. Он предназначен для обеспечения удобочитаемости кода.
Условная компиляция используется для включения поддержки дополнительных функций: NUSE_SUSPEND_ENABLE определяет, могут ли задачи быть приостановлены; NUSE_SCHEDULE_COUNT_SUPPORT определяет, требуется ли значение счетчика каждый раз, когда запланирована задача. Более подробную информацию об этом можно найти в следующей статье.
Планировщик Round Robin
Если требуется чуть больше гибкости, чем предоставляется планировщиком RTC, подойдет планировщик RR. Он позволяет задаче передать управление или приостановиться, а затем продолжить с той же точки. Дополнительные накладные расходы, помимо сложности кода и непортируемости, заключаются в том, что для каждой задачи требуется собственный стек.
Код планировщика состоит из двух частей. Компонент запуска выглядит следующим образом:
![](https://habrastorage.org/webt/-j/83/7r/-j837r3mppxr_3xjpeeazjsuwgc.jpeg)
Если включена поддержка начального состояния задачи (с помощью параметра NUSE_INITIAL_TASK_STATE_SUPPORT, см. «Параметры» в следующей статье), планирование начинается с первой готовой задачи; в противном случае используется задача с индексом 0. Контекст этой задачи затем загружается с помощью NUSE_Context_Load(). Дополнительные сведения о сохранении и восстановлении контекста см. в разделе «Сохранение контекста» в следующей статье.
Вторая часть планировщика — это компонент «перепланирования»:
![](https://habrastorage.org/webt/zb/rz/-k/zbrz-ku365uguee3z989gq4-pd8.jpeg)
Этот код вызывается, когда задача освобождает центральный процессор или приостанавливается.
Код выбирает для запуска задачу со следующим индексом и помещает значение в NUSE_Task_Next, принимая во внимание, включена ли приостановка задач или нет. Макрос NUSE_CONTEXT_SWAP() затем используется для вызова переключения контекста с использованием программного прерывания. Дополнительные сведения о сохранении и восстановлении контекста см. в разделе «Сохранение контекста» в следующей статье.
Планировщик Priority
Планировщик Priority в Nucleus SE, как и другие варианты, предназначен для обеспечения требуемой функциональности, будучи достаточно простым. В результате, каждая задача имеет уникальный приоритет, невозможно иметь несколько задач с одним уровнем приоритета. Приоритет определяется индексом задачи, где 0 — это уровень наивысшего приоритета. Индекс задачи определяется ее местом в массиве NUSE_Task_Start_Address[]. В следующей статье будет дана более подробная информация о настройке задач.
Как и планировщики RR и TS, планировщик Priority имеет два компонента. Компонент запуска планировщика Priority такой же, как у планировщиков RR и TS, что было проиллюстрировано выше. Компонент перепланирования несколько отличается:
![](https://habrastorage.org/webt/eu/6v/n6/eu6vn6zlqoex1bacs9r1iwq4c4u.jpeg)
Нет условного кода, который мог бы отключить приостановку задач, так как эта возможность является обязательной для планировщика приоритетов; любая альтернатива была бы нелогичной. Функция NUSE_Reschedule() принимает параметр, который «подсказывает», какая задача может быть запланирована следующей — new_task. Это значение устанавливается, когда вызывается перепланирование, потому что активизируется другая задача. Индекс этой задачи передается как параметр. Затем планировщик может определить, следует ли выполнять переключение контекста, сравнивая значение new_task с индексом текущей задачи (NUSE_Task_Active). Если перепланирование является результатом приостановки задачи, параметр будет установлен в NUSE_NO_TASK, и планировщик будет искать задачу с самым высоким приоритетом.
Состояния задач
Как правило все операционные системы имеют концепцию нахождения задач в определенном «состоянии». Детали разнятся в зависимости от ОСРВ. В настоящей статье мы рассмотрим, как Nucleus RTOS и Nucleus SE используют состояния задач.
Состояния задач в Nucleus RTOS
Nucleus RTOS поддерживает 5 состояний задач.
- Выполнение: задача, управляющая процессором в настоящее время. Очевидно, что только одна задача может занимать это состояние.
- Готовность: задача, готовая к исполнению (или продолжению исполнения) до принятия планировщиком решения запустить её. Как правило, задача имеет более низкий приоритет, чем та, которая выполняется.
- Приостановка: «спящая» задача. Она не учитывается при планировании до тех пор, пока не проснется, и в этот момент она будет «готова» и позже может продолжить выполнение. Обычно задача находится в состоянии «сна», потому что она ждет чего-то: когда станет доступен ресурс, когда истечет установленный период времени или когда другая задача разбудит её.
- Отмена: задача была «убита». Она не учитывается при планировании до тех пор, пока не будет сброшена, после чего задача будет «готова» или «приостановлена».
- Окончание: задача завершена и вышла из своей внешней функции, просто покинув внешний блок или выполнив инструкцию «return». Она не учитывается при планировании до тех пор, пока не будет сброшена, после чего задача будет «готова» или «приостановлена».
Состояния задач в Nucleus SE
Модель состояния задач в Nucleus SE немного проще. Обычно есть всего 3 состояния: Выполнение, Готовность и Приостановка. Состояние каждой задачи хранится в NUSE_Task_Status[], который имеет значения, типа NUSE_READY, хотя никогда не имеет значения, отражающего состояние Выполнение. Если приостановка задач не включена (см. «Параметры» в следующей статье), возможно только два состояния задач, а этот массив отсутствует.
Есть несколько возможных типов приостановки задачи. Если задача приостанавливается явно сама по себе или другой задачей, это называется «pure suspend» («чистая» приостановка) и представляется статусом NUSE_PURE_SUSPEND. Если включено состояние «сна» и задача приостановилась на определенный период времени, она имеет статус
NUSE_SLEEP_SUSPEND. Если включена функция блокирующих вызовов API (через NUSE_BLOCKING_ENABLE, см. «Параметры» в следующей статье), задача может быть приостановлена, пока ресурс не станет доступным. У каждого типа объекта есть свой статус приостановки задачи, например, в виде NUSE_MAILBOX_SUSPEND. В Nucleus SE задача может быть заблокирована в разделе памяти, группе событий, почтовом ящике, очереди, канале или семафоре.
Состояние потоков
При обсуждении поведения задач слова «Состояние» и «Статус» обычно используются довольно свободно. Существует дополнительный фактор, который условно можно назвать «состоянием потока». Это глобальная переменная NUSE_Thread_State, которая содержит указание на характер выполняемого кода. Это относится к поведению многих вызовов API. Возможные значения:
- NUSE_TASK_CONTEXT – вызов API был выполнен из задачи.
- NUSE_STARTUP_CONTEXT – вызов API был выполнен из кода запуска; планировщик еще не был запущен.
- NUSE_NISR_CONTEXT и NUSE_MISR_CONTEXT – вызов API был выполнен из обработчика прерываний. Прерывания в Nucleus SE рассмотрим в следующей статье.
В следующей статье будут подробно описаны дополнительные функции планировщика в Nucleus SE, а также сохранение контекста.
Об авторе: Колин Уоллс уже более тридцати лет работает в сфере электронной промышленности, значительную часть времени уделяя встроенному ПО. Сейчас он — инженер в области встроенного ПО в Mentor Embedded (подразделение Mentor Graphics). Колин Уоллс часто выступает на конференциях и семинарах, автор многочисленных технических статей и двух книг по встроенному ПО. Живет в Великобритании. Профессиональный блог Колина, e-mail: colin_walls@mentor.com