Продолжаем обзор семафоров и рассмотрим четыре вызова API, предоставляющие функционал, связанный с семафорами: сброс семафора, получение информации о семафоре, получение количества семафоров в приложении и получение указателей на все семафоры в приложении.
Nucleus RTOS имеет четыре вызова API, предоставляющие функционал, связанный с семафорами: сброс семафора, получение информации о семафоре, получение количества семафоров в приложении и получение указателей на все семафоры в приложении. Первые три из них реализованы в Nucleus SE.
Этот вызов API сбрасывает семафор в его начальное, неиспользуемое состояние. Данная функция API необычна по сравнению с функциями других объектов ядра, так как несмотря на то, что она выполняет сброс, она не просто устанавливает счетчик в начальное значение, а в вызове передается новое начальное значение счетчика. Любая задача, которая была приостановлена на семафоре, возобновляется и возвращает код NUSE_SEMAPHORE_WAS_RESET в Nucleus SE, а в Nucleus RTOS – NU_SEMAPHORE_RESET.
Основная задача функции API NUSE_Semaphore_Reset() – установить соответствующий элемент NUSE_Semaphore_Counter[] в указанное значение (после проверки параметров).
while (NUSE_Semaphore_Blocking_Count[semaphore] != 0)
{
U8 index; /* check whether any tasks are blocked */
/* on this 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_SEMAPHORE_WAS_RESET;
NUSE_Task_Status[index] = NUSE_READY;
break;
}
}
NUSE_Semaphore_Blocking_Count[semaphore]--;
}
#if NUSE_SCHEDULER_TYPE == NUSE_PRIORITY_SCHEDULER
NUSE_Reschedule(NUSE_NO_TASK);
#endif
Каждая приостановленная на семафоре задача помечается как «готовая», а код приостановки задачи возвращает NUSE_SEMAPHORE_WAS_RESET. После того, как этот процесс завершен, если используется планировщик Priority, вызов инициализирует NUSE_Reschedule(), так как одна или несколько задач с более высоким приоритетом могли перейти в готовое состояние и ожидают возобновления.
Этот служебный вызов возвращает информацию о семафоре. Реализация этого вызова в Nucleus SE отличается от Nucleus RTOS тем, что возвращается меньше информации, т. к. именование объектов и порядок приостановки не поддерживается, а сама приостановка задач может быть отключена.
STATUS NU_Semaphore_Information(NU_SEMAPHORE *semaphore, CHAR *name, UNSIGNED *current_count, OPTION *suspend_type, UNSIGNED *tasks_waiting, NU_TASK **first_task);
semaphore – указатель на блок управления семафора, о котором требуется предоставить информацию;
name – указатель на 8-символьное имя семафора, с включенным в эту область нулевым терминирующим байтом;
current_count – указатель на переменную, которая примет текущее значение счетчика семафора;
suspend_type – указатель на переменную, которая примет тип приостановки задачи, может принимать значения NU_FIFO и NU_PRIORITY;
task_waiting – указатель на переменную, которая примет количество приостановленных задач в семафоре;
first_task – указатель на переменную типа NU_TASK, которая примет указатель на блок управления первой приостановленной задачи.
STATUS NUSE_Semaphore_Information(NUSE_SEMAPHORE semaphore, U8 *current_count, U8 *tasks_waiting, NUSE_TASK *first_task);
semaphore – индекс семафора, о котором требуется предоставить информацию;
current_count – указатель на переменную, которая примет текущее значение счетчика семафора;
tasks_waiting – указатель на переменную, которая примет количество приостановленных на этом семафоре задач (ничего не возвращается, если поддержка приостановки задач отключена);
first_task – указатель на переменную типа NUSE_TASK, которая примет индекс первой приостановленной задачи (ничего не возвращается, если поддержка приостановки задач отключена).
while (NUSE_Semaphore_Blocking_Count[semaphore] != 0)
{
U8 index; /* check whether any tasks are blocked */
/* on this 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_SEMAPHORE_WAS_RESET;
NUSE_Task_Status[index] = NUSE_READY;
break;
}
}
NUSE_Semaphore_Blocking_Count[semaphore]--;
}
#if NUSE_SCHEDULER_TYPE == NUSE_PRIORITY_SCHEDULER
NUSE_Reschedule(NUSE_NO_TASK);
#endif
Функция возвращает статус семафора. Затем, если функциональность блокировки API-вызовов активирована, возвращается количество ожидающих задач и индекс первой из них (в противном случае этим параметрам присваивается значение 0).
Этот служебный вызов возвращает количество семафоров в приложении. В Nucleus RTOS это значение меняется со временем и возвращаемое значение соответствует текущему количеству семафоров, а в Nucleus SE возвращаемое значение устанавливается на этапе сборки и больше не меняется.
Семафоры используют два или три массива структур данных (в ОЗУ и ПЗУ), которые, как и все другие объекты Nucleus SE, являются набором таблиц, размер которых зависит от количества семафоров в приложении и выбранных параметров.
Настоятельно рекомендую, чтобы код приложения не использовал прямой доступ к этим структурам данных, а обращался к ним через предоставляемые функции API. Это позволит избежать несовместимости с будущими версиями Nucleus SE и нежелательных побочных эффектов, а также упростит портирование приложения на Nucleus RTOS. Для лучшего понимания принципа работы кода служебных вызовов и для отладки ниже дан подробный обзор структур данных.
Эти данные имеют следующую структуру:
NUSE_Semaphore_Counter[] – массив типа U8, имеющий одну запись для каждого сконфигурированного семафора, в нем хранится значение счетчика.
NUSE_Semaphore_Blocking_Count[] – массив типа U8, содержит счетчики заблокированных на каждом семафоре задач. Этот массив существует, только если активирована функциональность блокировки API вызовов.
NUSE_Semaphore_Counter[] инициализируется в начальное значение (смотри «Данные в ПЗУ» ниже), а NUSE_Semaphore_Blocking_Count[] зануляется при помощи NUSE_Init_Semaphore() при запуске Nucleus SE. Одна из следующих статей предоставит полное описание процедур запуска Nucleus SE.
RAM U8 NUSE_Semaphore_Counter[NUSE_SEMAPHORE_NUMBER];
#if NUSE_BLOCKING_ENABLE
RAM U8 NUSE_Semaphore_Blocking_Count[NUSE_SEMAPHORE_NUMBER];
#endif
NUSE_Semaphore_Initial_Value[] – массив типа U8, имеющий одну запись для каждого семафора, это начальные значения семафоров.
ROM U8 NUSE_Semaphore_Initial_Value[NUSE_SEMAPHORE_NUMBER] =
{
/* semaphore initial count values */
};
NUSE_SEMAPHORE_NUMBER * 2
Этот вызов API удаляет ранее созданный семафор. В нем нет необходимости в Nucleus SE, так как семафоры создаются статически и не могут быть удалены.
Этот вызов API формирует последовательный список указателей на все семафоры в системе. В нем нет необходимости в Nucleus SE, так как семафоры идентифицируются простым индексом, а не указателем.
Как и в случае со всеми другими объектами Nucleus SE, целью было обеспечение максимальной совместимости кода приложений с Nucleus RTOS. Семафоры не являются исключением и, с точки зрения пользователя, они реализованы также, как и в Nucleus RTOS. Есть и определенная несовместимость, которую я посчитал допустимой с учетом того, что финальный код станет более понятным и более эффективным с точки зрения объема требуемой памяти. В остальном, вызовы API Nucleus RTOS могут быть практически напрямую использованы как вызовы Nucleus SE.
В Nucleus RTOS все объекты описываются структурами данных (блоками управления), имеющими определенный тип. Указатель на этот блок управления служит идентификатором семафора. Я решил, что в Nucleus SE для эффективного использования памяти необходим другой подход: все объекты ядра описываются набором таблиц в ОЗУ и/или ПЗУ. Размер этих таблиц определяется количеством сконфигурированных объектов каждого типа. Идентификатор конкретного объекта – индекс в этой таблице. Таким образом, я определил NUSE_SEMAPHORE в качестве эквивалента U8, переменная (а не указатель) этого типа служит идентификатором семафора. С этой небольшой несовместимостью легко справиться, если код портируется с Nucleus SE на Nucleus RTOS и наоборот. Обычно над идентификаторами объектов не выполняются никакие операции, кроме перемещения и хранения.
Nucleus RTOS также поддерживает присвоение имен семафорам. Эти имена используются только при отладке. Я исключил их из Nucleus SE, чтобы сэкономить память.
В Nucleus RTOS счетчик семафора имеет тип unsigned, который обычно представляет из себя 32-битную переменную. В Nucleus SE счетчик 8-битный, но это можно легко изменить. Обычно, в Nucleus RTOS проверка на переполнение семафора не производится. Вызов Nucleus SE API не позволит присвоить счетчику значения выше 255.
Nucleus RTOS поддерживает восемь служебных вызовов для работы с семафорами. Из них три не реализованы в Nucleus SE. Детали этих вызовов, а также решение об их исключении из Nucleus SE были описаны выше.
Об авторе: Колин Уоллс уже более тридцати лет работает в сфере электронной промышленности, значительную часть времени уделяя встроенному ПО. Сейчас он – инженер в области встроенного ПО в Mentor Embedded ( подразделение Mentor Graphics). Колин Уоллс часто выступает на конференциях и семинарах, автор многочисленных технических статей и двух книг по встроенному ПО. Живет в Великобритании. Профессиональный блог Колина: https://blogs.mentor.com/colinwalls/, e-mail: colin_walls@mentor.com