Продолжение подробного сравнительного анализа каналов передачи данных в операционных системах Nucleus и Nucleus SE от Колина Уоллса, эксперта в области встроенного ПО.
Nucleus RTOS имеет четыре вызова API, которые предоставляют вспомогательные функции, связанные с каналами: сброс канала, получение информации о канале, получение количества каналов в приложении и получение указателей на все каналы в приложении. Первые три функции реализованы в Nucleus SE.
Этот вызов API сбрасывает канал в его исходное, неиспользуемое состояние. Любые сообщения, хранящиеся в нем, будут потеряны. Любые приостановленные на канале задачи возобновляются с кодом возврата NUSE_PIPE_WAS_RESET.
Код функции NUSE_Pipe_Reset() (после проверки параметров) довольно прост. Индексам начала и конца канала, а также счетчику сообщений в канале присваивается значение 0.
Если блокировка задач активирована, дополнительный код отвечает за восстановление приостановленные задачи:
while (NUSE_Pipe_Blocking_Count[pipe] != 0)
{
U8 index; /* check whether any tasks are blocked */
/* on this pipe */
for (index=0; index<NUSE_TASK_NUMBER; index++)
{
if ((LONIB(NUSE_Task_Status[index]) == NUSE_PIPE_SUSPEND)
&& (HINIB(NUSE_Task_Status[index]) == pipe))
{
NUSE_Task_Blocking_Return[index] = NUSE_PIPE_RESET;
NUSE_Task_Status[index] = NUSE_READY;
break;
}
}
NUSE_Pipe_Blocking_Count[pipe]--;
}
#if NUSE_SCHEDULER_TYPE == NUSE_PRIORITY_SCHEDULER
NUSE_Reschedule(NUSE_NO_TASK);
#endif
Каждой приостановленной на канале задаче присваивается статус «готова» с кодом возврата NUSE_PIPE_WAS_RESET. После того как этот процесс завершен, если используется планировщик Priority, вызывается функция NUSE_Reschedule(), так как одна или несколько задач с высоким приоритетом могут быть готовыми к выполнению.
Этот служебный вызов возвращает информацию о канале. Реализация этого вызова в Nucleus SE отличается от Nucleus RTOS тем, что она возвращает меньше информации. Это вызвано тем, что именование объектов, сообщения переменной длины и порядок приостановки задач не поддерживаются в Nucleus SE, а приостановка задач может быть отключена.
STATUS NU_Pipe_Information(NU_PIPE *pipe, CHAR *name, VOID **start_address, UNSIGNED *pipe_size, UNSIGNED *available, UNSIGNED *messages, OPTION *message_type, UNSIGNED *message_size, OPTION *suspend_type, UNSIGNED *tasks_waiting, NU_TASK **first_task);
pipe – указатель на предоставляемый пользователем блок управления каналом;
name – указатель на 8-символьную область для имени сообщения канала;
start_address – указатель на указатель, в который будет записан адрес начала области данных канала;
pipe_size – указатель на переменную для хранения общего количества байтов в канале;
available – указатель на переменную для хранения количества доступных байтов в канале;
messages – указатель на переменную для хранения количества сообщений в канале;
message_type – указатель на переменную для хранения типа сообщений, поддерживаемый каналом. Может принимать значения NU_FIXED_SIZE и NU_VARIABLE_SIZE;
message_size – указатель на переменную для хранения количества байтов в каждом сообщении канала. Если канал поддерживает сообщения переменной длины, это число будет являться максимальным размером сообщения;
suspend_type – указатель на переменную для хранения типа приостановки задачи. Может принимать значения NU_FIFO и NU_PRIORITY;
tasks_waiting – указатель на переменную для хранения количества приостановленных на этом канале задач;
first_task – указатель на указатель на первую приостановленную задачу.
STATUS NUSE_Pipe_Information(NUSE_PIPE pipe, ADDR *start_address, U8 *pipe_size, U8 *available, U8 *messages, U8 *message_size, U8 *tasks_waiting, NUSE_TASK *first_task);
pipe – индекс канала, информация о котором запрашивается;
start_address – указатель на переменную типа ADDR для хранения адреса начала области данных канала;
pipe_size – указатель на переменную типа U8 для хранения общего количества сообщений, которое может принять канал;
available – указатель на переменную типа U8 для хранения количества сообщений, для которых в канале осталось свободное место;
messages – указатель на переменную типа U8 для хранения текущего количества сообщений в канале;
message_size – указатель на переменную типа U8 для хранения размера сообщений, обрабатываемых данным каналом;
tasks_waiting – указатель на переменную для хранения количества задач, приостановленных на этом канале (ничего не возвращается, если приостановка задач отключена);
first_task – указатель на переменную типа NUSE_TASK, которая примет индекс первой приостановленной задачи (ничего не возвращается, если приостановка задач отключена).
*start_address = NUSE_Pipe_Data[pipe];
*pipe_size = NUSE_Pipe_Size[pipe];
*available = NUSE_Pipe_Size[pipe] - NUSE_Pipe_Items[pipe];
*messages = NUSE_Pipe_Items[pipe];
*message_size = NUSE_Pipe_Message_Size[pipe];
#if NUSE_BLOCKING_ENABLE
*tasks_waiting = NUSE_Pipe_Blocking_Count[pipe];
if (NUSE_Pipe_Blocking_Count[pipe] != 0)
{
U8 index;
for (index=0; index<NUSE_TASK_NUMBER; index++)
{
if ((LONIB(NUSE_Task_Status[index]) == NUSE_PIPE_SUSPEND)
&& (HINIB(NUSE_Task_Status[index]) == pipe))
{
*first_task = index;
break;
}
}
}
else
{
*first_task = 0;
}
#else
*tasks_waiting = 0;
*first_task = 0;
#endif
Функция возвращает статус канала. Затем, если блокировки задач активирована, возвращаются количество ожидающих задач и индекс первой из них (в противном случае, этим двум параметрам присваивается значение 0).
Этот служебный вызов возвращает количество каналов, сконфигурированных в приложении. В Nucleus RTOS это значение может меняться со временем, а возвращаемое значение будет показывать текущее количество каналов. В Nucleus SE возвращаемое значение задается на этапе сборки и не может измениться.
Каналы используют шесть либо семь структур данных (которые находятся либо в ОЗУ, либо в ПЗУ), являющиеся (как и другие объекты Nucleus SE) набором таблиц, размер и количество которых соответствует количеству сконфигурированных каналов и их параметрам.
Настоятельно рекомендую, чтобы код приложения не использовал прямой доступ к этим структурам данных, а обращался к ним через предоставляемые функции API. Это позволит избежать несовместимости с будущими версиями Nucleus SE и нежелательных побочных эффектов, а также упростит портирование приложений на Nucleus RTOS. Ниже приведен подробный обзор структур данных, чтобы упростить понимание работы кода служебных вызовов и для отладки.
Эти данные имеют следующую структуру:
NUSE_Pipe_Head[] — это массив указателей типа U8, имеющий одну запись для каждого сконфигурированного канала и указывающий на начало канала сообщений. Используется в качестве индекса адресов в NUSE_Pipe_Data[] (см. ниже).
NUSE_Pipe_Tail[] – это массив типа U8, имеющий одну запись для каждого сконфигурированного канала и указывающий на конец канала сообщений. Используется в качестве индекса адресов в NUSE_Pipe_Data[] (см. ниже).
NUSE_Pipe_Items[] – это массив типа U8, имеющий одну запись для каждого сконфигурированного канала и являющийся счетчиком текущего количества сообщений в канале. Эти данные являются избыточными, так как это значение можно получить через индексы начала и конца канала, но наличие счетчика упрощает код.
NUSE_Pipe_Blocking_Count[] – этот массив типа U8 содержит счетчики количества, заблокированных задач на каждом канале. Этот массив создается, только если активирована поддержка блокировки задач.
Все эти структуры данных инициализируются нулями функцией NUSE_Init_Pipe() при запуске Nucleus SE. Это логично, так как все каналы создаются пустыми (неиспользуемыми). Одна из следующих статей будет содержать полное описание процедур запуска Nucleus SE.
Ниже приведены определения этих структур данных в файле nuse_init.c:
RAM U8 NUSE_Pipe_Head[NUSE_PIPE_NUMBER];
RAM U8 NUSE_Pipe_Tail[NUSE_PIPE_NUMBER];
RAM U8 NUSE_Pipe_Items[NUSE_PIPE_NUMBER];
#if NUSE_BLOCKING_ENABLE
RAM U8 NUSE_Pipe_Blocking_Count[NUSE_PIPE_NUMBER];
#endif
На пользователе лежит ответственность за предоставление области данных в ОЗУ для хранения данных каждого сконфигурированного канала. Размер этой области должен вмещать массив типа U8, в который поместятся все сообщения канала.
Структуру этих данных такова:
NUSE_Pipe_Data[] – массив типа ADDR, имеющий одну запись для каждого сконфигурированного канала и указывающий на область данных каждого канала (см. раздел «Пользовательские данные в ОЗУ» выше).
NUSE_Pipe_Size[] – массив типа U8, имеющий одну запись для каждого сконфигурированного канала и показывающий количество сообщений, которое может поместиться в каждый канал.
NUSE_Pipe_Message_Size[] – массив типа U8, имеющий одну запись для каждого сконфигурированного канала и показывающий размер сообщений (в байтах), которые можно поместить в каждый канал.
ROM ADDR *NUSE_Pipe_Data[NUSE_PIPE_NUMBER] =
{
/* addresses of pipe data areas ------ */
};
ROM U8 NUSE_Pipe_Size[NUSE_PIPE_NUMBER] =
{
/* pipe sizes ------ */
};
ROM U8 NUSE_Pipe_Message_Size[NUSE_PIPE_NUMBER] =
{
/* pipe message sizes ------ */
};
Как и у всех других объектов ядра Nucleus SE, объем памяти, необходимый для каналов, предсказуем.
Объем данных в ПЗУ (в байтах) для всех каналов в приложении можно вычислить следующим образом:
NUSE_PIPE_NUMBER * (sizeof(ADDR) + 2)
Объем данных ядра в ОЗУ (в байтах) для всех каналов приложении при активированной блокировке задач можно вычислить следующим образом: NUSE_PIPE_NUMBER * 4
STATUS NU_Create_Pipe(NU_PIPE *pipe, CHAR *name, VOID *start_address, UNSIGNED pipe_size, OPTION message_type, UNSIGNED message_size, OPTION suspend_type);
pipe – указатель на предоставленный пользователем блок управления каналом, он будет использоваться в качестве основного активатора каналов в других вызовах API;
name – указатель на 7-символьное имя канала с нулевым терминирующим нулем;
start_address – адрес начала канала;
pipe_size – общее количество байтов в канале;
message_type – тип сообщений, поддерживаемый каналом. Может принимать значения NU_FIXED_SIZE или NU_VARIABLE_SIZE;
message_size – если канал поддерживает сообщения фиксированной длины, этот параметр указывает точный размер каждого сообщения. В противном случае, если канал поддерживает сообщения переменной длины, это значение будет являться максимальным размером сообщения;
suspend_type – указывает тип приостановки задач на канале. Может принимать значения NU_FIFO и NU_PRIORITY (планировщик FIFO и планировщик PRIORITY, соответственно).
NU_SUCCESS – вызов был успешно завершен;
NU_INVALID_PIPE – нулевой указатель на блок управления каналом (NULL), или блок управления уже используется;
NU_INVALID_MEMORY – в start_address была указана некорректная область данных;
NU_INVALID_MESSAGE – некорректный параметр message_type;
NU_INVALID_SIZE – либо размер сообщения больше размера канала, либо размер канала или сообщения равен нулю;
NU_INVALID_SUSPEND – некорректный параметр suspend_type.
Этот вызов API удаляет ранее созданный канал. В Nucleus SE в нем нет необходимости, так как каналы создаются статически и их нельзя удалять.
Этот вызов API строит последовательный список указателей на все каналы в системе. В Nucleus SE в нем нет необходимости, так как каналы идентифицируются при помощи простого индекса, а не указателя, следовательно, такая функция была бы избыточной.
pointer_list – указатель на массив указателей NU_PIPE. Этот массив будет заполнен указателями на созданные ранее каналы в системе;
maximum pointers – максимальное количество указателей в массиве.
Этот вызов API передает сообщение всем задачам, ожидающие сообщений от определенного канала. В Nucleus SE эта функция не была реализована, так как она добавляет избыточную сложность.
pipe – указатель на блок управления каналом;
message – указатель на передаваемое сообщение;
size – количество элементов данных типа UNSIGNED в сообщении. Если канал поддерживает сообщения переменной длины, этот параметр должен быть равен или меньше размера сообщения, поддерживаемого каналом. Если канал поддерживает сообщения фиксированной длины, этот параметр должен быть точно равен размеру сообщений, поддерживаемому каналом;
suspend – указывает, нужно ли приостанавливать вызывающую задачу, если канал уже заполнен. Может принимать значения NU_NO_SUSPEND, NU_SUSPEND или значение таймаута.
Как и в случае со всеми другими объектами Nucleus SE, моей целью было обеспечение максимальной совместимости кода приложений с Nucleus RTOS. Каналы не являются исключением и, с точки зрения пользователя, они реализованы так же, как и в Nucleus RTOS. Есть и определенная несовместимость, которую я посчитал допустимой, с учетом того, что в результате код станет более понятным и более эффективным с точки зрения объема требуемой памяти. В остальном, вызовы API Nucleus RTOS могут быть практически напрямую перенесены на Nucleus SE.
В Nucleus RTOS все объекты описываются структурой данных (управляющим блоком), который имеет определенный тип данных. Указатель на этот блок управления служит идентификатором канала. Я решил, что в Nucleus SE для эффективного использования памяти необходим другой подход: все объекты ядра описываются набором таблиц в ОЗУ и/или ПЗУ. Размер этих таблиц определяется количеством сконфигурированных объектов каждого типа. Идентификатор конкретного объекта – индекс в этой таблице. Таким образом, я определил NUSE_PIPE в качестве эквивалента U8, переменная (а не указатель) этого типа служит идентификатором канала. С этой небольшой несовместимостью легко справиться если код портируется с Nucleus SE на Nucleus RTOS и наоборот. Обычно над идентификаторами объектов не выполняются никакие операции, кроме перемещения и хранения.
Nucleus RTOS также поддерживает присваивание имен каналам. Эти имена используются только при отладке. Я исключил их из Nucleus SE, чтобы сэкономить память.
В Nucleus RTOS канал может быть сконфигурирован таким образом, чтобы обрабатывать сообщения, состоящие произвольного количества байтов, точно также, как и Nucleus SE. Nucleus RTOS также поддерживает каналы с сообщениями переменной длины, для которых во время создания указывается только максимальный размер сообщений. Сообщения переменной длины не поддерживаются в Nucleus SE.
В Nucleus SE максимально количество сообщений в канале равно 256, так как все переменные и константы имеют тип U8. Nucleuts RTOS не имеет таких ограничений.
Nucleus RTOS поддерживает десять служебных вызовов для работы с каналами. Четыре из них не реализованы в Nucleus SE. Подробное описание этих вызовах, а также причины такого решения можно найти в разделе «Нереализованные вызовы API» выше в этой статье.
В следующей статье мы рассмотрим системное время.
Об авторе: Колин Уоллс уже более тридцати лет работает в сфере электронной промышленности, значительную часть времени уделяя встроенному ПО. Сейчас он – инженер в области встроенного ПО в Mentor Embedded ( подразделение Mentor Graphics). Колин Уоллс часто выступает на конференциях и семинарах, автор многочисленных технических статей и двух книг по встроенному ПО. Живет в Великобритании. Профессиональный блог Колина: https://blogs.mentor.com/colinwalls/, e-mail: colin_walls@mentor.com