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

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

14 декабря 2018

В этой статье мы продолжим рассматривать очереди.



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


Вспомогательные службы очередей


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


Сброс очереди

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

Вызов для сброса очереди в Nucleus RTOS
Прототип служебного вызова:
STATUS NU_Reset_Queue(NU_QUEUE *queue);

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

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

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

Прототип служебного вызова:
STATUS NUSE_Queue_Reset(NUSE_QUEUE queue);

Параметры:
queue – индекс (ID) сбрасываемой очереди.

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

Реализация сброса очереди в Nucleus SE

Код функции NUSE_Queue_Reset (после проверки параметров) довольно прост. Индексам головы и хвоста очереди, а также счетчику сообщений в очереди присваивается нулевое значение.

Если блокировка задач активирована, дополнительный код отвечает за восстановление приостановленных задач:


while (NUSE_Queue_Blocking_Count[queue] != 0)
{
   U8 index;             /* check whether any tasks are blocked */
                         /* on this queue */
   for (index=0; index<NUSE_TASK_NUMBER; index++)
   {
      if ((LONIB(NUSE_Task_Status[index]) == NUSE_QUEUE_SUSPEND)
           && (HINIB(NUSE_Task_Status[index]) == queue))
      {
         NUSE_Task_Blocking_Return[index] = NUSE_QUEUE_WAS_RESET;
         NUSE_Task_Status[index] = NUSE_READY;
         break;
      }
   }
   NUSE_Queue_Blocking_Count[queue]--;
}
#if NUSE_SCHEDULER_TYPE == NUSE_PRIORITY_SCHEDULER
   NUSE_Reschedule(NUSE_NO_TASK);
#endif


Каждой приостановленной задаче в очереди присваивается статус «готова» с кодом возврата NUSE_QUEUE_WAS_RESET. После того, как этот процесс завершен, если используется планировщик Priority, вызывается функция NUSE_Reschedule(), так как одна или несколько задач с высоким приоритетом могут быть готовыми к выполнению.


Получение информации об очереди

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

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

STATUS NU_Queue_Information(NU_QUEUE *queue, CHAR *name, VOID **start_address, UNSIGNED *queue_size, UNSIGNED *available, UNSIGNED *messages, OPTION *message_type, UNSIGNED *message_size, OPTION *suspend_type, UNSIGNED *tasks_waiting, NU_TASK **first_task);


Параметры:

queue – указатель на предоставленный пользователем блок управления очередью;
name – указатель на 8-символьную область для имени сообщения в очереди;
start_address – указатель на указатель, в который будет записан адрес начала области данных очереди;
queue_size – указатель на переменную для хранения общего количества элементов типа UNSIGNED в очереди;
available – указатель на переменную для хранения количества доступных элементов UNSIGNED в очереди;
messages – указатель на переменную для хранения текущего количества сообщений в очереди;
message_type – указатель на переменную для хранения типа сообщений, поддерживаемых очередью. Допустимые значения — NU_FIXED_SIZE и NU_VARIABLE;
message_size – указатель на переменную для хранения количества элементов данных типа UNSIGNED в каждом сообщении очереди. Если очередь поддерживает сообщения переменной длины, это число показывает максимальную длину сообщения;
suspend_type – указатель на переменную для хранения типа приостановки задач. Допустимые значения — NU_FIFO и NU_PRIORITY;
tasks_waiting – указатель на переменную для хранения количества приостановленных на этой очереди задач;
first_task – указатель на указатель задачи, в который помещается указатель первой приостановленной задачи.


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

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

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

STATUS NUSE_Queue_Information(NUSE_QUEUE queue, ADDR *start_address, U8 *queue_size, U8 *available, U8 *messages, U8 *tasks_waiting, NUSE_TASK *first_task);


Параметры:

queue – индекс очереди, о которой запрашивается информация;
start_address – указатель на переменную типа ADDR, в которой будет храниться адрес начала области данных очереди;
queue_size – указатель на переменную типа U8, в которой будет храниться общее количество сообщений, способное поместиться в очередь;
available – указатель на переменную типа U8, в которой будет храниться количество свободных мест в очереди;
messages – указатель на переменную типа U8, в которой будет храниться текущее количество сообщений в очереди;
tasks_waiting – указатель на переменную, в которой будет храниться количество задач, приостановленных на этой очереди (ничего не возвращается, если блокировка задач отключена);
first_task – указатель на переменную типа NUSE_TASK, в которой будет храниться индекс первой приостановленной задачи (ничего не возвращается, если блокировка задач отключена).


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

