+7 (812) 670-9095
Обратная связьEnglish
Главная → Статьи → Системное ПО → Статья #13. Структуры данных задач и неподдерживаемые вызовы API
Полезный совет
Анализируете однотипные данные отчетов по нескольким филиалам - не тратьте время на переход от одного листа к другому, выбирая нужные ячейки - воспользуйтесь трёхмерными формулами Excel.Подробнее
Версия для печати

Статья #13. Структуры данных задач и неподдерживаемые вызовы API

8 октября 2018

В третьей и последней статье, посвященной задачам, автор рассматривает структуры данных задач Nucleus SE.


Статья #13. Структуры данных задач и неподдерживаемые вызовы API



Структуры данных

Задачи используют различные структуры данных (и в ОЗУ, и в ПЗУ), которые, как и другие объекты 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.


Статья #13. Структуры данных задач и неподдерживаемые вызовы API



Пользовательские данные в ОЗУ


Пользователь должен определить каждой задаче стек (если не используется планировщик 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:


Статья #13. Структуры данных задач и неподдерживаемые вызовы API


Объём памяти для хранения данных задач (Task Data Footprint)

Как и все объекты ядра Nucleus SE, объём памяти, необходимой для хранения данных, предсказуем.

Объём ПЗУ (в байтах), требуемый для всех задач приложения:
NUSE_TASK_NUMBER * sizeof (ADDR)

Плюс, если выбран любой планировщик, отличный от RTC:
NUSE_TASK_NUMBER * (sizeof(ADDR)+2)

Плюс, если выбрана поддержка начального состояния задачи:
NUSE_TASK_NUMBER

Для хранения данных в ОЗУ объём памяти (в байтах) обусловливается выбранными параметрами, и он может иметь нулевое значение, если ни один из параметров не выбран.


Если выбран планировщик, отличный от RTC:
NUSE_TASK_NUMBER * NUSE REGISTERS * sizeof (ADDR)

Плюс, если выбрана поддержка сигналов:
NUSE_TASK_NUMBER

Плюс, если активирован вызов API NUSE_Task_Sleep():
NUSE_TASK_NUMBER * 2

Плюс, если активирована приостановка задач:
NUSE_TASK_NUMBER

Плюс, если активирована блокировка вызовов API:
NUSE_TASK_NUMBER

Плюс, если активирован счетчик планировщика:
NUSE_TASK_NUMBER * 2

Нереализованные в Nucleus SE вызовы API

Ниже перечислены семь вызовов API, которые есть в Nucleus RTOS, не реализованы в Nucleus SE.

Создание задачи (Create Task)


Этот вызов 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 – что задача приостановлена.


Возвращаемое значение:

NU_SUCCESS – указывает на успешное завершение службы;
NU_INVALID_TASK – указывает на то, что указатель на блок управления задачей нулевой (NULL);
NU_INVALID_ENTRY – указывает на то, что указатель на входную функцию задачи нулевой (NULL);
NU_INVALID_MEMORY – указывает на то, что сектор памяти, назначенный параметром stack_address, нулевой (NULL);
NU_INVALID_SIZE – указывает на то, что указанный размер стека недостаточен;
NU_INVALID_PREEMPT – указывает на то, что параметр preempt задан некорректно;
NU_INVALID_START – указывает на то, что параметр auto_start задан некорректно.

Удаление задачи (Delete Task)


Этот вызов API удаляет ранее созданную задачу приложения, которая должна иметь статус Finished (завершение) или Terminated (полная приостановка). В этом вызове также нет необходимости в Nucleus SE, так как задачи создаются статически и не могут быть удалены.


Прототип вызова:

STATUS NU_Delete_Task (NU_TASK *task);

Параметры:

task – указатель на блок управления задачей

Возвращаемое значение:

NU_SUCCESS – указывает на успешное завершение службы;
NU_INVALID_TASK – указывает на то, что указатель на задачу задан некорректно;
NU_INVALID_DELETE – указывает на то, что задача не находится в состоянии «Finished» или «Terminated».

Получение указателей задач (Get Task Pointers)


Этот вызов API составляет последовательный список указателей на все задачи в системе. Он не нужен в Nucleus SE, так как задачи идентифицируются при помощи простого индекса, а не указателя.


Прототип вызова:

UNSIGNED NU_Task_Pointers (NU_TASK **pointer_list, UNSIGNED maximum_pointers);

Параметры:


pointer_list – указатель на массив указателей NU_TASK. Этот массив будет заполнен указателями на установленные в системе задачи;
maximum_pointers – максимальное количество указателей, которое можно поместить в массив.


Возвращаемое значение:
Количество указателей NU_TASK, помещенных в массив.

Изменение приоритета задачи (Change Task Priority)

Этот вызов API назначает задаче новый приоритет. В Nucleus SE он не требуется, так как приоритеты задач постоянны.

Прототип вызова:

OPTION NU_Change_Priority (NU_TASK *task, OPTION new_priority);

Параметры:

task – указатель на блок управления задачами;
new_priority – задает приоритет от 0 до 255.

Возвращаемое значение:
Предыдущее значение приоритета задачи.

Изменение алгоритма вытеснения задачи (Change Task Preemption)


Этот вызов API меняет порядок вытеснения выполняемой задачи. В Nucleus SE он не нужен, так как используется более простой алгоритм планирования.


Прототип вызова:
OPTION NU_Change_Preemption (OPTION preempt);

Параметры:
preempt – новый алгоритм вытеснения, принимает значения NU_PREEMPT или NU_NO_PREEMPT

Возвращаемое значение:
Предыдущий алгоритм вытеснения задачи.

Изменение временного кванта задачи (Change Task Time Slice)


Этот вызов API меняет квант времени определенной задачи. В Nucleus SE в нем нет необходимости, так как кванты времени задач фиксированы.


Прототип вызова:
UNSIGNED NU_Change_Time_Slice (NU_TASK *task, UNSIGNED time_slice);

Параметры:

task – указатель на блок управления задачей;
time_slice – максимальное количество квантов времени, которое может пройти при выполнении этой задачи, нулевое значение этого поля отключает квантование времени для этой задачи.


Возвращаемое значение:
Предыдущее значение кванта времени задачи.

Завершение задачи (Terminate Task)

Этот вызов API завершает определенную задачу. В Nucleus SE в этом нет необходимости, так как состояние Terminated не поддерживается.

Прототип вызова:
STATUS NU_Terminate_Task (NU_TASK *task);

Параметры:
task – указатель на блок управления задачей.

Возвращаемое значение:
NU_SUCCESS – указывает на успешное завершение службы;
NU_INVALID_TASK – указывает на то, что указатель задачи задан некорректно.

Совместимость с Nucleus RTOS


При разработке 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).


Нереализованныеt вызовы API


Nucleus RTOS поддерживает 16 служебных вызовов для работы с задачами. Из них 7 не реализованы в Nucleus SE. Их описание, а также причина их исключения описаны выше.

В следующей статье начнем рассматривать управление памятью RTOS.


Об авторе: Колин Уоллс уже более тридцати лет работает в сфере электронной промышленности, значительную часть времени уделяя встроенному ПО. Сейчас он – инженер в области встроенного ПО в Mentor Embedded ( подразделение Mentor Graphics). Колин Уоллс часто выступает на конференциях и семинарах, автор многочисленных технических статей и двух книг по встроенному ПО. Живет в Великобритании. Профессиональный блог Колина: https://blogs.mentor.com/colinwalls/, e-mail: colin_walls@mentor.com


Источник: https://www.embedded.com/design/operating-systems/4458931/Task-data-structures-and-unsupported-API-calls

Теги: ОСРВ, RTOS, задачи, структуры данных задач, служебные вызовы, API, микроконтроллеры