Программирование ARM FreeRTOS: семафоры со счетчиком и оповещения задач Thu, July 02 2020  

Поделиться

Нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.

FreeRTOS: семафоры со счетчиком и оповещения задач Печать
Добавил(а) microsin   

В документации FreeRTOS [3, 4, 5] приведено больше дополнительной информации про очереди, двоичные семафоры, мьютексы, семафоры со счетчиками, рекурсивные семафоры, вместе с простыми рабочими примерами.

[Counting Semaphore]

Точно так же, как двоичные семафоры могут рассматриваться как очередь длиной 1, семафоры со счетчиком можно рассматривать как очередь с длиной больше 1. Только в случае семафора пользователей не интересуют данные, которые хранятся в очереди - важно только лишь, пуста очередь, или нет. В качестве нижележащего механизма семафоры используют очереди (queue), так что функции очередей и семафоров в некоторой степени совместимы.

Семафоры со счетчиком обычно используются в двух случаях:

Подсчет событий. В этом сценарии использования обработчик события (event handler) будет "выдавать" (give) семафор каждый раз при возникновении события (при этом счетчик семафора увеличивается на 1), и задача обработки (handler task) будет "брать" (take) семафор, когда есть такая возможность - зависит от приоритета задач и свободного времени в системе, этим управляет планировщик (когда семафор "взят", счетчик семафора уменьшается на 1). Таким образом, значение счетчика семафора показывает разницу между количеством произошедших событий и количеством обработанных событий. В этом случае при создании семафора со счетчиком его счетчик должен быть равен 0.

Управление ресурсом. В этом сценарии значение счетчика показывает количество доступных ресурсов. Чтобы получить контроль над ресурсом, задача (task) сначала должна получить семафор, при этом уменьшая на 1 значение счетчика семафора. Когда значение счетчика достигает 0, это означает отсутствие доступных ресурсов. Когда задаче завершает работу с ресурсом, она возвращает семафор обратно (gives semaphore back), увеличивая при этом на 1 счетчик семафора. В этом случае при создании семафора со счетчиком его счетчик должен быть равен некому максимальному значению, соответствующему количеству доступного ресурса.

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

Ниже во врезке приведено краткое описание функций, связанных с семафорами (полное описание параметров функций с примерами см. по ссылкам в статье [6]). Также просмотрите файлы в каталоге FreeRTOS/Demo/Common/Minimal, где есть несколько примеров использования семафоров.

Общие замечания по именам API-функций:

1. В обработчиках прерываний можно использовать только те функции, имена которых заканчиваются на "FromISR".

2. Функции, которые создают объекты семафоров и мьютексов, требуют некоторое количество памяти для хранения информации о состоянии создаваемого объекта и связанных с ним данных. Имена функций, заканчивающиеся на "Static", требуют для себя макроса configSUPPORT_STATIC_ALLOCATION, установленного в 1 с помощью файла конфигурации FreeRTOSConfig.h. В этом случае RAM предоставляется программистом приложения, для этого требуется специальный параметр функции, и RAM выделяется статически в момент компиляции. Подробнее про статическое и динамическое выделение памяти см. Static Vs Dynamic Memory Allocation site:freertos.org. Если имя функции создания объекта не заканчивается на "Static", то память выделяется из кучи FreeRTOS. В этом случае макрос configSUPPORT_DYNAMIC_ALLOCATION в файле FreeRTOSConfig.h должен быть установлен в 1, либо он должен быть оставлен не определенным (в таком случае вступает в действие его значение по умолчанию 1). Подробнее про статическое и динамическое выделение памяти см. Static Vs Dynamic Memory Allocation site:freertos.org.

Описываемые здесь функции определены в заголовочном файле semphr.h, однако следует подключать к проекту другой файл - cmsis_os.h:

#include "cmsis_os.h"

xSemaphoreCreateBinary, xSemaphoreCreateBinaryStatic, vSemaphoreCreateBinary. Функция создает двоичный семафор, и возвращает дескриптор, по которому к этому семафору можно обращаться. Функция vSemaphoreCreateBinary() устарела и оставлена для совместимости, используйте вместо неё функцию xSemaphoreCreateBinary(). В случае проблем с выделением памяти возвратит NULL.

SemaphoreHandle_t xSemaphoreCreateBinary( void );

Семафор создается в "пустом" состоянии. Это значит, что для того, чтобы семафор можно было "взять" вызовом xSemaphoreTake() (т. е. для того, чтобы задача могла на семафоре разблокироваться), семафор надо сначала "предоставить" с помощью вызова API-функции xSemaphoreGive().

Двоичные семафоры и мьютексы очень похожи друг на друга, но имеют тонкие отличия: мьютексы снабжены механизмом наследования приоритета (priority inheritance), а двоичные семафоры этой возможностью не обладают. Это делает двоичные семафоры лучшим выбором для реализации синхронизации (между задачами или между задачами и прерыванием), а мьютексы больше подходят для реализаци простого взаимного исключения (mutual exclusion).

