![](https://habrastorage.org/webt/0c/3i/wg/0c3iwgjkuq1yriusgd5f_doplxs.jpeg)
Понятие задачи было введено в предыдущих статьях. По сути, задача – просто набор значений, которые могут быть загружены в регистры процессора (для выполняемой задачи) или могут храниться в состоянии, готовом к контекстному переключению на задачу в будущем. Чаще всего задача имеет собственный стек.
Само собой, при использовании планировщика Run to Completion (RTC) контекстное переключение не используется, и задача может считаться просто значением программного счетчика (точкой входа в код).
Определение задачи не включает в себя сам код. Задача должна выполнять код, но он ей не принадлежит. Задачи могут обладать общими функциями. Более того, весь код нескольких задач может быть общим. Общий код практически всегда должен быть написан согласно требованиям реентерабельности. Большинство компиляторов без труда справляются с таким кодом, однако необходима осторожность с библиотечными функциями, так как они могут быть не предназначены для многозадачных приложений.
Такое определение диктует определенные правила, которых следует придерживаться при разработке структур данных задач и функций API, описанных в этой статье. Я рассмотрю конфигурацию задач в Nucleus SE и начну подробный обзор служебных вызовов (вызовов API), которые относятся к задачам как в Nucleus SE, так и в Nucleus RTOS.
Предыдущие статьи серии:
Статья #10. Планировщик: дополнительные возможности и сохранение контекста
Статья #9. Планировщик: реализация
Статья #8. Nucleus SE: внутреннее устройство и развертывание
Статья #7. Nucleus SE: введение
Статья #6. Другие сервисы ОСРВ
Статья #5. Взаимодействие между задачами и синхронизация
Статья #4. Задачи, переключение контекста и прерывания
Статья #3. Задачи и планирование
Статья #2. ОСРВ: Структура и режим реального времени
Статья #1. ОСРВ: введение.
Конфигурирование задач
Количество задач
В 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
Каждая функция API (служебный вызов) в Nucleus SE активируется директивой #define в nuse_config.h. Для задач такими параметрами являются:
- NUSE_TASK_SUSPEND
- NUSE_TASK_RESUME
- NUSE_TASK_SLEEP
- NUSE_TASK_RELINQUISH
- NUSE_TASK_CURRENT
- NUSE_TASK_CHECK_STACK
- NUSE_TASK_RESET
- NUSE_TASK_INFORMATION
- NUSE_TASK_COUNT
По умолчанию, все вышеперечисленные параметры имеют значение FALSE, дезактивируя таким образом каждый служебный вызов и предотвращая включение любого реализующего их кода. Для конфигурирования задач под приложение нужно выбрать необходимые вызовы API и присвоить соответствующим символам значения TRUE.
Ниже приведен фрагмент файла nuse_config.h по умолчанию.
![](https://habrastorage.org/webt/6d/oe/s_/6does_k3iu5pvaa2r49h9lxw1tq.png)
Если ваш код использует вызов 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 имеет служебный вызов с аналогичным функционалом.
Вызов приостановки задачи в Nucleus RTOS
Прототип служебного вызова:
STATUS NU_Suspend_Task(NU_TASK *task);
Параметры:
task – указатель на блок управления приостанавливаемой задачи (которая может быть текущей, а ее ID можно получить при помощи NU_Current_Task_Pointer(), подробнее в следующей статье).
Возвращаемое значение:
NU_SUCCESS – вызов был успешно завершен;
NU_INVALID_TASK – некорректный указатель на задачу;
NU_INVALID_SUSPEND – указанная задача имеет статус NU_FINISHED или NU_TERMINATED.
Вызов приостановки задачи в Nucleus SE
Этот API-вызов поддерживает основной функционал Nucleus PLUS API.
Прототип служебного вызова:
STATUS NUSE_Task_Suspend(NUSE_TASK task);
Параметры:
task – индекс (ID) приостанавливаемой задачи (которая может быть текущей, а ее ID можно получить при помощи NUSE_Task_Current() – подробнее в следующей статье).
Возвращаемое значение:
NUSE_SUCCESS – вызов был успешно завершен;
NUSE_INVALID_TASK – некорректный индекс задачи.
Реализация приостановки задачи в Nucleus SE
Основной функционал API функции довольно простой:
![](https://habrastorage.org/webt/4v/ju/z5/4vjuz5oyjviowrrgcd2gzok4ypg.png)
По сути, в этой реализации вызывается функция планировщика NUSE_Suspend_Task() с параметром «безусловная остановка» (NUSE_PURE_SUSPEND). Эта функция вызывает планировщик, если приостанавливаемая задача является текущей.
Возобновление задачи
Nucleus RTOS предоставляет простой API-вызов, который позволяет возобновить задачу, приостановленную ранее на неопределенный период времени. Nucleus SE имеет служебный вызов с аналогичным функционалом.
Вызов возобновления задачи в Nucleus RTOS
Прототип служебного вызова:
STATUS NU_Resume_Task(NU_TASK *task);
Параметры:
task — указатель на блок управления возобновляемой задачи.
Возвращаемое значение:
NUSE_SUCCESS – вызов был успешно завершен;
NUSE_INVALID_TASK – некорректный указатель на задачу;
NUSE_INVALID_RESUME – задача не была безусловно приостановлена.
Вызов возобновления задачи в Nucleus SE
Этот API-вызов поддерживает основной функционал Nucleus RTOS API.
Прототип служебного вызова:
STATUS NUSE_Task_Resume(NUSE_TASK task);
Параметры:
task – индекс (ID) возобновляемой задачи.
Возвращаемое значение:
NUSE_SUCCESS – вызов был успешно завершен;
NUSE_INVALID_TASK – некорректный индекс задачи;
NUSE_INVALID_RESUME – задача не была безусловно приостановлена.
Реализация возобновления задачи в Nucleus SE
Основной функционал API функции довольно прост:
![](https://habrastorage.org/webt/8f/ih/al/8fihalzojhb_mn8d26pzyxcvfna.png)
Фактически, в этой реализации вызывается функция планировщика NUSE_Wake_Task(). Эта функция вызывает планировщик, если используется планировщик Priority, а возобновляемая задача имеет более высокий приоритет, чем текущая задача.
Приостановка задачи на определенный период времени
Nucleus RTOS предоставляет простой API-вызов для приостановки текущей задачи на определенный период времени. Nucleus SE имеет служебный вызов с аналогичным функционалом.
Вызов приостановки задачи на определенный период времени Nucleus RTOS
Прототип служебного вызова:
VOID NU_Sleep(UNSIGNED ticks);
Параметры:
ticks – период времени, на который должна быть приостановлена задача (в тактах часов реального времени).
Возвращаемое значение:
Нет.
Вызов приостановки задачи на определенный период времени Nucleus SE
Этот API- вызов поддерживает основной функционал Nucleus RTOS API.
Прототип служебного вызова:
void NUSE_Task_Sleep(U16 ticks);
Параметры:
ticks – период времени, на который должна быть приостановлена задача (в тактах часов реального времени).
Возвращаемое значение:
Нет.
Реализация приостановки задачи на определенный период времени в Nucleus SE
Основной функционал API функции довольно прост:
![](https://habrastorage.org/webt/xz/_y/fe/xz_yfebb4wdhr7wzxhepfu6j1yy.png)
Этот код загружает значение задержки в параметр текущей задачи в NUSE_Task_Timeout_Counter[]. После этого задача приостанавливается при помощи NUSE_Suspend_Task() с указанием периода времени приостановки (NUSE_SLEEP_SUSPEND).
Значение таймаута используется обработчиком прерывания часов реального времени (real-time clock). Код показан ниже и будет более подробно рассмотрен в одной из будущих статей.
![](https://habrastorage.org/webt/_r/cu/mw/_rcumw5d8xxuzvdti8qu4qamc-o.png)
Освобождение процессора
Nucleus PLUS предоставляет простой API-вызов для предоставления возможности передачи управления процессором любой из готовых к исполнению задач с одинаковым приоритетом на основе алгоритма Round Robin. Nucleus SE имеет служебный вызов с очень похожим функционалом. Однако его нельзя использовать с планировщиком Priority, так как несколько задач с одним приоритетом не поддерживаются. Попытка использования этого API-вызова с планировщиком Priority приведет к ошибке. Служебный вызов работает с планировщиками Round Robin и Time Slice, с планировщиком Run To Completion этот API-вызов неэффективен.
Вызов освобождения процессора Nucleus RTOS
Этот API- вызов поддерживает основной функционал Nucleus PLUS API.
Прототип служебного вызова:
VOID NU_Relinquish(VOID);
Параметры:
Отсутствуют.
Возвращаемое значение:
Нет.
Вызов освобождения процессора Nucleus SE
Этот вызов API поддерживает ключевой функционал Nucleus PLUS API.
Прототип служебного вызова:
void NUSE_Task_Relinquish(void);
Параметры:
Отсутствуют.
Возвращаемое значение:
Нет.
Реализация освобождения процессора Nucleus SE
Основной функционал API функции:
![](https://habrastorage.org/webt/fd/a_/nk/fda_nkanqupe9wqqkdazlsauuoq.png)
По сути, эта реализация вызывает функцию планировщика NUSE_Reschedule(). Эта функция просто сообщает планировщику о необходимости выполнить следующую задачу.
Следующие две статьи продолжат обзор служебных вызовов RTOS, связанных с задачами, на примерах Nucleus RTOS и Nucleus SE.
Об авторе: Колин Уоллс уже более тридцати лет работает в сфере электронной промышленности, значительную часть времени уделяя встроенному ПО. Сейчас он — инженер в области встроенного ПО в Mentor Embedded (подразделение Mentor Graphics). Колин Уоллс часто выступает на конференциях и семинарах, автор многочисленных технических статей и двух книг по встроенному ПО. Живет в Великобритании. Профессиональный блог Колина, e-mail: colin_walls@mentor.com.
О переводе: этот цикл статей показался интересным тем, что, несмотря на местами устаревшие описываемые подходы, автор очень понятным языком знакомит малоподготовленного читателя с особенностями ОС реального времени. Я сам принадлежу к коллективу создателей российской ОСРВ, которую мы предполагаем сделать бесплатной, и надеюсь, что цикл будет полезен начинающим разработчикам.