Понятие задачи было введено в предыдущих статьях. В этой статье автор рассмотрит конфигурацию задач в Nucleus SE и начнет подробный обзор служебных вызовов, которые относятся к задачам в Nucleus SE и Nucleus RTOS.
Понятие задачи было введено в предыдущих статьях. По сути, задача – просто набор значений, которые могут быть загружены в регистры процессора (для выполняемой задачи) или могут храниться в состоянии, готовом к контекстному переключению на задачу в будущем. Чаще всего задача имеет собственный стек.
Само собой, при использовании планировщика Run to Completion (RTC) контекстное переключение не используется, и задача может считаться просто значением программного счетчика (точкой входа в код).
Определение задачи не включает в себя сам код. Задача должна выполнять код, но он ей не принадлежит. Задачи могут обладать общими функциями. Более того, весь код нескольких задач может быть общим. Общий код практически всегда должен быть написан согласно требованиям реентерабельности. Большинство компиляторов без труда справляются с таким кодом, однако необходима осторожность с библиотечными функциями, так как они могут быть не предназначены для многозадачных приложений.
Такое определение диктует определенные правила, которых следует придерживаться при разработке структур данных задач и функций API, описанных в этой статье. Я рассмотрю конфигурацию задач в Nucleus SE и начну подробный обзор служебных вызовов (вызовов API), которые относятся к задачам как в Nucleus SE, так и в Nucleus RTOS.
В Nucleus SE конфигурация задач в основном управляется директивами #define в nuse_config.h. Ключевой параметр NUSE_TASK_NUMBER определяет количество задач, которые можно сконфигурировать в приложении. Значение по умолчанию – 1 (т.е. одна задача в процессе исполнения), а максимальное значение параметра – 16. Некорректное значение приведет к ошибке компиляции, которая будет сгенерирована проверкой в nuse_config_chech.h (она включается в nuse_config.c, а значит, компилируется вместе с этим модулем), сработает директива #error. Этот параметр используется при определении структур данных, от его значения зависит их размер.
В Nucleus SE, кроме того случая, когда используется планировщик RTC, необходимо, чтобы хотя бы одна задача всегда была готовой к выполнению. При использовании планировщика Priority нужно убедиться, что задача с наименьшим приоритетом никогда не будет находиться в приостановленном состоянии, такую задачу следует считать «фоновой задачей».
В отличии от некоторых других ядер реального времени Nucleus SE не использует «системные задачи», а значит, все 16 задач доступны коду пользовательского приложения или Middleware.
Каждая функция API (служебный вызов) в Nucleus SE активируется директивой #define в nuse_config.h. Для задач такими параметрами являются:
По умолчанию, все вышеперечисленные параметры имеют значение FALSE, дезактивируя таким образом каждый служебный вызов и предотвращая включение любого реализующего их кода. Для конфигурирования задач под приложение нужно выбрать необходимые вызовы API и присвоить соответствующим символам значения TRUE.
Ниже приведен фрагмент файла nuse_config.h по умолчанию.
Если ваш код использует вызов API, который не был активизирован, при компоновке появится ошибка, так как код реализации не был включен в приложение.
В Nucleus SE можно добавить некоторый функционал задач. И снова необходимые параметры находятся в файле nuse_config.h:
NUSE_SUSPEND_ENABLE позволяет приостанавливать задачи. Если этот параметр не выбран, все задачи постоянно ожидают планирования. Активация этого параметра обязательна при использовании планировщика Priority.
NUSE_BLOCKING_ENABLE позволяет приостанавливать задачи нескольким вызовам API функций. Если этот параметр активирован, NUSE_SUSPEND_ENABLE также должен быть активирован.
NUSE_INITIAL_TASK_STATE_SUPPORT позволяет задать начальное состояние задачи. Если этот параметр не выбран, все задачи будут добавлены в планировщик сразу после создания.
Служебные вызовы задач
Nucleus RTOS поддерживает 16 служебных вызовов (API) для работы с задачами, которые обеспечивают следующий функционал:
Описание функционала | Nucleus RTOS | Nucleus SE |
Приостановка задачи | NU_Suspend_Task() | NUSE_Task_Suspend() |
Возобновление задачи | NU_Resume_Task() | NUSE_Task_Resume() |
Приостановка задачи на определенный период | NU_Sleep() | NUSE_Task_Sleep() |
Освобождение управления процессором | NU_Relinquish() | NUSE_Task_Relinquish() |
Получение ID текущей задачи | NU_Current_Task_Pointer() | NUSE_Task_Current() |
Проверка доступного объема стека | NU_Check_Stack() | NUSE_Task_Check_Stack() |
Возвращение задачи в неиспользуемое состояние (сброс) | NU_Reset_Task() | NUSE_Task_Reset() |
Предоставление информации о конкретной задаче | NU_Task_Information() | NUSE_Task_Information() |
Получение счетчика сконфигурированных задач (на данный момент) в приложении | NU_Established_Tasks() | NUSE_Task_Count() |
Добавление новой задачи в приложение (создание) | NU_Create_Task() | Не реализовано. |
Удаление задачи из приложения | NU_Delete_Task() | Не реализовано. |
Возврат указателей на все задачи в приложении | NU_Task_Pointers() | Не реализовано. |
Изменение алгоритма вытеснения | NU_Change_Preemption() | Не реализовано. |
Изменение приоритета задачи | NU_Change_Priority() | Не реализовано. |
Изменение временного кванта задачи | NU_Change_Time_Slice() | Не реализовано. |
Завершение задачи | NU_Terminate_Task() | Не реализовано. |
Реализация каждого из вышеперечисленных служебных вызовов подробно рассмотрена ниже, а также в следующих статьях об ОСРВ.
Основные операции с задачами: приостановка задачи на неопределенное время, возобновление, приостановка задачи на определенное время, освобождение процессора. Nucleus RTOS и Nucleus SE предоставляют четыре основных вызова API для выполнения этих операций, которые я опишу ниже.
Nucleus PLUS предоставляет простой вызов API, который позволяет приостановить конкретную задачу на неопределенное время. Nucleus SE имеет служебный вызов с аналогичным функционалом.
task – указатель на блок управления приостанавливаемой задачи (которая может быть текущей, а ее ID можно получить при помощи NU_Current_Task_Pointer(), подробнее в следующей статье).
task – индекс (ID) приостанавливаемой задачи (которая может быть текущей, а ее ID можно получить при помощи NUSE_Task_Current() – подробнее в следующей статье).
По сути, в этой реализации вызывается функция планировщика NUSE_Suspend_Task() с параметром «безусловная остановка» (NUSE_PURE_SUSPEND). Эта функция вызывает планировщик, если приостанавливаемая задача является текущей.
Nucleus RTOS предоставляет простой API-вызов, который позволяет возобновить задачу, приостановленную ранее на неопределенный период времени. Nucleus SE имеет служебный вызов с аналогичным функционалом.
Фактически, в этой реализации вызывается функция планировщика NUSE_Wake_Task(). Эта функция вызывает планировщик, если используется планировщик Priority, а возобновляемая задача имеет более высокий приоритет, чем текущая задача.
Nucleus RTOS предоставляет простой API-вызов для приостановки текущей задачи на определенный период времени. Nucleus SE имеет служебный вызов с аналогичным функционалом.
Этот код загружает значение задержки в параметр текущей задачи в NUSE_Task_Timeout_Counter[]. После этого задача приостанавливается при помощи NUSE_Suspend_Task() с указанием периода времени приостановки (NUSE_SLEEP_SUSPEND).
Значение таймаута используется обработчиком прерывания часов реального времени (real-time clock). Код показан ниже и будет более подробно рассмотрен в одной из будущих статей.
Nucleus PLUS предоставляет простой API-вызов для предоставления возможности передачи управления процессором любой из готовых к исполнению задач с одинаковым приоритетом на основе алгоритма Round Robin. Nucleus SE имеет служебный вызов с очень похожим функционалом. Однако его нельзя использовать с планировщиком Priority, так как несколько задач с одним приоритетом не поддерживаются. Попытка использования этого API-вызова с планировщиком Priority приведет к ошибке. Служебный вызов работает с планировщиками Round Robin и Time Slice, с планировщиком Run To Completion этот API-вызов неэффективен.
По сути, эта реализация вызывает функцию планировщика NUSE_Reschedule(). Эта функция просто сообщает планировщику о необходимости выполнить следующую задачу.
Следующие две статьи продолжат обзор служебных вызовов RTOS, связанных с задачами, на примерах Nucleus RTOS и Nucleus SE.
Об авторе: Колин Уоллс уже более тридцати лет работает в сфере электронной промышленности, значительную часть времени уделяя встроенному ПО. Сейчас он – инженер в области встроенного ПО в Mentor Embedded ( подразделение Mentor Graphics). Колин Уоллс часто выступает на конференциях и семинарах, автор многочисленных технических статей и двух книг по встроенному ПО. Живет в Великобритании. Профессиональный блог Колина: https://blogs.mentor.com/colinwalls/, e-mail: colin_walls@mentor.com