Двоичный семафор не нужно отдавать обратно, когда он получен, так что синхронизация может быть реализована одной задачей (или прерыванием), которая постоянно "предоставляет" (give) семафор, в том время как другая задача постоянно "берет" (take) этот семафор. Это демонстрирует пример кода на страничке документации функции xSemaphoreGiveFromISR() site:freertos.org. Обратите внимание, что той же функциональности можно добиться более эффективным способом с помощью прямого оповещения задачи (см. далее раздел "Task Notifications").

Приоритет задачи, которая "берет" (take) мьютекс, может быть потенциально повышен, если другая задача с более высоким приоритетом пытается "получить" (obtain) тот же самый мьютекс. Задача, которая овладела мьютексом, "наследует" приоритет задачи, которая пытается "взять" тот же самый мьютекс. Это значит, что мьютекс должен быть всегда возвращен назад (give back) – иначе задача с более высоким приоритетом никогда не сможет получить мьютекс, и задача с более низким приоритетом никогда не отменит свое наследование повышенного приоритета. Пример мьютекса, используемого для реализации взаимного исключения, предоставлен на странице документации xSemaphoreTake() site:freertos.org.

И к мьютексам, и к двоичным семафорам обращаются через переменную-дескриптор типа SemaphoreHandle_t, и её можно использоваться в любой API-функции, которая принимает параметр такого типа. В отличие от мьютексов, двоичные семафоры могут использоваться в обработчиках прерывания (ISR, т. е. имеют версии с суффиксом "FromISR").

xSemaphoreCreateCounting, xSemaphoreCreateCountingStatic. Функция создает семафор со счетчиком, и возвратит его дескриптор. В случае проблем с выделением памяти возвратит NULL.

SemaphoreHandle_t xSemaphoreCreateCounting( UBaseType_t uxMaxCount,
                                            UBaseType_t uxInitialCount);

xSemaphoreCreateMutex, xSemaphoreCreateMutexStatic, xSemaphoreCreateRecursiveMutex, xSemaphoreCreateRecursiveMutexStatic. Создает мьютекс и возвратит его дескриптор. В случае проблем с выделением памяти возвратит NULL.

SemaphoreHandle_t xSemaphoreCreateMutex( void );

В отличие от семафоров, мьютексы не могут использоваться в обработчиках прерываний (ISR), т. е. они используются исключительно для синхронизации между задачами (task). Мьютексы "берутся" (take) с помощью вызова xSemaphoreTake(), и "даются" (give) вызовом xSemaphoreGive(). Версии xSemaphoreTakeRecursive() and xSemaphoreGiveRecursive() могут использоваться только с теми мьютексами, которые были созданы вызовом xSemaphoreCreateRecursiveMutex().

Не рекурсивный мьютекс может быть "взят" задачей только один раз. Любая попытка задачи взять не рекурсивный мьютекс, который уже получил владельца, потерпит неудачу – и мьютекс всегд возвращается обратно задачей, которая первая взяла мьютекс.

В отличие от не рекурсивных мьютексов задача может "взять" рекурсивный мьютекс несколько раз, и рекурсивный мьютекс будет возвращен только после того, как задача-владелец "вернула" мьютекс то же самое количество раз, сколько раз она "забрала" этот мьютекс.

Мьютексы и двоичные семафоры функционально очень похожи друг на друга, но имеют некоторые тонкие отличия: у мьютексов есть механизм наследования приоритета, а у семафоров нет. Таким образом, семафоры лучше использовать для синхронизации (либо между задачами, либо между прерыванием и задачами), а мьютексы лучше подходят для реализации простого взаимного исключения задач (mutual exclusion).

Как и не рекурсивные, так и рекурсивные мьютексы реализуют алгоритм наследования приоритета. Приоритет задачи, которая "взяла" (take) мьютекс, будет временно повышен, если другая задача с более высоким приоритетом пытается получить тот же мьютекс. Задача, которая уже завладела мьютексом, "наследует" приоритет задачи, которая попыталась "взять" тот же мьютекс. Это значит, что мьютекс всегда должен быть "возвращен обратно" (give back) - иначе задача с более высоким приоритетом никогда не сможет получить мьютекс и разблокироваться, а задача с более низким приоритетом никогда не отменит наследование приоритета.

Двоичный семафор не нужно отдавать обратно после того, как он был взят, поэтому синхронизация задачи может быть реализована так, что одна задача (или прерывание) постоянно "дают" семафор, а другая задача постоянно "берет" этот семафор (при успешном взятии семафора задача в этот момент разблокируется).

И к мьютексам, и к двоичным семафорам обращаются через переменную-дескриптор типа SemaphoreHandle_t, и её можно использоваться в любой API-функции, которая принимает параметр такого типа.

vSemaphoreDelete. Удалит любой семафор, включая мьютексы, и рекурсивные и не рекурсивные. Не удаляйте тот семафор, на котором заблокирована задача или несколько задач (это те задачи, которые находятся в состоянии Blocked, ожидая появления доступности семафора).

void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );

xSemaphoreGetMutexHolder. Для использования этой функции необходимо, чтобы макросы INCLUDE_xSemaphoreGetMutexHolder и configUSE_MUTEXES файла FreeRTOSConfig.h были установлены в 1. Функция возвратит дескриптор задачи, которая удерживает мьютекс, указанный в параметре функции. Если никто не удерживает мьютекс, то будет возвращен NULL.

