Группы флагов событий уже упоминались в одной из предыдущих статей (#5). Так же, как и сигналы они предоставляют малозатратный и гибкий способ передачи простых сообщений между задачами.
В Nucleus SE флаги событий определяются на этапе сборки. Максимальное количество групп флагов событий в приложении – 16. Если группы флагов событий не определены, то код, относящийся к структурам данных и служебным вызовам групп флагов событий, не будет включен в приложение.
Группа флагов событий – набор из восьми битовых флагов, доступ к которым регулируется таким образом, чтобы одним флагом могли безопасно пользоваться несколько задач. Одна задача может установить или очистить любую комбинацию флагов событий. Другая задача может прочитать группу флагов в любое время, а также может дождаться определенной последовательности флагов (по опросу или с приостановкой).
Как и в большинстве объектов Nucleus SE, настройка групп флагов событий задается директивами #define в nuse_config.h. Основным параметром является NUSE_EVENT_GROUP_NUMBER, который определяет, сколько групп флагов событий будет определено в приложении. По умолчанию, этот параметр установлен в 0 (т.е. группы флагов событий не используются) и может иметь любое значение вплоть до 16. Некорректное значение приведет к ошибке при компиляции, которая будет сгенерирована проверкой в nuse_config_check.h (она включается nuse_config.c, а значит компилируется вместе с этим модулем), в результате сработает директива #error. Выбор ненулевого значения служит главным активатором групп флагов событий. Этот параметр используется при определении структур данных и от его значения зависит их размер (более подробно об этом в следующих статьях). Кроме того, ненулевое значение активирует настройки API.
Каждая функция API (служебный вызов) в Nucleus SE активируется директивой #define в nuse_config.h. Для групп флагов событий к ним относятся:
NUSE_EVENT_GROUP_SET
NUSE_EVENT_GROUP_RETRIEVE
NUSE_EVENT_GROUP_INFORMATION
NUSE_EVENT_GROUP_COUNT
По умолчанию, им присвоено значение FALSE, таким образом, отключая каждый служебный вызов и блокируя включение реализующего их кода. Для настройки групп флагов событий нужно выбрать необходимые вызовы API и присвоить соответствующим директивам значение TRUE.
Ниже приведена выдержка из файла nuse_config.h по умолчанию.
#define NUSE_EVENT_GROUP_NUMBER 0 /* Number of event groups
in the system - 0-16 */
#define NUSE_EVENT_GROUP_SET FALSE /* Service call enabler */
#define NUSE_EVENT_GROUP_RETRIEVE FALSE /* Service call enabler */
#define NUSE_EVENT_GROUP_INFORMATION FALSE /* Service call enabler */
#define NUSE_EVENT_GROUP_COUNT FALSE /* Service call enabler */
Активированная функция API при отсутствии в приложении групп флагов событий приведет к ошибке компиляции (кроме NUSE_Event_Group_Count(), которая разрешена всегда). Если ваш код использует вызов API, который не был активирован, возникнет ошибка компоновки, так как реализующий код не был включен в приложение.
Реализация каждого из этих служебных вызовов подробно рассмотрена ниже.
Стоит заметить, что функции сброса нет ни в Nucleus RTOS, ни в Nucleus SE. Это сделано намерено. Функция сброса подразумевает преобладание особого состояния флагов. Для групп флагов событий единственным «особым» состоянием является обнуление всех флагов, которое может быть выполнено при помощи NUSE_Event_Group_Set().
Фундаментальные операции, которые могут быть выполнены над группой флагов событий – установка значения одного и более флагов, а также считывание текущих значений флагов. Nucleus RTOS и Nucleus SE предоставляют четыре базовых вызова API для этих операций.
Поскольку флаги событий являются битами, их лучше всего визуализировать в виде двоичных чисел. Так как стандарт С исторически не поддерживает представление двоичных констант (только восьмеричных и шестнадцатеричных), Nucleus SE имеет полезный заголовочный файл nuse_binary.h, который содержит символы #define вида b01010101 для всех 256 8-битных значений.
Служебный вызов Nucleus RTOS API для установки флагов очень гибкий и позволяет устанавливать и очищать значения флагов при помощи операций И и ИЛИ. Nucleus SE предоставляет аналогичный функционал, но приостановка задач является опциональной.
NUSE_CS_Enter();
if (operation == NUSE_OR)
{
NUSE_Event_Group_Data[group] |= event_flags;
}
else /* NUSE_AND */
{
NUSE_Event_Group_Data[group] &= event_flags;
}
Битовая маска event_flags накладывается (при помощи операции И или ИЛИ) на значение выбранной группы флагов событий.
Оставшийся код включается только при активированной блокировке задач:
#if NUSE_BLOCKING_ENABLE
while (NUSE_Event_Group_Blocking_Count[group] != 0)
{
U8 index; /* check whether any tasks are blocked */
/* on this event group */
for (index=0; index<NUSE_TASK_NUMBER; index++)
{
if ((LONIB(NUSE_Task_Status[index]) ==
NUSE_EVENT_SUSPEND)
&& (HINIB(NUSE_Task_Status[index]) == group))
{
NUSE_Task_Blocking_Return[index] = NUSE_SUCCESS;
NUSE_Task_Status[index] = NUSE_READY;
break;
}
}
NUSE_Event_Group_Blocking_Count[group]--;
}
#if NUSE_SCHEDULER_TYPE == NUSE_PRIORITY_SCHEDULER
NUSE_Reschedule(NUSE_NO_TASK);
#endif
#endif
NUSE_CS_Exit();
return NUSE_SUCCESS;
Если какие-либо задачи приостановлены (для чтения) из этой группы флагов, они возобновляются. Когда перед ними открывается возможность продолжить выполнение (это зависит от планировщика), они могут определить, удовлетворены условия их возобновления или нет (см. чтение флагов событий).
Служебные вызовы Nucleus RTOS API для чтения очень гибкие и позволяют приостанавливать задачи на неопределенное время либо с определенным таймаутом, если операция не может быть выполнена немедленно (например, если вы попытаетесь считать определенную последовательность флагов событий, которая не представляет текущее состояние). Nucleus SE предоставляет те же функции, только приостановка задачи опциональна, а таймаут не реализован.
STATUS NU_Retrieve_Events(NU_EVENT_GROUP *group, UNSIGNED requested_events, OPTION operation, UNSIGNED *retrieved_events, UNSIGNED suspend);
group – указатель на предоставленный пользователем блок управления группой флагов событий;
requested_events – битовая маска, определяющая считываемые флаги;
operation – доступны четыре операции: NU_AND, NU_AND_CONSUME, NU_OR и NU_OR_CONSUME. Операции NU_AND и NU_AND_CONSUME указывают, что необходимы все запрашиваемые флаги. Операции NU_OR и NU_OR_CONSUME указывают, что достаточно одного или нескольких из запрошенных флагов. Параметр CONSUME автоматически очищает существующие флаги после успешного запроса;
retrieved_events – указатель на хранилище для значений считываемых флагов событий;
suspend – спецификация для приостановки задач; может принимать значения NU_NO_SUSPEND или NU_SUSPEND, либо значение таймаута в тактах системного таймера (от 1 до 4,294,967,293).
STATUS NUSE_Event_Group_Retrieve(NUSE_EVENT_GROUP group, U8 requested_events, OPTION operation, U8 *retrieved_events, U8 suspend);
Вариант кода функции API NUSE_Event_Group_Retrieve() (после проверки параметров) выбирается во время условной компиляции в зависимости от того, активирована поддержка API вызовов блокировки (приостановки) задач или нет. Рассмотрим эти два варианта отдельно.
temp_events = NUSE_Event_Group_Data[group] & requested_events;
if (operation == NUSE_OR)
{
if (temp_events != 0)
{
return_value = NUSE_SUCCESS;
}
else
{
return_value = NUSE_NOT_PRESENT;
}
}
else /* operation == NUSE_AND */
{
if (temp_events == requested_events)
{
return_value = NUSE_SUCCESS;
}
else
{
return_value = NUSE_NOT_PRESENT;
}
}
Требуемые флаги событий выбираются из указанной группы флагов событий. Значение сравнивается с требуемыми событиями, учитывая операцию И/ИЛИ, а также возвращаемый результат и непосредственные значения запрашиваемых флагов.
Если блокировка задач активирована, код становится более сложным:
do
{
temp_events = NUSE_Event_Group_Data[group] & requested_events;
if (operation == NUSE_OR)
{
if (temp_events != 0)
{
return_value = NUSE_SUCCESS;
}
else
{
return_value = NUSE_NOT_PRESENT;
}
}
else /* operation == NUSE_AND */
{
if (temp_events == requested_events)
{
return_value = NUSE_SUCCESS;
}
else
{
return_value = NUSE_NOT_PRESENT;
}
}
if (return_value == NUSE_SUCCESS)
{
suspend = NUSE_NO_SUSPEND;
}
else
{
if (suspend == NUSE_SUSPEND) /* block task */
{
NUSE_Event_Group_Blocking_Count[group]++;
NUSE_Suspend_Task(NUSE_Task_Active, (group << 4) |
NUSE_EVENT_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_NOT_PRESENT. Если параметру suspend было присвоено значение NUSE_SUSPEND, задача приостанавливается. При возврате (когда задача возобновляется), если возвращаемое значение NUSE_SUCCESS, указывающее, что задача была возобновлена, потому что флаги событий в этой группе были установлены или очищены, — цикл начинается с начала, флаги считываются и проверяются. Поскольку не существует функции API для сброса групп флагов событий, это является единственной причиной для возобновления задачи, но процесс проверки NUSE_Task_Blocking_Return[] был оставлен в системе для совместимости управления блокировкой с другими типами объектов.
В следующей статье будут описаны дополнительные вызовы API, связанные с группами флагов событий, а также их структуры данных.
Об авторе: Колин Уоллс уже более тридцати лет работает в сфере электронной промышленности, значительную часть времени уделяя встроенному ПО. Сейчас он – инженер в области встроенного ПО в Mentor Embedded ( подразделение Mentor Graphics). Колин Уоллс часто выступает на конференциях и семинарах, автор многочисленных технических статей и двух книг по встроенному ПО. Живет в Великобритании. Профессиональный блог Колина: https://blogs.mentor.com/colinwalls/, e-mail: colin_walls@mentor.com