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

Предыдущие статьи серии:
Статья #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 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


Каждая функция 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 SE реализовано в функции NUSE_Mailbox_Send().
  • Чтение сообщения из почтового ящика. В Nucleus SE реализовано в функции NUSE_Mailbox_Receive().
  • Восстановление почтового ящика в неиспользованное состояние с освобождением всех приостановленных задач (сброс). В Nucleus SE реализовано в NUSE_Mailbox_Reset().
  • Предоставление информации о конкретном почтовом ящике. В Nucleus SE реализовано в NUSE_Mailbox_Information().
  • Возврат количества сконфигурированных на данный момент в приложении почтовых ящиков. В Nucleus SE реализовано в NUSE_Mailbox_Count().
  • Добавление нового почтового ящика (создание). В Nucleus SE не реализовано.
  • Удаление почтового ящика. В Nucleus SE не реализовано.
  • Возврат указателей на все почтовые ящики в приложении. В Nucleus SE не реализовано.
  • Отправка сообщения всем приостановленным на почтовом ящике задачам (трансляция). В Nucleus SE не реализовано.

Рассмотрим подробно реализацию каждого служебного вызова.

Служебные вызовы чтения и записи почтовых ящиков


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

Запись в почтовый ящик


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

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

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

Параметры:
mailbox – указатель на почтовый ящик;
message – указатель на отправляемое сообщение, состоящие из четырех элементов типа unsigned;
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_WAS_RESET – почтовый ящик был сброшен, пока задача была приостановлена.

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

Прототип служебного вызова:
STATUS NUSE_Mailbox_Send(NUSE_MAILBOX mailbox, ADDR *message, U8 suspend);

Параметры:
mailbox – индекс (ID) почтового ящика;
message – указатель на отправляемое сообщение, представляет из себя одну переменную типа ADDR;
suspend – спецификация приостановки задачи, может принимать значения NUSE_NO_SUSPEND или NUSE_SUSPEND.

Возвращаемое значение:
NUSE_SUCCESS – вызов был успешно завершен;
NUSE_INVALID_MAILBOX – некорректный индекс почтового ящика;
NUSE_INVALID_POINTER – нулевой указатель на сообщение (NULL);
NUSE_INVALID_SUSPEND – попытка приостановки из не связанного с задачей потока или при деактивированной функциональности блокировки вызовов API;
NUSE_MAILBOX_FULL – почтовый ящик полон, а тип приостановки не указан;
NUSE_MAILBOX_WAS_RESET – почтовый ящик был сброшен, пока задача была приостановлена.

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

Если блокировка задач отключена, логика этого вызова 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;
}

Сообщение хранится в соответствующем элементе NUSE_Mailbox_Data[], а почтовый ящик помечается как используемый.

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

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

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

Параметры:
mailbox – указатель на блок управления почтовым ящиком, предоставленный пользователем;
message – указатель на хранилище для принимаемого сообщения, которое имеет размер, равный четырем переменным типа unsigned;
suspend – спецификация приостановки задачи, может принимать значения NUSE_NO_SUSPEND, NUSE_SUSPEND или значение таймаута.

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

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

Прототип служебного вызова:
STATUS NUSE_Mailbox_Receive(NUSE_MAILBOX mailbox, ADDR *message, U8 suspend);

Параметры:
mailbox – индекс (ID) почтового ящика;
message – указатель на хранилище принимаемого сообщения, представляет из себя одну переменную типа ADDR;
suspend – спецификация приостановки задачи, может принимать значения NUSE_NO_SUSPEND или NUSE_SUSPEND.

Возвращаемое значение:
NUSE_SUCCESS – вызов был успешно завершен;
NUSE_INVALID_MAILBOX – некорректный индекс почтового ящика;
NUSE_INDALID_POINTER – нулевой указатель на сообщение (NULL);
NUSE_INVALID_SUSPEND – попытка приостановки из некорректного потока или при отключенных вызовах API для блокировки задач;
NUSE_MAILBOX_EMPTY – почтовый ящик пуст, а тип приостановки задач не указан;
NUSE_MAILBOX_WAS_RESET – почтовый ящик был сброшен, пока задача была приостановлена.

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

Если блокировка задач отключена, логика этого вызова 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;
}

Чтение сообщения происходит из соответствующего элемента NUSE_Mailbox_Data[], а почтовый ящик помечается как пустой.

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

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). Колин Уоллс часто выступает на конференциях и семинарах, автор многочисленных технических статей и двух книг по встроенному ПО. Живет в Великобритании. Профессиональный блог Колина, e-mail: colin_walls@mentor.com.

Комментарии (0)