TaskHandle_t xSemaphoreGetMutexHolder( SemaphoreHandle_t xMutex );

Функция xSemaphoreGetMutexHolder() может быть надежно использована, чтобы определить, является ли вызывающая задача владельцем мьютекса. Однако эта функция не может надежно использоваться, если мьютекс удерживается другой задачей, отличной от той, которая вызвала функцию xSemaphoreGetMutexHolder(). Причина в том, что владелец мьютекса может поменяться между моментом, когда функция была вызвана, и моментом, когда в вызвашей функцию задаче проверяется возвращенное из функции значение.

xSemaphoreTake, xSemaphoreTakeFromISR, xSemaphoreTakeRecursive. Это макрос для "взятия" семафора или мьютекса. Указанный в параметре семафор/мьютекс должен быть предварительно создан вызовом xSemaphoreCreateBinary(), xSemaphoreCreateMutex() или xSemaphoreCreateCounting(). Вернет pdTRUE, если семафор был успешно взят.

xSemaphoreTake( SemaphoreHandle_t xSemaphore,
                TickType_t xTicksToWait );

Параметр xTicksToWait задает таймаут в тиках, после которого задача, вызвавшая xSemaphoreTake, гарантированно разблокируется. Для преобразования тиков в реальное время может использоваться макрос portTICK_PERIOD_MS (для удобства часто время тика устанавливается на 1 мс, так что макрос portTICK_PERIOD_MS обычно не используют). Нулевое время блокировки может использоваться для опроса семафора. Если макрос INCLUDE_vTaskSuspend установлен в 1, то может использоваться portMAX_DELAY в качестве указываемого времени блокировки, чтобы задача могла быть заблокирована бесконечно долго (без таймаута).

Версия макроса xSemaphoreTake не может использоваться из ISR. Если необходимо, версия xSemaphoreTakeFromISR (или xQueueReceiveFromISR) может использоваться для взятия семафора из прерывания, хотя это не считается нормальным функционированием. В качестве нижележащего механизма семафоры используют очереди (queue), так что функции очередей и семафоров в некоторой степени совместимы. В отличие от xSemaphoreTake(), версия xSemaphoreTakeFromISR() не позволяет указать время блокировки.

xSemaphoreTakeFromISR( SemaphoreHandle_t xSemaphore,
                       BaseType_t *pxHigherPriorityTaskWoken );

Возможно (но не обязательно, в зависимости от типа семафора), что у семафора есть несколько задач, которые на этом семафоре заблокированы. Вызов xSemaphoreTakeFromISR() приведет к тому, что задача будет выведена из состояния ожидания (состояние Blocked). Если вызвавшая функция выйдет из состояния Blocked, и разблокированная задача имеет тот же приоритет, что и текущая выполняющаяся задача (та задача, выполнение которой было прервано), то внутри себя функция установит *pxHigherPriorityTaskWoken в значение pdTRUE.

Параметр pxHigherPriorityTaskWoken: если xSemaphoreTakeFromISR() установила *pxHigherPriorityTaskWoken в значение pdTRUE, то должно быть выполнено переключение контекста перед тем, как произойдет выход из преывания. Это даст гарантию, что прерывание возвратит управление в ту задачу, которая находится в состоянии готовности (Ready) и имеет самый высокий приоритет. Этот механизм идентичен используемому в функции xQueueReceiveFromISR() и дополнительную информацию можно получить из описания функции xQueueReceiveFromISR().

Начиная с версии FreeRTOS V7.3.0 параметр pxHigherPriorityTaskWoken стал опциональным, и его можно установить в значение NULL.

Версия xSemaphoreTakeRecursive это макрос, предназначенный для взятия мьютекса. Этот мьютекс должен быть предварительно создан вызовом xSemaphoreCreateRecursiveMutex (но не вызовом xSemaphoreCreateMutex). Чтобы можно было использовать xSemaphoreTakeRecursive, в файле конфигурации FreeRTOSConfig.h должен быть установлен в 1 макрос configUSE_RECURSIVE_MUTEXES.

xSemaphoreTakeRecursive( SemaphoreHandle_t xMutex,
                         TickType_t xTicksToWait );

Мьютекс, используемый рекурсивно, может быть "взят" несколько раз задачей-владельцем этого мьютекса (т. е. той же задачей, которая уже взяла его). Этот мьютекс не станет доступным до тех пор, пока задача-владелец не вызовет xSemaphoreGiveRecursive() для каждого успешного запроса взятия мьютекса - сколько раз мьютекс был взят, столько же раз он должен быть освобожден той же самой задачей. Например, если задача предварительно "взяла" один и тот же мьютекс 5 раз, то этот мьютекс не станет доступным для взятия другими задачами, пока он не будет "возвращен" обратно те же 5 раз задачей-владельцем.

xSemaphoreGive, xSemaphoreGiveRecursive, xSemaphoreGiveFromISR. Это макрос, который освобождает ("дает") семафор (или мьютекс). Семафор (мьютекс) должен быть предварительно создан вызовом xSemaphoreCreateBinary(), xSemaphoreCreateMutex(), xSemaphoreCreateCounting().

