Семафоры уже упоминались в одной из предыдущих статей (#5). Основная задача семафоров – управление доступом к ресурсам.
В 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 (служебный вызов) в 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 RTOS и Nucleus SE предоставляют два базовых вызова API для этих операций.
Служебный вызов Nucleus RTOS для захвата семафора очень гибкий и позволяет приостанавливать задачи неявным образом или с определенным таймаутом, если операция не может быть выполнена в данный момент, например, если вы попытаетесь захватить семафор с нулевым значением. Nucleus SE предоставляет те же функции, только приостановка задач опциональна, а таймаут не реализован.
semaphore – указатель на предоставленный пользователем блок управления семафором;
suspend – параметр приостановки задачи, может принимать значения NU_NO_SUSPEND или NU_SUSPEND, а также значение таймаута.
NUSE_SUCCESS – вызов был успешно завершен;
NUSE_UNAVAILABLE – семафор имел нулевое значение;
NUSE_INVALID_SEMAPHORE – некорректный индекс семафора;
NUSE_INVALID_SUSPEND – попытка выполнить приостановку из не связанного с задачей потока или при отключенной функциональности блокировки API;
NUSE_SEMAPHORE_WAS_RESET – семафор был сброшен, пока задача была приостановлена;
Вариант кода функции NUSE_Semaphore_Obtain() (после проверки параметров) выбирается при помощи условной компиляции в зависимости от того, активирована поддержка блокировки (приостановки) задач или нет. Рассмотрим оба варианта.
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 предоставляет те же возможности, только с дополнительной проверкой на переполнение.
Код функции 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