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

Original author: Colin Walls
  • Translation


В этой статье продолжается обзор почтовых ящиков, начатый в предыдущей статье серии «Вся правда об ОСРВ».

Предыдущие статьи серии:

Статья #21. Почтовые ящики: введение и базовые службы
Статья #20. Семафоры: вспомогательные службы и структуры данных
Статья #19. Семафоры: введение и базовые службы
Статья #18. Группы флагов событий: вспомогательные службы и структуры данных
Статья #17. Группы флагов событий: введение и базовые службы
Статья #16. Сигналы
Статья #15. Разделы памяти: службы и структуры данных
Статья #14. Разделы памяти: введение и базовые службы
Статья #13. Структуры данных задач и неподдерживаемые вызовы API
Статья #12. Службы для работы с задачами
Статья #11. Задачи: конфигурация и введение в API
Статья #10. Планировщик: дополнительные возможности и сохранение контекста
Статья #9. Планировщик: реализация
Статья #8. Nucleus SE: внутреннее устройство и развертывание
Статья #7. Nucleus SE: введение
Статья #6. Другие сервисы ОСРВ
Статья #5. Взаимодействие между задачами и синхронизация
Статья #4. Задачи, переключение контекста и прерывания
Статья #3. Задачи и планирование
Статья #2. ОСРВ: Структура и режим реального времени
Статья #1. ОСРВ: введение.


Вспомогательные службы почтовых ящиков


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

Сброс почтового ящика


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

Вызов для сброса почтового ящика в Nucleus RTOS
Прототип служебного вызова:
STATUS NU_Reset_Mailbox(NU_MAILBOX *mailbox);

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

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

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

Прототип служебного вызова:
STATUS NUSE_Mailbox_Reset(NUSE_MAILBOX mailbox);

Параметры:
mailbox – индекс (ID) сбрасываемого почтового ящика.

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

Реализация сброса почтового ящика в Nucleus SE
Вариант кода функции NUSE_Mailbox_Reset (после проверки параметров) выбирается при помощи условной компиляции, в зависимости от того, активирована поддержка блокировки (приостановки) задач или нет. Мы рассмотрим оба эти варианта.

Если блокировка не активирована, код этой функции API довольно прост. Почтовый ящик помечается как неиспользуемый путем присвоения параметру NUSE_Mailbox_Status[] значения FALSE.

Если блокировка активирована, код становится более сложным:

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

Почтовый ящик сбрасывается в исходное состояние «Пустой».

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

Получение информации о почтовом ящике


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

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

Прототип служебного вызова:
STATUS NU_Mailbox_Information(NU_MAILBOX *mailbox, CHAR *name, OPTION *suspend_type, DATA_ELEMENT *message_present, UNSIGNED *tasks_waiting, NU_TASK **first_task);

Параметры:

mailbox – указатель на блок управления почтовым ящиком;
name – указатель на 8-символьную область для имени почтового ящика. В эту область включен и терминирующий нулевой байт;
suspend_type – указатель на переменную, в которой хранится тип приостановки задачи. Может принимать значения NU_FIFO и NU_PRIORITY;
message_present – указатель на переменную, которая примет значение NU_TRUE или NU_FALSE, в зависимости от того, заполнен почтовый ящик, или нет;
tasks_waiting – указатель на переменную, которая примет количество приостановленных на этом почтовом ящике задач;
first_task – указатель на указатель задачи, который примет указатель на первую приостановленную задачу.

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

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

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

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

STATUS NUSE_Mailbox_Information(NUSE_MAILBOX mailbox, U8 *message_present, U8 *tasks_waiting, NUSE_TASK *first_task);

Параметры:

mailbox – индекс почтового ящика, о котором запрашивается информация;
message_present – указатель на переменную, которая примет значение TRUE или FALSE, в зависимости от того, заполнен почтовый ящик, или нет;
tasks_waiting – указатель на переменную, которая примет количество приостановленных на этом почтовом ящике задач (ничего не возвращается, если приостановка задач отключена);
first_task – указатель на переменную типа NUSE_TASK, которая примет индекс первой приостановленной задачи (ничего не возвращается, если приостановка задач отключена).

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

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

Реализация получения информации о почтовом ящике в Nucleus SE

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