xSemaphoreGive( SemaphoreHandle_t xSemaphore );

Версия xSemaphoreGive не может использоваться из обработчика прерывания (ISR). Также xSemaphoreGive не может использоваться на семафорах, созданных вызовом xSemaphoreCreateRecursiveMutex().

Версия xSemaphoreGiveFromISR может (и должна) использоваться из кода ISR.

xSemaphoreGiveFromISR( SemaphoreHandle_t xSemaphore,
                       BaseType_t *pxHigherPriorityTaskWoken );

Значение параметра *pxHigherPriorityTaskWoken будет установлено в pdTRUE если указанный семафор разблокирует задачу, и разблокированная задача имеет приоритет выше, чем текущая работающая задача. Если функция xSemaphoreGiveFromISR() установила этот параметр в pdTRUE, то должно быть запрошено переключение контекста перед выходом из прерывания.

Начиная с версии FreeRTOS V7.3.0 параметр pxHigherPriorityTaskWoken стал опциональным, и его можно установить в значение NULL.

Версия xSemaphoreGiveRecursive это макрос, предназначенный для предоставления (выдачи) мьютекса. Вернет pdTRUE, если мьютекс был успешно "выдан". Чтобы можно было использовать xSemaphoreGiveRecursive, необходимо установить в 1 значение макроса configUSE_RECURSIVE_MUTEXES (находится в файле конфигурации FreeRTOSConfig.h).

xSemaphoreGiveRecursive( SemaphoreHandle_t xMutex );

Как уже упоминалось, рекурсивно используемый мьютекс может быть "взят" задачей-владельцем несколько раз. Этот мьютекс не станет доступным для других задач, пока задача-владелец не вызовет xSemaphoreGiveRecursive() столько же раз, сколько раз она успешно вызвала xSemaphoreTakeRecursive.

uxSemaphoreGetCount. Вернет значение счетчика семафора, если это семафор со счетчиком. Если это двоичный семафор, то вернет 1, если семафор доступен для взятия, и 0 если семафор недоступен.

UBaseType_t uxSemaphoreGetCount( SemaphoreHandle_t xSemaphore );

[Task Notification]

Оповещение задачи (task notification) во многих ситуациях может предоставить облегченную альтернативу семафорам со счетчиком.

Разблокировка задачи RTOS путем её прямого оповещения работает на 45% быстрее и использует меньше RAM, чем разблокировка задачи с использованием семафора.

Семафор со счетчиком это семафор, у которого может быть значение счетчика от 0 до максимального значения, причем начальное значение счетчика задается при создании семафора. Задача может только "взять" (take) семафор, если он доступен, и семафор доступен только тогда, когда значение его счетчика больше 0.

Когда task notification используется вместо семафора со счетчиком, значение оповещения принимающей задачи используется вместо значения счетчика семафора, и API-функция ulTaskNotifyTake() используется вместо API-функции xSemaphoreTake(). У функции ulTaskNotifyTake() параметр xClearOnExit устанавливается в значение pdFALSE, чтобы значение счетчика только декрементировалось (вместо его очистки) каждый раз, когда оповещение взято (notification is taken), тем самым эмулируется семафор со счетчиком.

Подобным образом используются функции xTaskNotifyGive() или vTaskNotifyGiveFromISR() вместо функций семафоров xSemaphoreGive() и xSemaphoreGiveFromISR().

В первом примере, показанном во врезке ниже, используется значение оповещения для принимающей задачи в качестве семафора со счетчиком. Во втором примере дана более прагматичная и эффективная реализация.

В этом примере показан принцип работы оповещения задачи, когда оповещение используется вместо семафора со счетчиком.

/* Обработчик прерывания (ISR) не обрабатывает прерывание напрямую
   в своем теле, вместо этого перекладывая обработку на задачу
   RTOS, для которой установлен высокий приоритет. Для этого ISR
   использует систему RTOS task notification, чтобы разблокировать
   задачу RTOS и одновременно увеличить значение оповещения для
   этой задачи (task's notification value). */
