+7 (812) 670-9095
Обратная связьEnglish
Главная → Статьи → Системное ПО → Статья #19. Семафоры: введение и базовые службы
Версия для печати

Статья #19. Семафоры: введение и базовые службы

12 ноября 2018

Семафоры уже упоминались в одной из предыдущих статей (#5). Основная задача семафоров – управление доступом к ресурсам.





Статья #19. Семафоры: введение и базовые службы



Использование семафоров


В Nucleus SE семафоры определяются на этапе сборки. Приложение может иметь до 16 семафоров. Если семафоры не заданы, код служебных вызовов и структур данных не включается в приложение.

Семафор представляет из себя счетчик типа U8, доступ к которому управляется таким образом, чтобы им могли пользоваться несколько задач. Задача может уменьшить значение счетчика семафора (захватить) или увеличить его (освободить). Попытка захватить семафор с нулевым значением может привести к ошибке или к приостановке задачи, в зависимости от выбранных параметров вызова API и конфигурации Nucleus SE.


Настройка семафоров

Количество семафоров


Как и в большинстве объектов Nucleus SE, настройка семафоров определяется директивами #define в nuse_config.h. Основным параметром является NUSE_SEMAPHORE_NUMBER, определяющий количество семафоров в приложении. По умолчанию параметр установлен в 0 (семафоры в приложении не используются) и может принимать любые значения вплоть до 16. Некорректное значение приведет к ошибке при компиляции, которая будет сгенерирована проверкой в nuse_config_check.h (этот файл включен в nuse_config.c, а значит компилируется вместе с этим модулем), в результате сработает директива #error.

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


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


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


NUSE_SEMAPHORE_OBTAIN
NUSE_SEMAPHORE_RELEASE
NUSE_SEMAPHORE_RESET
NUSE_SEMAPHORE_INFORMATION
NUSE_SEMAPHORE_COUNT

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

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


#define NUSE_SEMAPHORE_NUMBER       0         /* Number of semaphores in
                                                 the system - 0-16 */
#define NUSE_SEMAPHORE_OBTAIN       FALSE     /* Service call enabler */
#define NUSE_SEMAPHORE_RELEASE      FALSE     /* Service call enabler */
#define NUSE_SEMAPHORE_RESET        FALSE     /* Service call enabler */
#define NUSE_SEMAPHORE_INFORMATION  FALSE     /* Service call enabler */
#define NUSE_SEMAPHORE_COUNT        FALSE     /* Service call enabler */


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


Служебные вызовы семафоров


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

  • Захват семафора. В Nucleus SE реализовано в функции NUSE_Semaphore_Obtain().
  • Освобождение семафора. В Nucleus SE реализовано в функции NUSE_Semaphore_Release().
  • Возврат семафора в неиспользуемое состояние с освобождением всех приостановленных задач (перезагрузка). В Nucleus SE реализовано в NUSE_Semaphore_Reset().
  • Предоставление информации о конкретном семафоре. В Nucleus SE реализовано в NUSE_Semaphore_Information().
  • Возвращения количества сконфигурированных семафоров в приложении. В Nucleus SE реализовано в NUSE_Semaphore_Count().
  • Добавление нового семафора в приложение. В Nucleus SE не реализовано.
  • Удаление семафора из приложения. В Nucleus SE не реализовано.
  • Возвращение указателей на все семафоры. В Nucleus SE не реализовано.

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

Служебные вызовы захвата и освобождения семафоров


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


Захват семафора


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


Вызов для захвата семафора в Nucleus RTOS
Прототип служебного вызова:
STATUS NU_Obtain_Semaphore(NU_SEMAPHORE *semaphore, UNSIGNED suspend);

Параметры:

semaphore – указатель на предоставленный пользователем блок управления семафором;
suspend – параметр приостановки задачи, может принимать значения NU_NO_SUSPEND или NU_SUSPEND, а также значение таймаута.


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

NU_SUCCESS – вызов был успешно завершен;
NU_UNAVAILABLE – семафор имел нулевое значение;
NU_INVALID_SEMAPHORE – некорректный указатель на семафор;
NU_INVALID_SUSPEND – попытка выполнить приостановку из не связанного с задачей потока;
NU_SEMAPHORE_WAS_RESET – семафор был сброшен, пока задача была приостановлена.

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

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

STATUS NUSE_Semaphore_Obtain(NUSE_SEMAPHORE semaphore, U8 suspend);

Параметры:
semaphore – индекс (ID) используемого семафора;
suspend – параметр приостановки задачи, может принимать значение NUSE_NO_SUSPEND или NUSE_SUSPEND.

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

NUSE_SUCCESS – вызов был успешно завершен;
NUSE_UNAVAILABLE – семафор имел нулевое значение;
NUSE_INVALID_SEMAPHORE – некорректный индекс семафора;
NUSE_INVALID_SUSPEND – попытка выполнить приостановку из не связанного с задачей потока или при отключенной функциональности блокировки API;
NUSE_SEMAPHORE_WAS_RESET – семафор был сброшен, пока задача была приостановлена;


Реализация захвата семафоров в Nucleus SE

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


Если блокировка не активирована, логика этого вызова API довольно проста:

if (NUSE_Semaphore_Counter[semaphore] != 0) /* semaphore available */
{
    NUSE_Semaphore_Counter[semaphore]--;
    return_value = NUSE_SUCCESS;
}
else                                        /* semaphore unavailable */
{
    return_value = NUSE_UNAVAILABLE;
}


Значение счетчика семафора проверяется, и, если оно не равно нулю, уменьшается.

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

do
{
    if (NUSE_Semaphore_Counter[semaphore] != 0) /* semaphore available */
    {
        NUSE_Semaphore_Counter[semaphore]--;
        return_value = NUSE_SUCCESS;
        suspend = NUSE_NO_SUSPEND;
    }
    else                                    /* semaphore unavailable */
    {
        if (suspend == NUSE_NO_SUSPEND)
        {
            return_value = NUSE_UNAVAILABLE;
        }
        else
        {                                   /* block task */
            NUSE_Semaphore_Blocking_Count[semaphore]++;
            NUSE_Suspend_Task(NUSE_Task_Active,
                              semaphore << 4) | NUSE_SEMAPHORE_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_SUCESS.

Если семафор имеет нулевое значение, а переменной suspend присвоено значение NUSE_NO_SUSPEND, вызов API возвращает значение NUSE_UNAVAILABLE. Если приостановке было присвоено значение NUSE_SUSPEND, задача приостанавливается. После завершения вызова (например, когда задача возобновляется), если возвращаемое значение равно NUSE_SUCCESS (которое говорит о том, что задача была возобновлена после освобождения семафора, а не после сброса), цикл начинается с начала.


Освобождение семафора


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


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

STATUS NU_Release_Semaphore(NU_SEMAPHORE *semaphore);

Параметры:
semaphore – указатель на предоставленный пользователем блок управления семафором.

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

NU_SUCCESS – вызов был успешно завершен;
NU_INVALID_SEMAPHORE – некорректный указатель на семафор.

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

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

STATUS NUSE_Semaphore_Release(NUSE_SEMAPHORE semaphore);

Параметры:
semaphore – индекс (ID) освобождаемого семафора.

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

NUSE_SUCCESS – вызов был успешно завершен;
NUSE_INVALID_SEMAPHORE – некорректный индекс семафора;
NUSE_UNAVAILABLE – семафор имеет значение 255, и его нельзя увеличить.

Реализация освобождения семафоров в Nucleus SE

Код функции NUSE_Semaphore_Release() (после проверки параметров) общий, вне зависимости от того, активирована блокировка задач или нет. Значение счетчика семафора проверяется, и, если оно меньше 255, увеличивается.

Дальнейший код выбирается при помощи условной компиляции, если поддержка вызовов API блокировки (приостановки задач) активирована:


NUSE_CS_Enter();
if (NUSE_Semaphore_Counter[semaphore] < 255)
{
    NUSE_Semaphore_Counter[semaphore]++;
    return_value = NUSE_SUCCESS;
    #if NUSE_BLOCKING_ENABLE
        if (NUSE_Semaphore_Blocking_Count[semaphore] != 0)
        {
            U8 index;       /* check whether a task is blocked */
                            /* on this semaphore */
            NUSE_Semaphore_Blocking_Count[semaphore]--;
            for (index=0; index<NUSE_TASK_NUMBER; index++)
            {
                if ((LONIB(NUSE_Task_Status[index]) ==
                     NUSE_SEMAPHORE_SUSPEND)
                    && (HINIB(NUSE_Task_Status[index]) ==
                        semaphore))
                {
                    NUSE_Task_Blocking_Return[index] =
                        NUSE_SUCCESS;
                    NUSE_Wake_Task(index);
                    break;
                }
            }
        }
    #endif
}
else
{
    return_value = NUSE_UNAVAILABLE;
}
NUSE_CS_Exit();
return return_value;


Если на данном семафоре приостановлены какие-либо задачи, первая из них возобновляется.

В следующей статье будут описаны дополнительные вызовы API, связанные с семафорами, и их структуры данных.


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


Источник: https://www.embedded.com/design/operating-systems/4460559/Semaphores--introduction-and-basic-services

Теги: ОСРВ, RTOS, семафоры, захват семафора, освобождение семафора, управление доступом к ресурсам, микроконтроллеры