Source: https://www.freertos.org/RTOS_Task_Notification_As_Counting_Semaphore.html
Перевод может содержать ошибки. Читайте первоисточник.
[Task Notifications - Уведомления]
Разблокировка задачи с помощью уведомления на 45% быстрее и требует меньше ОЗУ, чем разблокировка с помощью счётного семафора. На этой странице продемонстрировано, как это может быть реализовано в приложении.
Счётный семафор - это семафор, который имеет счётчик значений. Значения находятся в диапазоне от нуля до максимального значения, которое указывается в момент создания семафора. Задача может "взять" семафор только если он доступен, а семафор доступен только если значение его счётчика больше нуля.
Когда уведомление используется вместо счётного семафора, поле значения уведомления в задаче-получателе используется вместо счётчика счётного семафора, а функция API ulTaskNotifyTake() используется вместо функции API xSemaphoreTake(). Параметр xClearOnExit при вызове функции ulTaskNotifyTake() должен быть установлен в значение pdFALSE для того, чтобы значение уведомления каждый раз "при забирании семафора" только уменьшалось на единицу (а не обнулялось). Благодаря этому эмулируется именно счётный семафор.
Аналогично функции API xTaskNotifyGive() и vTaskNotifyGiveFromISR() используются вместо функций xSemaphoreGive() и xSemaphoreGiveFromISR().
Первый пример использует уведомление принимающей задачи в качестве счётного семафора. Второй пример предоставляет более прагматичную и эффективную реализацию.
/*----------------------------------------------------------------------------*/ /* Обработчик прерывания не обрабатывает прерывание напрямую, но вместо этого сразу же передаёт обработку задаче RTOS с высоким приоритетом. Обработчик прерывания использует уведомление: 1) для разблокировки задачи RTOS, 2) для увеличения значения в поле уведомления задачи RTOS. */ void vANInterruptHandler( void ) { BaseType_t xHigherPriorityTaskWoken; /* Очищаем флаг прерывания. */ prvClearInterruptSource(); /* Переменная xHigherPriorityTaskWoken должна быть инициализирована значением pdFALSE. Если вызов функции vTaskNotifyGiveFromISR() разблокирует задачу-обработчик, и приоритет задачи-обработчика выше, чем приоритет текущей запущенной задачи, тогда xHigherPriorityTaskWoken будет автоматически установлен в значение pdTRUE. */ xHigherPriorityTaskWoken = pdFALSE; /* Разблокируем задачу-обработчик, чтобы она могла выполнить все необходимые действия по обработке прерывания. xHandlingTask это хэндлер задачи, который получаем в момент создания задачи. vTaskNotifyGiveFromISR() также увеличивает на единицу значение в поле уведомления задачи. */ vTaskNotifyGiveFromISR( xHandlingTask, &xHigherPriorityTaskWoken ); /* Принудительно переключаем контекст, если значение xHigherPriorityTaskWoken изменилось на pdTRUE. Макрос, используемый для этого, зависит от порта для вашего процессора, и может называться 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( ;; ) { /* Блокируем задачу для ожидания уведомления. Здесь уведомление используется в качестве счётного семафора. Значение в поле уведомления увеличивается на единицу каждый раз, когда обработчик прерывания вызывает функцию vTaskNotifyGiveFromISR(), и уменьшается на единицу каждый раз, когда задача вызывает функцию ulTaskNotifyTake() - так получаем эффект сохранения количества поступивших запросов обработки прерывания. Первый параметр при вызове должен быть pdFALSE, чтобы значение уведомления только уменьшалось на единицу (а не обнулялось), и за один раз обрабатывался один запрос от прерывания. Дополнительно посмотрите пример 2 (ниже), где реализован более прагматичный подход. */ ulNotifiedValue = ulTaskNotifyTake( pdFALSE, xBlockTime ); if( ulNotifiedValue > 0 ) { /* Выполняем некие действия, которые требуются для обработки прерывания. */ xEvent = xQueryPeripheral(); if( xEvent != NO_MORE_EVENTS ) { vProcessPeripheralEvent( xEvent ); } } else { /* Не получили уведомления за отведённое время. */ vCheckForErrorConditions(); } } }
Этот пример показывает более прагматичный и эффективный подход к реализации задачи-обработчика. Здесь значение, возвращаемое функцией ulTaskNotifyTake() сохраняется и используется для определения, сколько запросов на обслуживание было сгенерировано в обработчике прерывания. Это позволяет обнулять значение в поле уведомления каждый раз, когда вызывается ulTaskNotifyTake(). Обработчик прерывания для этого примера аналогичен примеру 1 (выше).
/*----------------------------------------------------------------------------*/ /* Задача переходит в заблокированное состояние для ожидания уведомления о том, что периферия требует обслуживания (servicing) */ void vHandlingTask( void *pvParameters ) { BaseType_t xEvent; const TickType_t xBlockTime = pdMS_TO_TICS( 500 ); uint32_t ulNotifiedValue; for( ;; ) { /* Как и в примере выше, переходим в заблокированное состояние для ожидания уведомления от обработчика прерывания. Но первый параметр в вызове функции ulTaskNotifyTake() установлен в значение 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; } } } } }