void vANInterruptHandler( void )
{
   BaseType_t xHigherPriorityTaskWoken;
 
   /* Очистка прерывания: */
    prvClearInterruptSource();
 
   /* Переменная xHigherPriorityTaskWoken должна быть инициализирована
      в значение pdFALSE. Если вызов vTaskNotifyGiveFromISR()
      разблокирует задачу обработки, и приоритет задачи обработки
      установлен выше, чем приоритет текущей выполняющейся задачи,
      то xHigherPriorityTaskWoken автоматически установится в pdTRUE. */
   xHigherPriorityTaskWoken = pdFALSE;
 
   /* Разблокировка задачи обработки, чтобы она могла выполнить любые
      нужные действия (например, обработать какие-то данные), связанные
      с прерыванием. Здесь xHandlingTask это дескриптор той самой
      задачи обработки, полученный при её создании. Вызов функции
      vTaskNotifyGiveFromISR() из ISR также инкрементирует значение
      оповещения для принимающей задачи обработки. */
   vTaskNotifyGiveFromISR( xHandlingTask, &xHigherPriorityTaskWoken );
 
   /* Произойдет принудительное переключение контекста, если теперь
      xHigherPriorityTaskWoken установлена в pdTRUE. Для этого
      используется макрос, зависящий от конкретного порта FreeRTOS.
      Его имя может быть portEND_SWITCHING_ISR. */
   portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
 
/* А это тело задачи, которая блокируется на ожидании оповещения
   о том, что какое-то периферийное устройство требует соответствующей
   обработки. */
void vHandlingTask( void *pvParameters )
{
   BaseType_t xEvent;
   const TickType_t xBlockTime = pdMS_TO_TICS( 500 );
   uint32_t ulNotifiedValue;
 
   for( ;; )
   {
      /* Здесь происходит блокировка на ожидании появления оповещения.
         Используется система RTOS task notification вместо семафора
         со счетчиком. Значение оповещения (task's notification value)
         инкрементируется всякий раз, когда ISR вызывает функцию
         vTaskNotifyGiveFromISR(), и декрементируется, когда задача
         обработки (здесь это vHandlingTask) вызывает ulTaskNotifyTake()
         - так что счетчик оповещения дает информацию о том, сколько
         прерываний в настоящее время ожидает своей обработки. Первый
         параметр установлен pdFALSE, так что значение оповещения
         только декрементируется и не очищается в 0, и за один раз
         обрабатывается только одно отложенное событие. Ниже в Примере 2
         показан более прагматичный подход. */
      ulNotifiedValue = ulTaskNotifyTake( pdFALSE,
                                          xBlockTime );
      if( ulNotifiedValue > 0 )
      {
         /* Тут выполняется любая обработка, требуемая для прерывания. */
         xEvent = xQueryPeripheral();
         if( xEvent != NO_MORE_EVENTS )
         {
            vProcessPeripheralEvent( xEvent );
         }
      }
      else
      {
         /* Не было получено оповещение в пределах ожидаемого времени. */
         vCheckForErrorConditions();
      }
   }
}

В этом примере значение, возвращенное из ulTaskNotifyTake(), используется для того, чтобы узнать, сколько осталось необработанных событий ISR, которые еще должны быть обработаны, позволяя счетчику оповещений задачи (task’s notification count) быть очищенным обратно в нулевое значение каждый раз, когда вызвана функция ulTaskNotifyTake(). Подразумевается, что обработчик прерывания (ISR) используется тот же самый, что и в Примере 1 (см. врезку выше).

/* Задача обработки, которая блокируется в ожидании появления
   оповещения о том, что периферийное устройство требует
   соответствующей обработки. */
void vHandlingTask( void *pvParameters )
{
   BaseType_t xEvent;
   const TickType_t xBlockTime = pdMS_TO_TICS( 500 );
   uint32_t ulNotifiedValue;
 
   for( ;; )
   {
      /* Как и в Примере 1, здесь происходит блокировка на ожидании
         оповещения от ISR. Однако теперь первый параметр установлен
         в pdTRUE, очищая тем самым в 0 значение оповещения задачи.
         Это означает, что каждое ожидающее обработки событие должно
         быть обработано до того, как ulTaskNotifyTake() будет
         вызвана снова. */
      ulNotifiedValue = ulTaskNotifyTake( pdTRUE,
                                          xBlockTime );
      if( ulNotifiedValue == 0 )
      {
         /* Не было получено оповещение в пределах ожидаемого времени. */
         vCheckForErrorConditions();
      }
      else
      {
         /* В ulNotifiedValue хранится количество ожидающих обработки
            прерываний. Здесь они все будут обработаны по очереди. */
         while( ulNotifiedValue > 0 )
         {
            xEvent = xQueryPeripheral();
            if( xEvent != NO_MORE_EVENTS )
            {
               vProcessPeripheralEvent( xEvent );
               ulNotifiedValue--;
            }
            else
            {
               break;
            }
         }
      }
   }
}

Пример 3 показывает облегченную реализацию обработки группы событий.

Группа событий (event group) это набор двоичных флагов (или бит). Для каждого из них разработчик приложения обозначил некий смысл. Задача (RTOS task) может либо находиться в заблокированном состоянии (Blocked state) в ожидании активности одного или нескольких флагов в группе. Задача RTOS не потребляет рабочее время CPU, когда находится в заблокированном состоянии.

Когда оповещение задачи (task notification) используется вместо группы событий, в качестве этой группы событий служит значение оповещения. Биты значения оповещения в принимающей задаче используются в качестве флагов событий, и API-функция xTaskNotifyWait() используется вместо API-функции xEventGroupWaitBits().

Подобным образом используються биты, установленные API-функциями xTaskNotify() и xTaskNotifyFromISR() (у которых параметр eAction установлен в eSetBits) вместо функций xEventGroupSetBits() и xEventGroupSetBitsFromISR() соответственно.

Функция xTaskNotifyFromISR() имеет значительное преимущество по производительности в сравнении с xEventGroupSetBitsFromISR(), потому что xTaskNotifyFromISR() выполняется непосредственно в ISR, в то время как выполнение xEventGroupSetBitsFromISR() должно быть переложено на обработку в некой задаче-демоне.

В отличие от группы событий, получающая оповещение задача не может указать, что она хочет выйти из состояния Blocked только тогда, когда одновременно станет активной комбинация бит. Вместо этого задача разблокируется, когда любая комбинация бит станет активной, и поэтому задача должна сама проверить нужную комбинацию бит.

/* Этот пример демонстрирует, как одна задача RTOS используется для
   обработки событий, поступающих от двух разных ISR - обработчик
   прерывания передачи и обработчик прерывания приема. Многие
   периферийные устройства используют один и тот же обработчик
   для обоих этих событий, в этом случае регистр состояния прерывания
   периферийного устройства может быть просто побитовым ИЛИ,
   что используется в качестве значения уведомления задачи приема.
 
   Первые биты определены для идентификации каждого из источников
   прерывания. */
#define TX_BIT    0x01
#define RX_BIT    0x02
 
/* Задача-обработчик будет получать оповещения от прерываний.
   Это переменная, полученная при создании задачи-обработчика: */
static TaskHandle_t xHandlingTask;
 
/* Реализация обработчика прерывания передачи. */
void vTxISR( void )
{
   BaseType_t xHigherPriorityTaskWoken = pdFALSE;
 
   /* Очистка источника прерывания. */
   prvClearInterrupt();
 
   /* Оповещение задачи о завершении передачи путем установки
      бита TX_BIT в значении оповещения. */
   xTaskNotifyFromISR( xHandlingTask,
                       TX_BIT,
                       eSetBits,
                       &xHigherPriorityTaskWoken );
 
   /* Если xHigherPriorityTaskWoken сейчас установлено в pdTRUE,
      то должно быть выполнено переключение контекста для гарантии,
      что из прерывания произойдет возврат в задачу с самым высоким
      приоритетом. Макрос, используемый для этой цели, зависит
      от конкретного порта FreeRTOS, и имя макроса может быть
      другим. Здесь используется portEND_SWITCHING_ISR(). */
   portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
 
/* Реализация обработчика прерывания приема идентична
   за исключением того, что она устанавливает другой бит
   в значении оповещения задачи. */
void vRxISR( void )
{
   BaseType_t xHigherPriorityTaskWoken = pdFALSE;
 
   /* Очистка источника прерывания. */
   prvClearInterrupt();
 
   /* Оповещение задачи, что прием завершен, путем установки
      бита RX_BIT в значении оповещения. */
   xTaskNotifyFromISR( xHandlingTask,
                       RX_BIT,
                       eSetBits,
                       &xHigherPriorityTaskWoken );
 
   /* Если xHigherPriorityTaskWoken сейчас установлено в pdTRUE,
      то должно быть выполнено переключение контекста для гарантии,
      что из прерывания произойдет возврат в задачу с самым высоким
      приоритетом. Макрос, используемый для этой цели, зависит
      от конкретного порта FreeRTOS, и имя макроса может быть
      другим. Здесь используется portEND_SWITCHING_ISR(). */
   portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
 
/* Реализация задачи, которую оповещают обработчики прерываний. */
static void prvHandlingTask( void *pvParameter )
{
   const TickType_t xMaxBlockTime = pdMS_TO_TICKS( 500 );
   BaseType_t xResult;
 
   for( ;; )
   {
      /* Ожидание оповещения от прерываний. */
      xResult = xTaskNotifyWait( pdFALSE,    /* Не очищать биты на входе. */
                           ULONG_MAX,        /* На выходе очищаются все биты. */
                           &ulNotifiedValue, /* Здесь хранится значение оповещения. */
                           xMaxBlockTime );  /* Время таймаута на блокировке. */
      if( xResult == pdPASS )
      {
         /* Было получено оповещение. Проверка, какие биты установлены. */
         if( ( ulNotifiedValue & TX_BIT ) != 0 )
         {
            /* Установлен бит обработчика передачи (TX ISR). */
            prvProcessTx();
         }
         if( ( ulNotifiedValue & RX_BIT ) != 0 )
         {
            /* Установлен бит обработчика приема (RX ISR). */
            prvProcessRx();
         }
      }
      else
      {
         /* В ожидаемое время не получено ни одно оповещение. */
         prvCheckForErrors();
      }
   }
}

Оповещение может использоваться в качестве облегченной реализацию Mailbox (очереди с емкостью в 1 элемент).

Оповещение задачи RTOS может использоваться для передачи в эту задачу данных. Однако этот способ передачи данных более ограничен по сравнению с RTOS-очередью по следующим причинам:

1. Могут быть отправлены только 32-битные числа.
2. Это число сохраняется как значение оповещения для принимающей задачи, и в любой момент времени можно использовать только одно значение оповещения. Кстати, может быть передано все, что угодно, лишь бы оно укладывалось в 32 бита - например указатель на структуру или указатель на массив.

Поэтому фраза "облегченный mailbox" используется вместо фразы "облегченная очередь". Значение оповещения задачи используется как значение mailbox.

Данные отправляются в задачу с помощью вызова API-функций xTaskNotify() и xTaskNotifyFromISR(), у которых параметр eAction установлен в значение eSetValueWithOverwrite или в значение eSetValueWithoutOverwrite. Если eAction установлено в eSetValueWithOverwrite, то значение оповещения принимающей задачи обновится, даже если принимающая задача уже имеет ожидающее обработки оповещение. Если eAction установлено в eSetValueWithoutOverwrite, то значение оповещения принимающей задачи обновится только если у принимающей задачи пока нет необработанных оповещений - поскольку обновление значения оповещения перезаписало бы предыдущее значение до того, как принимающая задача его обработала.

Задача может прочитать свое значение оповещения с помощью вызова xTaskNotifyWait().

У каждой задачи RTOS есть 32-битное значение оповещения, которое инициируется в 0, когда создается задача. Оповещение задачи это событие, которое непосредственно направляется одной задачей (или обработчиком прерывания) в другую задачу, чтобы она могла разблокироваться. Значение оповещения может быть опционально обновлено несколькими различными способами. Например, оповещение может перезаписать значение оповещения для принимающей задачи, или просто установить один или несколько бит в этом значении оповещения.

Примечание: описанные здесь функции оповещения определены в заголовочном файле task.h, однако для их использования нужно подключать заголовок cmsis_os.h:

#include "cmsis_os.h"

ulTaskNotifyTake. Эта функция предназначена для использования в качестве более быстрой и эффектной альтернативы для двоичных семафоров или семафоров со счетчиком. Семафоры FreeRTOS "выдаются" вызовом API-функции xSemaphoreTake(), и ulTaskNotifyTake() является её эквивалентом, используемым в системе оповещений. Возвратит значение оповещения задачи перед его декрементом или очисткой (см. описание параметра xClearCountOnExit).

uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit,
                            TickType_t xTicksToWait );

Когда задача использует свое значение оповещения как двоичный семафор или семафор со счетчиком, другая задача или прерывание должно посылать оповещения для неё либо макросом xTaskNotifyGive(), либо функцией xTaskNotify() с параметром eAction, установленным в значением eIncrement.

ulTaskNotifyTake() может очистить значение оповещения в 0 на выходе, в тактом случае значение оповещения работает как двоичный семафор. Либо значение оповещения на выходе может быть декрементировано. тогда значение оповещения работает как семафор со счетчиком.

Задача RTOS может использовать ulTaskNotifyTake() для (опциональной) блокировки на ожидании, когда значение оповещения станет ненулевым. В состоянии блокировки (Blocked state) рабочее время CPU не расходуется.

Там, где xTaskNotifyWait возвратит управление, когда поступило ожидаемое оповещение, функция ulTaskNotifyTake() возвратит управление, когда значение оповещения задачи стало ненулевым, с декрементом этого значения оповещения перед возвратом.

Параметр xClearCountOnExit: если задачей получено оповещение, и xClearCountOnExit установлено в pdFALSE, то значение оповещения будет декрементировано перед выходом из ulTaskNotifyTake(). Это поведение эквивалентно семафору со счетчиком, который декрементируется при успешном вызове xSemaphoreTake(). Если же при получении оповещения задачей xClearCountOnExit был установлен в pdTRUE, то значение оповещения будет сброшено в 0 перед выходом из ulTaskNotifyTake(). Это поведение эквивалентно двоичному семафору, который устанавливается в 0 после успешного вызова xSemaphoreTake().

xTaskNotifyGive, vTaskNotifyGiveFromISR. Это макрос, предназначенный для значения оповещения задачи. Значение оповещения используется как облегченная и более эффективная альтернатива двоичным семафорам или семафорам со счетчиком. Всегда возвратит pdPASS, потому что это макрос, вызывающий xTaskNotify() с параметром eAction, установленным в значение eIncrement.

BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify );

Семафоры FreeRTOS "предоставляются" с помощью API-функции xSemaphoreGive(), и функция xTaskNotifyGive() является соответствующим эквивалентом, когда значение оповещения задачи используется как как эквивалент двоичного семафора или семафора со счетчиком. В этом случае оповещаемая задача должна ждать поступления оповещения с помощью вызова API-функции ulTaskNotifyTake() вместо xTaskNotifyWait().

xTaskNotifyGive() не должна вызываться из обработчика прерывания, для этой цели должна использоваться версия vTaskNotifyGiveFromISR().

xTaskNotifyWait. Функция xTaskNotifyWait вернет pdTRUE, если оповещение было получен, или оповещение уже ожидало обработки, когда была вызвана xTaskNotifyWait. Значение pdFALSE возвращается, если произошел таймаут при вызове до того, как было принято оповещение.

BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry,
                            uint32_t ulBitsToClearOnExit,
                            uint32_t *pulNotificationValue,
                            TickType_t xTicksToWait );

