Почтовые ящики уже упоминались ранее в одной из предыдущих статей (#5). Они являются вторым по простоте после сигналов методом межзадачных коммуникаций, который поддерживается в Nucleus SE, и обеспечивают малозатратный и гибкий способ передачи простых сообщений между задачами.
В Nucleus SE почтовые ящики определяются на этапе сборки. В приложении может существовать до 16 почтовых ящиков. Если в приложении нет почтовых ящиков, код служебных вызовов и структуры данных, связанные с почтовыми ящиками, не будут включены в приложение.
Почтовый ящик – это просто место для хранения данных, размера которого хватает для хранения переменной типа ADDR и безопасный доступ к которому контролируется таким образом, чтобы им могли пользоваться несколько задач. Задача может слать данные на почтовый ящик. В результате ящик станет заполненным, и ни одна задача не сможет посылать на него данные до тех пор, пока какая-либо задача не выполнит операцию чтения почтового ящика либо пока ящик не будет очищен. Попытка послать данные на полный почтовый ящик или попытка чтения пустого ящика приведет к ошибке или к приостановке задачи, в зависимости от выбранных настроек вызовов API и конфигурации Nucleus SE.
В некоторых реализациях ОС почтовые ящики не реализованы, а в качестве альтернативы предлагается использовать очередь. Это звучит логично, так как такая очередь обеспечит тот же функционал, что и почтовый ящик. Однако очередь – более сложная структура данных и несет в себе гораздо больше служебных данных, кода и имеет более длительное время обслуживания.
В Nucleus SE, как и в Nucleus RTOS, можно выбрать любой из этих типов объектов. Если в вашем приложении несколько очередей и один почтовый ящик, то имеет смысл рассмотреть замену почтового ящика на очередь. Это немного увеличит количество служебных данных, но позволит избавиться от всего кода API, связанного с почтовыми ящиками. Как вариант, можно сконфигурировать приложение обоими методами и сравнить объем данных и производительность.
Очереди будут рассмотрены в будущих статьях.
Как и для большинства объектов Nucleus SE, настройка почтовых ящиков в основном задается директивами #define в файле nuse_config.h. Основным параметром является NUSE_MAILBOX_NUMBER, который определяет количество почтовых ящиков в приложении. Значение по умолчанию равно нулю (то есть почтовых ящиков нет) и может принимать значения вплоть до 16. Некорректное значение приведет к ошибке во время компиляции, которая будет сгенерирована проверкой в nuse_config_check.h (он входит в файл nuse_config.c и компилируется вместе с ним), что приведет к срабатыванию директивы #error.
Выбор ненулевого значения является основным активатором для почтовых ящиков. Этот параметр используется при определении структур данных и от его значения зависит их размер (более подробно об этом в следующей статье). Кроме того, ненулевое значение активирует настройки API.
Каждая функция API (служебный вызов) в Nucleus SE активируется директивой #define в файле nuse_config.h. Для почтовых ящиков такими директивами являются:
NUSE_MAILBOX_SEND
NUSE_MAILBOX_RECEIVE
NUSE_MAILBOX_RESET
NUSE_MAILBOX_INFORMATION
NUSE_MAILBOX_COUNT
По умолчанию, им присваивается значение FALSE, таким образом, отключая все служебные вызовы и блокируя включение реализующего их кода. Для настройки почтовых ящиков в приложении нужно выбрать необходимые вызовы API и присвоить им значение TRUE.
Ниже приведен участок кода из файла nuse_config.h.
/* Number of mailboxes in the system - 0-16 */
#define NUSE_MAILBOX_NUMBER 0
/* Service call enablers: */
#define NUSE_MAILBOX_SEND FALSE
#define NUSE_MAILBOX_RECEIVE FALSE
#define NUSE_MAILBOX_RESET FALSE
#define NUSE_MAILBOX_INFORMATION FALSE
#define NUSE_MAILBOX_COUNT FALSE
Если функция API почтового ящика активирована, а почтовых ящиков в приложении нет (кроме NUSE_Mailbox_Count(), который разрешен всегда), возникает ошибка при компиляции. Если ваш код использует вызов API, который не был активирован, возникнет ошибка компоновки, так как код реализации не был включен в приложении.
Nucleus RTOS поддерживает девять служебных вызовов, которые связаны с почтовыми ящиками и предоставляют следующий функционал:
Базовые операции, которые можно выполнять над почтовыми ящиками – запись и чтение данных (отправка и получение). Nucleus RTOS и Nucleus SE предоставляют два базовых вызова API для этих операций, которые будут описаны ниже.
Вызов API Nucleus RTOS для записи в почтовый ящик очень гибкий, что позволяет приостанавливать задачу неявным образом или с таймаутом, если операцию нельзя завершить немедленно (например, при попытке записи в полный почтовый ящик). Nucleus SE предоставляет аналогичный служебный вызов, только приостановка задачи опциональна, а таймаут не реализован.
Nucleus RTOS также предлагает служебный вызов для трансляции данных на почтовый ящик, этот вызов не поддерживается в Nucleus SE и он будет описан в разделе «Нереализованные вызовы API» в следующей статье.
Вариант кода функции API NUSE_Mailbox_Send() (после проверки параметров) выбирается при помощи условной компиляции, в зависимости от того, активирована поддержка вызовов API для блокировки (приостановки задач) или нет. Рассмотрим оба варианта.
if (NUSE_Mailbox_Status[mailbox]) /* mailbox full */
{
return_value = NUSE_MAILBOX_FULL;
}
else /* mailbox empty */
{
NUSE_Mailbox_Data[mailbox] = *message;
NUSE_Mailbox_Status[mailbox] = TRUE;
return_value = NUSE_SUCCESS;
}
do
{
if (!NUSE_Mailbox_Status[mailbox]) /* mailbox empty */
{
NUSE_Mailbox_Data[mailbox] = *message;
NUSE_Mailbox_Status[mailbox] = TRUE;
if (NUSE_Mailbox_Blocking_Count[mailbox] != 0)
{
U8 index; /* check whether a task is blocked */
/* on this mailbox */
NUSE_Mailbox_Blocking_Count[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_SUCCESS;
NUSE_Wake_Task(index);
break;
}
}
}
return_value = NUSE_SUCCESS;
suspend = NUSE_NO_SUSPEND;
}
else /* mailbox full */
{
if (suspend == NUSE_NO_SUSPEND)
{
return_value = NUSE_MAILBOX_FULL;
}
else
{ /* block task */
NUSE_Mailbox_Blocking_Count[mailbox]++;
NUSE_Suspend_Task(NUSE_Task_Active, (mailbox << 4) |
NUSE_MAILBOX_SUSPEND);
return_value =
NUSE_Task_Blocking_Return[NUSE_Task_Active];
if (return_value != NUSE_SUCCESS)
{
suspend = NUSE_NO_SUSPEND;
}
}
}
} while (suspend == NUSE_SUSPEND);
Некоторые объяснения могут быть полезными.
Код заключен в цикл do…while, который выполняется, пока параметр suspend имеет значение NUSE_SUSPEND.
Если почтовый ящик пуст, сообщение записывается, а статус почтового ящика меняется, чтобы показать, что он полон. Выполняется проверка на приостановленные задачи (которые ожидают чтения) на этом почтовом ящике. Если такие задачи есть, первая из них возобновляется. Переменной suspend присваивается значение NUSE_NO_SUSPEND, а вызов API завершается и возвращает NUSE_SUCCESS.
Если почтовый ящик полон, а suspend имеет значение NUSE_NO_SUSPEND, вызов API вернет NUSE_MAILBOX_FULL. Если suspend имеет значение NUSE_SUSPEND, задача приостанавливается. После завершения функции (например, когда задача возобновляется), если возвращаемое значение – NUSE_SUCCESS (которое говорит о том, что задача была возобновлена, потому что сообщение было прочитано, а не о том, что почтовый ящик был сброшен), цикл начинается с начала.
Служебный вызов Nucleus RTOS API для чтения почтового ящика очень гибкий и позволяет приостанавливать задачи неявным образом или с таймаутом, если операция не может быть завершена немедленно (например, при чтении из пустого почтового ящика). Nucleus SE предоставляет аналогичную службу, только приостановка задач опциональна, а таймаут не реализован.
Вариант кода функции API NUSE_Mailbox_Receive() (после проверки параметров) выбирается при помощи условной компиляции, в зависимости от того, активированы вызовы API для блокировки (приостановки задач) или нет. Мы рассмотрим оба варианта.
if (!NUSE_Mailbox_Status[mailbox]) /* mailbox empty */
{
return_value = NUSE_MAILBOX_EMPTY;
}
else
{ /* mailbox full */
*message = NUSE_Mailbox_Data[mailbox];
NUSE_Mailbox_Status[mailbox] = FALSE;
return_value = NUSE_SUCCESS;
}
do
{
if (NUSE_Mailbox_Status[mailbox]) /* mailbox full */
{
*message = NUSE_Mailbox_Data[mailbox];
NUSE_Mailbox_Status[mailbox] = FALSE;
if (NUSE_Mailbox_Blocking_Count[mailbox] != 0)
{
U8 index; /* check whether a task is blocked */
/* on this mailbox */
NUSE_Mailbox_Blocking_Count[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_SUCCESS;
NUSE_Wake_Task(index);
break;
}
}
}
return_value = NUSE_SUCCESS;
suspend = NUSE_NO_SUSPEND;
}
else /* mailbox empty */
{
if (suspend == NUSE_NO_SUSPEND)
{
return_value = NUSE_MAILBOX_EMPTY;
}
else
{ /* block task */
NUSE_Mailbox_Blocking_Count[mailbox]++;
NUSE_Suspend_Task(NUSE_Task_Active,
(mailbox << 4) | NUSE_MAILBOX_SUSPEND);
return_value =
NUSE_Task_Blocking_Return[NUSE_Task_Active];
if (return_value != NUSE_SUCCESS)
{
suspend = NUSE_NO_SUSPEND;
}
}
}
} while (suspend == NUSE_SUSPEND);
Код заключен в цикл do…while, который выполняется, пока параметр suspend имеет значение NUSE_SUSPEND.
Если почтовый ящик полон, хранимое сообщение возвращается, а статус почтового ящика меняется, чтобы показать, что он пуст. Выполняется проверка на приостановленные задачи (которые ожидают записи) на этом почтовом ящике. Если такие задачи есть, первая из них возобновляется. Переменной suspend присваивается значение NUSE_NO_SUSPEND, а вызов API завершается и возвращает NUSE_SUCCESS.
Если почтовый ящик пуст, а параметр suspend имеет значение NUSE_NO_SUSPEND, вызов API возвращает значение NUSE_MAILBOX_EMPTY. Если suspend имеет значение NUSE_SUSPEND, задача приостанавливается. Если при завершении вызова возвращаемое значение равно NUSE_SUCCESS, которое говорит о том, что задача была возобновлена, потому что сообщение было отправлено (а не о том, что почтовый ящик был сброшен), цикл начинается с начала.
В следующей статье будут рассмотрены дополнительные вызовы API, связанные с почтовыми ящиками, а также соответствующие структуры данных.
Об авторе: Колин Уоллс уже более тридцати лет работает в сфере электронной промышленности, значительную часть времени уделяя встроенному ПО. Сейчас он – инженер в области встроенного ПО в Mentor Embedded ( подразделение Mentor Graphics). Колин Уоллс часто выступает на конференциях и семинарах, автор многочисленных технических статей и двух книг по встроенному ПО. Живет в Великобритании. Профессиональный блог Колина: https://blogs.mentor.com/colinwalls/, e-mail: colin_walls@mentor.com