В третьей и последней статье, посвященной задачам, автор рассматривает структуры данных задач Nucleus SE.
Задачи используют различные структуры данных (и в ОЗУ, и в ПЗУ), которые, как и другие объекты Nucleus SE, представляет из себя набор таблиц, размер которых соответствует количеству выбранных задач и параметров.
Настоятельно рекомендую, чтобы код приложения обращался к этим структурам данных при помощи функций API, а не напрямую. Это позволяет избежать нежелательных побочных эффектов, несовместимости с будущими версиями Nucleus SE, а также упрощает портирование приложения на Nucleus RTOS. Для лучшего понимания работы кода служебных вызовов и процесса отладки ниже приведено подробное описание структур данных.
К таким структурам данных относятся:
NUSE_Task_Context[][] – двумерный массив типа ADDR, имеет по одной строке на каждую задачу. Количество столбцов зависит от архитектуры контроллера и определяется символом NUSE_REGISTERS, который определен в nuse_types.h. Этот массив используется планировщиком для сохранения контекста каждой задачи и был подробное описан в разделе «Сохранение контекста» статьи #10. Не создается, если используется планировщик RTC.
NUSE_Task_Signal_Flags[] – массив типа U8, создается, если включены сигналы, и содержит по 8 сигнальных флагов для каждой задачи. О сигналах речь пойдет в одной из следующих статей.
NUSE_Task_Timeout_Counter[] – массив типа U16, состоит из вычитающих счетчиков для каждой задачи и создается, если активирован вызов API NUSE_Task_Sleep().
NUSE_Task_Status[] – массив типа U8, содержит статусы каждой задачи – NUSE_READY или статусы приостановки. Создается, только если активирована приостановка задач.
NUSE_Task_Blocking_Return[] – массив типа U8, создается если активирована блокировка вызовов API. Он содержит код возврата, который будет использован после блокировки вызовов API. Обычно он содержит NUSE_SUCCESS или код, сообщающий о том, что объект был сброшен (например, NUSE_MAILBOX_WAS_RESET).
NUSE_Task_Schedule_Count[] – массив типа U16, содержит счетчик каждой задачи и создается, только если подсчет планировщика был активирован.
NUSE_Task_Context[][] инициализируется в основном нулями, кроме записей, соответствующих регистру статуса (status register, SR), счетчику программ (program counter, PC) и указателю стека (stack pointer, SP), которым присваиваются начальные значения (см. «Данные в ПЗУ» ниже), а всем остальным структурам данных NUSE_Init_Task() присваивает нули при запуске Nucleus SE. Одна из следующих статей будет содержать полный список стартовых процедур Nucleus SE с их описанием.
Ниже представлены определения структур данных, которые содержатся в файле nuse_init.c.
Пользователь должен определить каждой задаче стек (если не используется планировщик RTC). Это должны быть массивы типа ADDR, которые обычно определены в nuse_config.c. Адреса и размеры стеков должны быть помещены в записи задач NUSE_Task_Stack_Base[] и NUSE_Task_Stack_Size[] соответственно (см. Данные в ПЗУ).
В ПЗУ хранится от одной до четырех структур данных, относящихся к задачам. Точное количество зависит от выбранных параметров:
NUSE_Task_Start_Address[] – массив типа ADDR, имеющий одну запись для каждой задачи, которая является указателем на точку входа в код для задачи. NUSE_Task_Stack_Base[] – массив типа ADDR, имеющий одну запись для каждой задачи, которая является указателем на базовый адрес стека для задачи. Этот массив создается, если используется любой планировщик, кроме RTC.
NUSE_Task_Stack_Size[] – массив типа U16, имеющий одну запись для каждой задачи, которая показывает размера стека для задачи (в словах). Этот массив создается, если используется любой планировщик, кроме RTC.
NUSE_Task_Initial_State[] – массив типа U8, имеющий одну запись для каждой задачи, которая показывает начальное состояние задачи. Может иметь значения NUSE_READY или NUSE_PURE_SUSPEND. Этот массив создается, если выбрана поддержка начального состояния задачи.
Эти структуры данных объявлены и инициализированы (статично) в nuse_config.c:
Для хранения данных в ОЗУ объём памяти (в байтах) обусловливается выбранными параметрами, и он может иметь нулевое значение, если ни один из параметров не выбран.
Этот вызов API создает задачу приложения. В Nucleus SE в этой функции нет необходимости, так как задачи создаются статически.
Прототип вызова:
STATUS NU_Create_Task (NU_TASK *task, CHAR *name, VOID (*task_entry)(UNSIGNED, VOID *), UNSIGNED argc, VOID *argv, VOID *stack_address, UNSIGNED stack_size, OPTION priority, UNSIGNED time_slice, OPTION preempt, OPTION auto_start);
Параметры:
task – указатель на пользовательский блок управления задачами, может использоваться как дескриптер/ссылка («handle») задачи в других вызовах API;
name – указатели на имя задачи, 7-символьную строку с завершающим нулем;
task_entry – указывает входную функцию для задачи;
argc – UNSIGNED элемент данных, который может использоваться для передачи начальной информации задаче;
argv – указатель, который может быть использован для передачи информации задаче;
stack_address – задает начальный сектор памяти для стека задачи;
stack_size – указывает количество байт в стеке;
priority – указывает значение приоритета задачи: от 0 до 255, где меньшие числа соответствуют наивысшему приоритету;
time_slice – указывает максимальное количество квантов времени, которое может пройти при выполнении этой задачи. Значение «0» отключает квантование времени для этой задачи;
preempt – указывает, является задача вытесняемой или нет. Может иметь значения NU_PREEMPT и NU_NO_PREEMPT;
auto_start – показывает начальное состояние задачи. NU_START означает, что задача готова к исполнению, а NU_NO_START – что задача приостановлена.
Этот вызов API удаляет ранее созданную задачу приложения, которая должна иметь статус Finished (завершение) или Terminated (полная приостановка). В этом вызове также нет необходимости в Nucleus SE, так как задачи создаются статически и не могут быть удалены.
Этот вызов API составляет последовательный список указателей на все задачи в системе. Он не нужен в Nucleus SE, так как задачи идентифицируются при помощи простого индекса, а не указателя.
pointer_list – указатель на массив указателей NU_TASK. Этот массив будет заполнен указателями на установленные в системе задачи;
maximum_pointers – максимальное количество указателей, которое можно поместить в массив.
Этот вызов API меняет порядок вытеснения выполняемой задачи. В Nucleus SE он не нужен, так как используется более простой алгоритм планирования.
Этот вызов API меняет квант времени определенной задачи. В Nucleus SE в нем нет необходимости, так как кванты времени задач фиксированы.
task – указатель на блок управления задачей;
time_slice – максимальное количество квантов времени, которое может пройти при выполнении этой задачи, нулевое значение этого поля отключает квантование времени для этой задачи.
При разработке Nucleus SE одной из основных задач было обеспечение высокого уровня совместимости кода с Nucleus RTOS. Задачи не являются исключением, и, с точки зрения пользователя, они реализованы практически так же, как и в Nucleus RTOS. Существуют некоторые несовместимые сферы, где я пришел к выводу, что такая несовместимость будет приемлемой, учитывая, что конечный код проще понять и он может более эффективно расходовать память. Однако, кроме этих несовместимостей, остальные API-вызовы Nucleus RTOS могут быть практически напрямую использованы как вызовы Nucleus SE. Одна из следующих статей будет содержать более подробную информацию по переходу от Nucleus RTOS к Nucleus SE
В Nucleus RTOS все объекты описаны структурой данных (блоками управления), которые имеют определенный тип. Указатель на этот блок управления служит идентификатором для задачи. В Nucleus SE я решил, что требуется другой подход для эффективного использования памяти. Все объекты ядра описаны набором таблиц в ОЗУ и/или ПЗУ. Размер этих таблиц определен количеством типов объектов. Идентификатор определенного объекта – индекс в этих таблицах. Поэтому я определил NUSE_TASK как эквивалент U8. Переменная этого типа (не указатель) служит в качестве идентификатора задач. Это небольшая несовместимость, с которой легко разобраться, если код перенесен с или на Nucleus RTOS. Идентификаторы объектов обычно хранятся и передаются без изменений.
Nucleus RTOS также поддерживает присваивание имен задачам. Эти имена используются только при отладке. Я исключил их из Nucleus SE для экономии памяти.
В Nucleus RTOS задачи могут находится в одном из нескольких состояний: Executing, Ready, Suspended (что приводит к неопределенности: задача находится в режиме ожидания или блокировки API-вызовом), Terminated или Finished.
Nucleus SE также поддерживает состояния Executing и Ready. Все три варианта Suspended поддерживаются опционально. Terminated и Finished не поддерживаются. Нет вызовов API для завершения задач. Внешняя функция задачи никогда не должна возвращать значение ни явным образом, ни неявным (это приведет к состоянию Finished в Nucleus RTOS).
Nucleus RTOS поддерживает 16 служебных вызовов для работы с задачами. Из них 7 не реализованы в Nucleus SE. Их описание, а также причина их исключения описаны выше.
В следующей статье начнем рассматривать управление памятью RTOS.
Об авторе: Колин Уоллс уже более тридцати лет работает в сфере электронной промышленности, значительную часть времени уделяя встроенному ПО. Сейчас он – инженер в области встроенного ПО в Mentor Embedded ( подразделение Mentor Graphics). Колин Уоллс часто выступает на конференциях и семинарах, автор многочисленных технических статей и двух книг по встроенному ПО. Живет в Великобритании. Профессиональный блог Колина: https://blogs.mentor.com/colinwalls/, e-mail: colin_walls@mentor.com