Примечание: если Вы используете оповещения задачи для реализации поведения двоичного семафора или семафора со счетчиком, то используйте более простую API-функцию ulTaskNotifyTake() вместо xTaskNotifyWait().

Функция xTaskNotifyWait() приостанавливает вызвавшую ее задачу. Эта задача ждет поступления оповещения с опциональным таймаутом (указанным в параметре xTicksToWait). Если принимающая оповещение задача уже находится в состоянии Blocked на ожидании оповещения, то принимающая задача будет выведена из состояния Blocked, и оповещение будет очищено.

Параметр ulBitsToClearOnEntry: любые биты, установленные в ulBitsToClearOnEntry, будут очищены в значении оповещения задачи на входе в функцию xTaskNotifyWait (перед ожиданием задачей нового оповещения) при условии, что уведомление еще не ожидает вызова xTaskNotifyWait. Например, если ulBitsToClearOnEntry равно 0x01, то бит 0 в значении оповещения задачи будет очищен на входе в функцию. Установка ulBitsToClearOnEntry в значение 0xffffffff (ULONG_MAX) очистит все биты в значении оповещения задачи, т. е. значение оповещения станет равным 0.

Параметр ulBitsToClearOnExit: любые биты, установленные в ulBitsToClearOnExit будут очищены в значении оповещения перед тем, как произойдет выход из xTaskNotifyWait, если было получено оповещение. Эти биты будут очищены после того, как значение оповещение будет сохранено в *pulNotificationValue. Например, если ulBitsToClearOnExit равно 0x03, то биты 0 и 1 значения оповещения задачи будут очищены перед выходом из функции xTaskNotifyWait. Установка ulBitsToClearOnExit в значение 0xffffffff (ULONG_MAX) очистит все биты в значении оповещения, т. е. значение оповещения станет равным 0.