*message_present = NUSE_Mailbox_Status[mailbox];
#if NUSE_BLOCKING_ENABLE
   *tasks_waiting = NUSE_Mailbox_Blocking_Count[mailbox];
   if (NUSE_Mailbox_Blocking_Count[mailbox] != 0)
   {
      U8 index;
      for (index=0; index<NUSE_TASK_NUMBER; index++)
      {
         if ((LONIB(NUSE_Task_Status[index]) ==
            NUSE_MAILBOX_SUSPEND)
            && (HINIB(NUSE_Task_Status[index]) == mailbox))
         {
            *first_task = index;
            break;
         }
      }
   }
   else
   {
      *first_task = 0;
   }
#else
   *tasks_waiting = 0;
   *first_task = 0;
#endif
return NUSE_SUCCESS;

Функция возвращает статус почтового ящика. Затем, если служебные вызовы для блокировки задач активированы, возвращается количество приостановленных задач и индекс первой из них (в противном случае, этим параметрам присваивается значение 0).

Получение количества почтовых ящиков


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

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

Прототип служебного вызова:
UNSIGNED NU_Established_Mailboxes(VOID);

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

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

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

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

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

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

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

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


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

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

Данные в ОЗУ


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

NUSE_Mailbox_Data[] – массив типа ADDR, имеющий одну запись для каждого сконфигурированного почтового ящика, в нем хранятся данные почтового ящика.
NUSE_Mailbox_Status[] – массив типа U8, имеющий одну запись для каждого сконфигурированного почтового ящика, в нем отслеживается использование почтовых ящиков. Ненулевое значение (TRUE) говорит о том, что почтовый ящик заполнен.
NUSE_Mailbox_Blocking_Count[] – массив типа U8, он содержит в себе счетчик заблокированных задач каждого почтового ящика. Этот массив создается, только если активирована поддержка функциональности блокировки вызовов API.

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

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

RAM ADDR NUSE_Mailbox_Data[NUSE_MAILBOX_NUMBER];
RAM U8 NUSE_Mailbox_Status[NUSE_MAILBOX_NUMBER];
#if NUSE_BLOCKING_ENABLE
   RAM U8 NUSE_Mailbox_Blocking_Count[NUSE_MAILBOX_NUMBER];
#endif

Данные в ПЗУ


Для реализации почтовых ящиков не используются данные в ПЗУ.

Объем памяти для почтовых ящиков


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

Объем памяти для данных в ПЗУ для всех почтовых ящиков в приложении равен 0.

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

В противном случае:
NUSE_MAILBOX_NUMBER * (sizeof(ADDR) +1)

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


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

Создание почтового ящика


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

Прототип служебного вызова:
STATUS NU_Create_Mailbox(NU_MAILBOX *mailbox, CHAR *name, UNSIGNED OPTION suspend_type);

Параметры:

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

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

NU_SUCCESS – вызов был успешно завершен;
NU_INVALID_MAILBOX – нулевой указатель на блок управления почтовым ящиком (NULL), или указатель уже используется;
NU_INVALID_SUSPEND – некорректный параметр suspend_type.

Удаление почтового ящика


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

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

STATUS NU_Delete_Mailbox(NU_MAILBOX *mailbox);

Параметры:

mailbox – указатель на блок управления почтовым ящиком.

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

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

Указатели на почтовые ящики


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

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

UNSIGNED NU_Mailbox_Pointers(NU_MAILBOX **pointer_list, UNSIGNED maximum_pointers);

Параметры:

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

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

Количество указателей NU_MAILBOX в массиве.

Запись сообщения в почтовый ящик для доставки всем адресатам


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

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

STATUS NU_Broadcast_To_Mailbox(NU_MAILBOX *mailbox, VOID *message, UNSIGNED suspend);

Параметры:

mailbox – указатель на блок управления почтовым ящиком;
message – указатель на передаваемое сообщение;
suspend – указывает на то, необходимо ли приостанавливать вызывающую задачу, если почтовый ящик уже содержит сообщение; может принимать значения NU_NO_SUSPEND, NU_SUSPEND или значение таймаута.

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

NU_SUCCESS – вызов был успешно завершен;
NU_INVALID_MAILBOX – некорректный указатель на почтовый ящик;
NU_INVALID_POINTER – нулевой указатель на сообщение (NULL);
NU_INVALID_SUSPEND – попытка приостановки из не связанного с задачей потока;
NU_MAILBOX_FULL – почтовый ящик уже содержит сообщение;
NU_TIMEOUT – после истечения времени таймаута почтовый ящик все еще полон;
NU_MAILBOX_DELETED – почтовый ящик был удален пока задача была приостановлена.
NU_MAILBOX_RESET – почтовый ящик был сброшен пока задача была приостановлена.

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


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

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


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

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

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


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

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


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

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

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

Similar posts

Comments 0

Only users with full accounts can post comments. Log in, please.