[Перевод] Вся правда об ОСРВ. Статья #16. Сигналы

pxwmt4bbchih_uwlzsmgubcn1cy.jpeg

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

Использование сигналов


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

Любая задача может устанавливать сигналы другой задачи. Считывать сигналы может только задача-владелец сигнала. В процессе чтения сигналы сбрасываются. Задачи не могут читать или сбрасывать сигналы других задач.

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

Настройка сигналов


Как и в большинстве объектов Nucleus SE, настройка сигналов определяется директивами #define в nuse_config.h. Основным параметром является NUSE_SIGNAL_SUPPORT, который активирует поддержку функциональности (для всех задач в приложении). Вопрос об указании числа сигналов не стоит: на каждую задачу выделяется по 8 флагов.

Установка этого разрешающего параметра служит главным активатором сигналов. Это обеспечивает строго определенную структуру данных, имеющую соответствующий размер. Кроме того, этот параметр активирует настройки API.

Активация вызовов API


Каждая функция API (служебный вызов) в Nucleus SE активируется директивой #define в nuse_config.h. Для сигналов к ним относятся:

NUSE_SIGNALS_SEND
NUSE_SIGNALS_RECEIVE


По умолчанию, им присвоено значение FALSE, отключая, таким образом, каждый служебный вызов и препятствуя включению реализующего их кода. Для настройки сигналов в приложении необходимо выбрать нужные вызовы API и присвоить соответствующим директивам значение TRUE.

Ниже приведена выдержка из файла nuse_config.h по умолчанию:

#define NUSE_SIGNAL_SUPPORT  FALSE   /* Enables support for signals */
#define NUSE_SIGNALS_SEND    FALSE   /* Service call enabler */
#define NUSE_SIGNALS_RECEIVE FALSE   /* Service call enabler */


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

Служебные вызовы сигналов


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

  • Отправка сигналов заданной задаче. В Nucleus SE реализовано в функции NUSE_Signals_Send ().
  • Прием сигналов. В Nucleus SE реализовано в функции NUSE_Signals_Receive ().
  • Регистрация обработчика сигналов. Не реализовано в Nucleus SE.
  • Включение/отключение (управление) сигналами. Не реализовано в Nucleus SE.


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

Службы отправки и принятия сигналов


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

Так как сигналы являются битами, их лучше всего визуализировать как двоичные числа. Так как стандарт С исторически не поддерживает представление двоичных констант (только восьмеричных и шестнадцатеричных), Nucleus SE имеет полезный заголовочный файл nuse_binary.h, который содержит символы #define вида b01010101 для всех 256 8-битных значений. Ниже приведена выдержка из файла nuse_binary.h:

#define b00000000 ((U8) 0x00)
#define b00000001 ((U8) 0x01)
#define b00000010 ((U8) 0x02)
#define b00000011 ((U8) 0x03)
#define b00000100 ((U8) 0x04)
#define b00000101 ((U8) 0x05)


Отправка сигналов


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

Вызов для отправки сигналов в Nucleus RTOS
Прототип служебного вызова:
STATUS NU_Send_Signals (NU_TASK *task, UNSIGNED signals);

Параметры:

task — указатель на блок управления задачи, которому принадлежат устанавливаемые сигнальные флаги;
signals — значение устанавливаемых сигнальных флагов.

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

NU_SUCCESS — вызов был успешно завершен;
NU_INVALID_TASK — некорректный указатель на задачу;

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

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

STATUS_NUSE_Signals_Send (NUSE_TASK task, U8 signals);

Параметры:

task — индекс (ID) задачи, которой принадлежат устанавливаемые сигнальные флаги;
signals — значение устанавливаемых сигнальных флагов.

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

NUSE_SUCCESS — служебный вызов был успешно завершен;
NUSE_INVALID_TASK — некорректный индекс задачи.

Реализация отправки сигналов в Nucleus SE
Ниже представлен полный код функции NUSE_Signals_Send ():

STATUS NUSE_Signals_Send(NUSE_TASK task, U8 signals)
{
        #if NUSE_API_PARAMETER_CHECKING
             if (task >= NUSE_TASK_NUMBER)
             {
                  return NUSE_INVALID_TASK;
              }
        #endif
        NUSE_CS_Enter();
        NUSE_Task_Signal_Flags[task] |= signals;
        NUSE_CS_Exit();
        return NUSE_SUCCESS;
}


Код очень прост. После любой проверки параметров значения сигналов проходят через операцию ИЛИ на сигнальные флаги указанной задачи. Блокировка задач на сигналы не влияет.

Прием сигналов


Задача может читать только собственный набор сигнальных флагов. В процессе чтения значения флагов сбрасываются.

Вызов для приема сигналов в Nucleus RTOS
Прототип служебного вызова:
UNSIGNED NU_Receive_Signals (VOID);

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

Возвращаемое значение:
Значения сигнальных флагов.

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

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

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

Возвращаемое значение:
Значения сигнальных флагов.

Реализация приема сигналов Nucleus SE
Ниже приведен полный код функции NUSE_Signals_Receive ():

U8 NUSE_Signals_Receive(void)
{
        U8 signals;
        NUSE_CS_Enter();
        Signals = NUSE_Task_Signal_Flags[NUSE_Task_Active];
        NUSE_Task_Signal_Flags[NUSE_Task_Active] = 0;
        NUSE_CS_Exit();
        return signals;
}


Код очень прост. Значение флагов копируется, начальное значение сбрасывается, а копия возвращается функцией API. Блокировка задач не влияет на сигналы.

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


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

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

Структура данных, размещаемых в ОЗУ


Структура данных:
NUSE_Task_Signal_Flags[] — массив типа U8 с одной записью на каждую сконфигурированную задачу, в этом массиве хранятся сигнальные флаги.

Эта структура данных инициализируется нулями функцией NUSE_Init_Task () при загрузке Nucleus SE.

Структура данных, размещаемых в ПЗУ


У сигналов отсутствуют структуры данных в ПЗУ.

Объемы памяти для хранения данных сигналов


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

Объем данных в ОЗУ для всех сигналов в приложении равен 0.

Объем памяти для хранения данных в ПЗУ (в байтах) для всех сигналов в приложении равен количеству сконфигурированных задач (NUSE_TASK_NUMBER). Но фактически, эти данные принадлежат задачам и описаны в предыдущей статье о задачах.

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


Два служебных вызова API сигналов из Nucleus RTOS не реализованы в Nucleus SE:

Регистрация обработчика сигналов


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

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

Параметры:
signal_handler — функция, которая должна вызываться при приеме сигналов

Возвращаемое значение:
NU_SUCCESS — вызов был успешно завершен;
NU_INVALID_POINTER — нулевой указатель на обработчик сигнала (NULL)

Управление (активирование/деактивирование) сигналами


Эта служба активирует и/или деактивирует сигналы для текущей задачи. Для каждой задачи доступно 32 сигнала. Каждый сигнал представлен битом в signal_enable_mask. Добавление бита в signal_enable_mask включает соответствующий сигнал, а удаление бита его отключает.

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

Параметры:
enable_signal_mask — паттерн бит, представляющий корректные сигналы.

Возвращаемое значение:
Маска активирования/деактивирования предыдущего сигнала.

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


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

Обработчики сигналов


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

Доступность сигналов и их количество


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

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


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

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

© Habrahabr.ru