Вся правда об ОСРВ. Статья #17. Группы флагов событий: введение и базовые службы +12




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

Использование флагов событий


В 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


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

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

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

Вызов для установки флагов в Nucleus RTOS
Прототип служебного вызова:

STATUS NU_Set_Events(NU_EVENT_GROUP *group, UNSIGNED event_flags, OPTION operation);

Параметры:

group – указатель на предоставленный пользователем блок управления группой флагов событий;
event_flags – значение битовой маски группы флагов;
operation – выполняемая операция, NU_OR (для установки флагов) или NU_AND (для очистки флагов).

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

NU_SUCCESS – вызов был успешно завершен;
NU_INVALID_GROUP – некорректный указатель на группу флагов событий;
NU_INVALID_OPERATION – указанная операция отличается от NU_OR и NU_AND.

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

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

STATUS NUSE_Event_Group_Set(NUSE_EVENT_GROUP group, U8 event_flags, OPTION operation);

Параметры:

group – индекс (ID) группы событий, флаги которой устанавливаются/очищаются;
event_flags – значение битовой макси группы флагов;
operation – выполняемая операция, NUSE_OR (для установки флагов) или NUSE_AND (для очистки флагов).

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

NUSE_SUCCESS – вызов был успешно завершен;
NUSE_INVALID_GROUP – некорректный индекс группы флагов событий;
NUSE_INVALID_OPERATION – указанная операция отличается от NUSE_OR и NUSE_AND.

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

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

Вызов для чтения флагов в Nucleus RTOS
Прототип служебного вызова:

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).

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

NU_SUCCESS – вызов был успешно завершен;
NU_NOT_PRESENT – указанная операция не вернула события (ни одного события в случае NU_OR и не все события в случае NU_AND);
NU_INVALID_GROUP – некорректный указатель на группу флагов событий;
NU_INVALID_OPERATION – указанная операция была некорректна;
NU_INVALID_POINTER – нулевой указатель на хранилище флагов событий (NULL);
NU_INVALID_SUSPEND – попытка выполнить приостановку из не связанного с задачей потока;
NU_TIMEOUT – требуемая комбинация флагов событий не установилась даже после указанного таймаута;
NU_GROUP_DELETED – группа флагов событий была удалена, в то время как задача была приостановлена.

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

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

STATUS NUSE_Event_Group_Retrieve(NUSE_EVENT_GROUP group, U8 requested_events, OPTION operation, U8 *retrieved_events, U8 suspend);

Параметры:

group – индекс (ID) считываемой группы флагов событий;
requested_events – битовая маска, определяющая считываемые флаги;
operation – спецификация, указывающая на количество необходимых флагов: NUSE OR (некоторые флаги) или NUSE AND (все флаги);
retrieved_events – указатель на хранилище для действительных значений считанных флагов событий (при операции NUSE_AND это будет то же, что передано в параметре requested_events);
suspend – спецификация для приостановки задачи, может принимать значения NUSE_NO_SUSPEND или NUSE_SUSPEND.

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

NUSE_SUCCESS – вызов был успешно завершен;
NUSE_NOT_PRESENT – указанная операция не вернула события (ни одного события в случае NUSE_OR и не все события в случае NUSE_AND);
NUSE_INVALID_GROUP – некорректный индекс группы флагов событий;
NUSE_INVALID_OPERATION – указанная операция отличается от NUSE_OR или NUSE_AND;
NUSE_INVALID_POINTER – нулевой указатель на хранилище считанных флагов событий (NULL);
NUSE_INVALID_SUSPEND – попытка выполнить приостановку из не связанного с задачей потока или при отключенной поддержке блокирующих API вызовов.

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

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




К сожалению, не доступен сервер mySQL