Реализация отображения информации об очереди в Nucleus SE

Реализация этого вызова API довольно проста:

*start_address = NUSE_Queue_Data[queue];
*queue_size = NUSE_Queue_Size[queue];
*available = NUSE_Queue_Size[queue] - NUSE_Queue_Items[queue];
*messages = NUSE_Queue_Items[queue];
#if NUSE_BLOCKING_ENABLE
   *tasks_waiting = NUSE_Queue_Blocking_Count[queue];
   if (NUSE_Queue_Blocking_Count[queue] != 0)
   {
      U8 index;
      for (index=0; index<NUSE_TASK_NUMBER; index++)
      {
         if ((LONIB(NUSE_Task_Status[index]) ==
            NUSE_QUEUE_SUSPEND)
            && (HINIB(NUSE_Task_Status[index]) == queue))
         {
            *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_Queues(VOID);

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

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

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

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

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

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

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

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


Очереди используют пять или шесть структур данных (которые находятся либо в ОЗУ, либо в ПЗУ), являющихся наборами таблиц (как и другие объекты Nucleus SE), количество и размер которых соответствует количеству очередей в приложении и выбранным параметрам.


Данные ядра в ОЗУ

Эти данные имеют следующую структуру:

NUSE_Queue_Head[] – массив указателей типа U8, имеет одну запись для каждой сконфигурированной очереди и указывает на голову очереди сообщений. Используется в качестве индекса адресов в NUSE_Queue_Data[] (см.ниже);
NUSE_Queue_Tail[] – массив типа U8, имеет одну запись для каждой сконфигурированной в приложении очереди и указывает на хвост очереди сообщений. Используется в качестве индекса адресов в NUSE_Queue_Data[] (см. ниже);
NUSE_Queue_Items[] – массив типа U8, имеет одну запись для каждой сконфигурированной очереди и является счетчиком сообщений в очереди. Эти данные можно считать излишними, так как эти значения можно получить через индексы начала и конца очереди, однако хранение счетчика упрощает код;
NUSE_Queue_Blocking_Count[] – этот массив типа U8 содержит счетчики количества задач, приостановленных на каждой очереди. Этот массив создается, только если активирована поддержка блокировки задач.


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

Ниже приведены определения этих структур в файле nuse_init.c:

RAM U8 NUSE_Queue_Head[NUSE_QUEUE_NUMBER];
RAM U8 NUSE_Queue_Tail[NUSE_QUEUE_NUMBER];
RAM U8 NUSE_Queue_Items[NUSE_QUEUE_NUMBER];
#if NUSE_BLOCKING_ENABLE
   RAM U8 NUSE_Queue_Blocking_Count[NUSE_QUEUE_NUMBER];
#endif

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


Пользователь ответственен за предоставление области ОЗУ для хранения каждой очереди. Размер этой области должен вмещать массив типа ADDR, в котором каждая запись соответствует одному сообщению в очереди. 


Данные в ПЗУ

Эти данные имеют следующую структуру:

NUSE_Queue_Data[] – массив типа ADDR, имеет одну запись для каждой сконфигурированной очереди и указывает на область данных очереди (см. Пользовательские данные ОЗУ);
NUSE_Queue_Size[] – массив типа U8, имеет одну запись для каждой сконфигурированной очереди и показывает максимальное количество сообщений, которое может принять каждая очередь.


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

ROM ADDR *NUSE_Queue_Data[NUSE_QUEUE_NUMBER] =
{
   /* addresses of queue data areas ------ */
};
ROM U8 NUSE_Queue_Size[NUSE_QUEUE_NUMBER] =
{
   /* queue sizes ------ */
};

Объем памяти для очередей

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

Объем данных в ПЗУ (в байтах) для всех очередей в приложении можно вычислить следующим образом:
NUSE_QUEUE_NUMBER * (sizeof(ADDR) + 1)


Объем данных ядра в ОЗУ (в байтах) для всех очередей в приложении при активированной блокировке задач вычисляется следующим образом:

NUSE_QUEUE_NUMBER * 3

Если блокировка отключена:
NUSE_QUEUE_NUMBER * 4

Объем пользовательских данных в ОЗУ (в байтах) для очереди с индексом queue:
NUSE_Queue_Size[queue] * sizeof(ADDR)

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

Четыре вызова API, которые можно найти в Nucleus RTOS, не реализованы в Nucleus SE:

Создание очереди

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

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

STATUS NU_Create_Queue(NU_QUEUE *queue, char *name, VOID *start_address, UNSIGNED queue_size, OPTION message_type, UNSIGNED message_size, OPTION suspend_type);


Параметры:

queue – указатель на предоставленный пользователем блок управления, используется для управления очередями в других вызовах API;
name – указатель на 7-символьное имя очереди с нулевым терминирующим байтом;
start_address – адрес начала очереди;
message_type – тип сообщения, поддерживаемый очередью. Может принимать значения NU_FIXED_SIZE или NU_VARIABLE_SIZE;
message_size – если очередь поддерживает сообщения фиксированной длины, этот параметр задает точную длину каждого сообщения, в противном случае, если очередь поддерживает сообщения переменной длины, это значение является максимальной длиной сообщения;
suspend_type – определяет тип приостановки задач в очереди. Может принимать значения NU_FIFO и NU_PRIORITY, которые означают принцип FIFO (First-In-First-Out) или принцип приоритета приостановки задач, соответственно.


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

NU_SUCCESS – вызов был успешно завершен;
NU_INVALID_QUEUE – нулевой указатель на блок управления очередью (NULL), или указатель уже используется;
NU_INVALID_MEMORY – некорректная область памяти, указанная в start_address;
NU_INVALID_MESSAGE – некорректный параметр message_type;
NU_INVALID_SIZE – очередь не поддерживает сообщения такой длины, либо размер очереди и/или длина сообщения равна 0;
NU_INVALID_SUSPEND – некорректный параметр suspend_type.


Удаление очереди


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


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

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

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

Указатели очереди


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


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

Параметры:

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


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

Передача в очередь (Broadcast to Queue)


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


Прототип служебного вызова:
STATUS NU_Broadcast_To_Queue(NU_QUEUE *queue, VOID *message, UNSIGNED size, UNSIGNED suspend);

Параметры:

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


Возвращаемое значение:
NU_SUCCESS – вызов был успешно завершен;
NU_INVALID_QUEUE – некорректный указатель на очередь;
NU_INVALID_POINTER – нулевой указатель на сообщение (NULL);
NU_INVALID_SIZE – указанная длина сообщения не совместима с длиной, указанной при создании очереди;
NU_INVALID_SUSPEND – попытка приостановки задачи из не связанного с задачей потока;
NU_QUEUE_FULL – в очереди недостаточно места для сообщения;
NU_TIMEOUT – очередь все еще переполнена после истечения таймаута;
NU_QUEUE_DELETED – очередь была удалена, пока задача была приостановлена;
NU_QUEUE_RESET – очередь была сброшена, пока задача была приостановлена.

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


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


Идентификаторы объектов


В Nucleus RTOS все объекты описываются структурой данных (блоками управления), который имеет определенный тип данных. Указатель на этот блок управления служит идентификатором очереди. Я решил, что в Nucleus SE для эффективного использования памяти необходим другой подход: все объекты ядра описываются набором таблиц в ОЗУ и/или ПЗУ. Размер этих таблиц определяется количеством сконфигурированных объектов каждого типа. Идентификатор конкретного объекта – индекс в этой таблице. Таким образом, я определил NUSE_QUEUE в качестве эквивалента U8, переменная (а не указатель) этого типа служит идентификатором очереди. С этой небольшой несовместимостью легко справиться, если код портируется с Nucleus SE на Nucleus RTOS и наоборот. Обычно над идентификаторами объектов не выполняются никакие операции, кроме перемещения и хранения.

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


Размер и тип сообщений


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

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


Размер очереди


В Nucleus SE максимально количество сообщений в очереди равно 256, так как все переменные и константы имеют тип U8. Nucleus 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/4461115/Queues--utility-services-and-data-structures

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