Параметр pulNotificationValue используется для передачи значения оповещения. Значение, скопированное в *pulNotificationValue, является значением оповещения для задачи, в котором оно было до настройки очистки бит с помощью ulBitsToClearOnExit. Если значение оповещения не используется, то установите pulNotificationValue в NULL.

xTaskNotify, xTaskNotifyFromISR. Функция используется для оповещения задачи. Возвратит pdPASS во всех случаях, кроме установки eAction в значение eSetValueWithoutOverwrite, когда значение оповещения задачи не может быть обновлено из-за того, что уже имеется ожидающее обработки значение оповещения.

BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify,
                        uint32_t ulValue,
                        eNotifyAction eAction );

Функция xTaskNotify() используется для прямой отправки события в потенциально разблокируемую задачу RTOS, а также для опционального обновления значения оповещения для принимающей задачи следующими способами:

• Запись 32-битного числа в значение оповещения.
• Добавление единицы (инкремент) значения оповещения.
• Установка одного или большего количества бит в значении оповещения.
• Значение оповещения остается не измененным.

Версия xTaskNotify() не должна использоваться для вызова из прерываний, используйте для этой цели версию xTaskNotifyFromISR().

Параметр xTaskToNotify задает дескриптор оповещаемой задачи. Дескриптор выполняющийся в настоящий момент задачи может быть получен вызовом API-функции xTaskGetCurrentTaskHandle().

