+7 (812) 670-9095
Обратная связьEnglish
Главная → Статьи → Системное ПО → Статья #26. Каналы: вспомогательные службы и структуры данных
Версия для печати

Статья #26. Каналы: вспомогательные службы и структуры данных

30 апреля 2019

Продолжение подробного сравнительного анализа каналов передачи данных в операционных системах Nucleus и Nucleus SE от Колина Уоллса, эксперта в области встроенного ПО.




Статья #26. Каналы: вспомогательные службы и структуры данных


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


Сброс канала


Этот вызов API сбрасывает канал в его исходное, неиспользуемое состояние. Любые сообщения, хранящиеся в нем, будут потеряны. Любые приостановленные на канале задачи возобновляются с кодом возврата NUSE_PIPE_WAS_RESET.


Вызов для сброса канала в Nucleus RTOS
Прототип служебного вызова:
STATUS NU_Reset_Pipe(NU_PIPE *pipe);

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

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

Вызов для сброса канала в Nucleus SE
Этот служебный вызов API поддерживает основной функционал Nucleus RTOS API.

Прототип служебного вызова:
STATUS NUSE_Pipe_Reset(NUSE_PIPE pipe);

Параметры:
pipe – индекс (ID) сбрасываемого канала.

Возвращаемое значение:
NUSE_SUCCESS – вызов был успешно завершен;
NUSE_INVALID_PIPE – некорректный индекс канала.

Реализация сброса канала в Nucleus SE

Код функции 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, а приостановка задач может быть отключена.


Вызов для получения информации о канале в Nucleus RTOS
Прототип служебного вызова:


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


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

Вызов для получения информации о канале в Nucleus SE
Этот вызов API поддерживает основной функционал Nucleus RTOS API.

Прототип служебного вызова:

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, которая примет индекс первой приостановленной задачи (ничего не возвращается, если приостановка задач отключена).


Возвращаемое значение:
NUSE_SUCCESS – вызов был успешно завершен;
NUSE_INVALID_PIPE – некорректный индекс канала;
NUSE_INVALID_POINTER – один или несколько параметров-указателей некорректны.

Реализация получения информации о канале в Nucleus SE
Реализация этого вызова API довольно проста:

*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 RTOS
Прототип служебного вызова:
UNSIGNED NU_Established_Pipes(VOID);

Параметры:
Отсутствуют.

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

Вызов для счетчика каналов в Nucleus SE
Этот служебный вызов поддерживает основной функционал Nucleus RTOS API

Прототип служебного вызова:
U8 NUSE_Pipe_Count(void);

Параметры:
Отсутствуют.

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

Реализация счетчика каналов в Nucleus SE
Реализация этого вызова API достаточно проста: возвращается значение символа #define NUSE_PIPE_NUMBER.

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


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


Эти структуры данных объявляются и инициализируются (статически) в файле nuse_config.c, таким образом:

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


В противном случае:
NUSE_PIPE_NUMBER * 3

Объем пользовательских данных в ОЗУ (в байтах) для канала с индексом pipe:
NUSE_Pipe_Size[pipe] * NUSE_Pipe_Message_Size[pipe]

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

Четыре служебных вызова API Nucleus RTOS не реализованы в Nucleus SE.

Создание канала

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

Прототип служебного вызова:

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 в нем нет необходимости, так как каналы создаются статически и их нельзя удалять.


Прототип служебного вызова:
STATUS NU_Delete_Pipe(NU_PIPE *pipe);

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

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

Указатели на каналы


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


Прототип служебного вызова:
UNSIGNED NU_Pipe_Pointers(NU_PIPE **pointer_list, UNSIGNED maximum_pointers);

Параметры:

pointer_list – указатель на массив указателей NU_PIPE. Этот массив будет заполнен указателями на созданные ранее каналы в системе;
maximum pointers – максимальное количество указателей в массиве.


Возвращаемое значение:
Количество указателей NU_PIPE в массиве

Массовая рассылка (broadcast) в канал


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


Прототип служебного вызова:

STATUS NU_Broadcast_To_Pipe(NU_PIPE *pipe, VOID *message, UNSIGNED size, UNSIGNED suspend);

Параметры:

pipe – указатель на блок управления каналом;
message – указатель на передаваемое сообщение;
size – количество элементов данных типа UNSIGNED в сообщении. Если канал поддерживает сообщения переменной длины, этот параметр должен быть равен или меньше размера сообщения, поддерживаемого каналом. Если канал поддерживает сообщения фиксированной длины, этот параметр должен быть точно равен размеру сообщений, поддерживаемому каналом;
suspend – указывает, нужно ли приостанавливать вызывающую задачу, если канал уже заполнен. Может принимать значения NU_NO_SUSPEND, NU_SUSPEND или значение таймаута.


Возвращаемое значение:
NU_SUCCESS – вызов был успешно завершен;
NU_INVALID_PIPE – некорректный указатель на канал;
NU_INVALID_POINTER – нулевой указатель на сообщение (NULL);
NU_INVALID_SIZE – указанный размер сообщения несовместим с размером сообщения, заданным при создании канала;
NU_INVALID_SUSPEND – попытка приостановки из не связанного с задачей потока;
NU_PIPE_FULL – в канале не хватает места для сообщения;
NU_TIMEOUT – канал все еще полон, даже после того как указанный период таймаута истек;
NU_PIPE_DELETED – канал был удален, пока задача была приостановлена;
NU_PIPE_RESET – канал был сброшен, пока задача была приостановлена.

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


Как и в случае со всеми другими объектами 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 не имеет таких ограничений.


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


Nucleus RTOS поддерживает десять служебных вызовов для работы с каналами. Четыре из них не реализованы в Nucleus SE. Подробное описание этих вызовах, а также причины такого решения можно найти в разделе «Нереализованные вызовы API» выше в этой статье.

В следующей статье мы рассмотрим системное время.


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


Источник: https://www.embedded.com/design/operating-systems/4461296/Pipes--utility-services-and-data-structures

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