Параметр ulValue используется для обновления значения оповещения задачи (см. описание параметра eAction).

Параметр eAction это перечисляемый тип (enum), который задает необходимый способ оповещения:

Установка eAction Выполняемое действие
eNoAction Оповещаемая задача принимает событие, но значение оповещения не обновляется. В этом случае параметр ulValue не используется.
eSetBits Значение оповещения задачи будет подвергнуто побитной операции ИЛИ со значением ulValue. Например, если ulValue установлено в 0x01, в значении оповещения задачи бит 0 будет установлен. Подобным образом, если ulValue равно 0x04, то будет установлен бит 2 значения оповещения задачи. Таким способом механизм RTOS task notification может использоваться в качестве облегченной альтернативы для группы событий (event group).
eIncrement Значение оповещения задачи будет инкрементировано на 1, делая тем самым вызов xTaskNotify() эквивалентным вызову xTaskNotifyGive(). В этом случае параметр ulValue не используется.
eSetValueWithOverwrite Значение оповещения задачи безусловно будет установлено в значение ulValue. Таким способом механизм RTOS task notification используется как облегченная альтернатива для xQueueOverwrite().
eSetValueWithoutOverwrite Если оповещаемая задача еще не имеет значения оповещения, ожидающего обработки, то значение оповещения будет установлено в ulValue. Если же оповещаемая задача уже имеет значение оповещения, ожидающее обработки, то это значение не будет обновлено, потому что обновление перезаписало бы предыдущее использованное значение. В этом случае вызов xTaskNotify() потерпит неудачу, и будет возвращено pdFALSE. Таким способом механизм RTOS task notification используется как облегченная альтернатива для xQueueSend() на очереди длиной 1.

[Ссылки]

1. FreeRTOS Counting Semaphores site:freertos.org.
2. FreeRTOS Task Notifications site:freertos.org.
3. FreeRTOS: практическое применение, часть 2 (управление очередями).
4. FreeRTOS: практическое применение, часть 3 (управление прерываниями).
5. FreeRTOS: практическое применение, часть 4 (управление ресурсами).
6. Semaphores API site:freertos.org.

 

Добавить комментарий


Защитный код
Обновить

Top of Page