Программирование ARM FreeRTOS: оповещения задач Thu, April 25 2024  

Поделиться

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

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

FreeRTOS: оповещения задач Печать
Добавил(а) microsin   

У каждой системы реального времени (RTOS) имеется массив оповещений задач (task notifications). Каждое оповещение задачи имеет состояние оповещения, которое может быть либо "в ожидании" (pending), либо "без ожидания" (not pending) какого-либо события, и 32-битное значение оповещения. Константа configTASK_NOTIFICATION_ARRAY_ENTRIES устанавливает количество индексов в этом массиве оповещений задачи. До версии FreeRTOS V10.4.0 у задач было только одно task notification, не массив оповещений.

Примечание: опции и константы FreeRTOS (макросы, имена которых начинаются на config) находятся в заголовочном файле конфигурации FreeRTOSConfig.h.

Прямое оповещение задачи это событие, которое посылается задаче непосредственно, вместо того, чтобы передать событие косвенно через промежуточный объект, такой как очередь (queue), группа событий (event group) или семафор (semaphore). Отправка прямого оповещения задачи установит состояние целевой задачи в 'pending'. Так же, как задача может быть заблокированной на промежуточном объекте, таком как семафор, чтобы ждать состояния его доступности, задача может заблокироваться на оповещении задачи, чтобы ждать, когда состояние этого оповещение станет 'pending'.

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

• Перезапись значения независимо от того, прочитала ли его получающая задача, или не прочитала.
• Перезапись значения только в том случае, когда получающая задача его прочитала.
• Установка одного или большего количества бит в значении.
• Инкремент значения на 1.

Вызов xTaskNotifyWait()/xTaskNotifyWaitIndexed() для чтения значения оповещения очистит состояние оповещения, переведя его в 'not pending'. Состояния оповещения также можно явно установить в 'not pending' вызовом xTaskNotifyStateClear()/xTaskNotifyStateClearIndexed().

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

Функционал RTOS task notification по умолчанию разрешен, и может быть исключен из сборки (при этом экономится 8 байт на одну задачу и на один индекс в массиве) путем установки в 0 опции configUSE_TASK_NOTIFICATIONS.

Важное замечание: буферы потока и сообщений (FreeRTOS Stream и Message Buffers) используют task notification по индексу массива 0. Если нужно поддерживать состояние task notification между вызовами API-функций Stream или Message Buffer, то используйте task notification с индексом > 0.

[Выигрыш в быстродействии и ограничения по использованию]

Гибкость реализации task notifications позволяет их использовать там, где иначе необходимо было бы создать отдельную очередь, двоичный семафор, семафор со счетчиком или группу событий. Разблокировка задачи RTOS прямым оповещением работает на 45% быстрее, и использует при этом меньший объем RAM, чем разблокировка задачи на промежуточном объекте, таком как двоичный семафор. Как и следовало бы ожидать, этот выигрыш в производительности влечет за собой некоторые ограничения в использовании:

1. RTOS task notifications можно использовать, если только одна задача может быть получателем события. Однако это условие выполняется в большинстве случаев использования, таких как обработка данных, собранных обработчиком прерывания.

2. Есть только один случай, когда вместо очереди может использоваться RTOS task notification: когда принимающая задача может ожидать оповещения в состоянии Blocked (не расходуя время CPU), отправляющая задача не может ждать завершения отправки в состоянии Blocked state, если отправка не может быть завершена немедленно.

[Варианты использования]

Оповещения посылаются API-вызовами xTaskNotifyIndexed() и xTaskNotifyGiveIndexed() (и их ISR-эквивалентами, предназначенными для вызова из тела обработчика прерывания), и остаются в состоянии pending, пока принимающая задача не вызовет либо xTaskNotifyWaitIndexed(), либо ulTaskNotifyTakeIndexed(). Каждая из этих API-функций имеет эквивалент без суффикса "Indexed". Версии без "Indexed" всегда работают с оповещениями по индексу 0. Например, xTaskNotifyGive(TargetTask) является эквивалентом xTaskNotifyGiveIndexed(TargetTask, 0) - оба этих вызова инкрементируют task notification с индексом 0 для задачи TargetTask.

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

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

Подобным образом xTaskNotifyGive() (или xTaskNotifyGiveIndexed(), либо их ISR-версии) используются вместо xSemaphoreGive() и xSemaphoreGiveFromISR().

Ниже показан пример.

/* В этом примере передающая функция используется драйвером
периферийного устройства. Задача RTOS вызывает передающую функцию,
затем ждет в состоянии Blocked (не потребляя время CPU), пока
не получит оповещение о завершении передачи. Передача происходит
под управлением DMA, и прерывание завершения DMA используется
для оповещения задачи. */
 
/* Сохранение дескриптора задачи, которая будет оповещаться,
   когда передача завершится. */
static TaskHandle_t xTaskToNotify = NULL;
 
/* Используемый индекс в массиве оповещений целевой задачи. */
const UBaseType_t xArrayIndex = 1;
 
/* Функция передачи драйвера периферийного устройства. */
void StartTransmission (uint8_t *pcData, size_t xDataLength)
{
   /* В этом месте xTaskToNotify должен быть NULL, поскольку
      передача не происходит. При необходимости может
      использоваться мьютекс для защиты доступа к периферийному
      устройству. */
   configASSERT( xTaskToNotify == NULL );
 
   /* Сохранение дескриптора текущей задачи. */
   xTaskToNotify = xTaskGetCurrentTaskHandle();
 
   /* Запуск передачи. По её завершению будет сгенерировано
      прерывание. */
   vStartTransmit( pcData, xDatalength );
}
 
/* Прерывание завершения передачи. */
void vTransmitEndISR (void)
{
   BaseType_t xHigherPriorityTaskWoken = pdFALSE;
 
   /* В этом месте xTaskToNotify не должно быть NULL,
      поскольку была запущена передача. */
   configASSERT( xTaskToNotify != NULL );
 
   /* Оповещение задачи о том, что передача завершена. */
   vTaskNotifyGiveIndexedFromISR(xTaskToNotify,
                                 xArrayIndex,
                                 &xHigherPriorityTaskWoken);
 
   /* Нет передачи, так что нет задач для оповещения. */
   xTaskToNotify = NULL;
 
   /* Если xHigherPriorityTaskWoken не установлено в pdTRUE,
      то должно произойти переключение контекста для гарантии,
      что возврат управления произойдет в задачу с самым
      высоким приоритетом. Для этой цели используется макрос,
      реализация которого зависит от порта FreeRTOS, и он
      может называться portEND_SWITCHING_ISR(). */
   portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
 
/* Задача, которая инициирует передачу, и затем входит в состояние
   Blocked (не потребляя время CPU) для ожидания завершения передачи. */
void vAFunctionCalledFromATask (uint8_t ucDataToTransmit,
                                size_t xDataLength)
{
   uint32_t ulNotificationValue;
   const TickType_t xMaxBlockTime = pdMS_TO_TICKS( 200 );
 
   /* Старт передачи вызовом функции, показанной выше. */
   StartTransmission( ucDataToTransmit, xDataLength );
 
   /* Ожидание оповещения о завершении передачи. Обратите внимание,
      что первый параметр pdTRUE, что дает эффект очистки значения
      оповещения обратно в 0, тем самым значение оповещения работает
      как двоичный семафор (вместо счетчика).  */
   ulNotificationValue = ulTaskNotifyTakeIndexed(xArrayIndex,
                                                 pdTRUE,
                                                 xMaxBlockTime );
 
   if (ulNotificationValue == 1)
   {
      /* Передача завершилась, как ожидалось. */
   }
   else
   {
      /* Таймаут вызова ulTaskNotifyTake(). */
   }
}

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

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

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

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

[Пример 1]

/* Обработчик прерывания, который не обрабатывает прерывания напрямую,
   вместо этого откладывая обработку на задачу с высоким приоритетом.
   Этот ISR использует RTOS task notifications, чтобы разблокировать
   эту задачу RTOS, и инкрементировать task notification value. */
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( ;; )
   {
      /* Блокировка на ожидании оповещения. Здесь task notification
         используется в качестве семафора со счетчиком. Значение|
         оповещения задачи инкрементируется всякий раз, когда ISR
         вызывает vTaskNotifyGiveFromISR(), и декрементируется,
         когда задача вызовет ulTaskNotifyTake() - таким образом
         счетчик хранит количество отложенных обработок прерываний.
         Первый параметр установлен в pdFALSE, чтобы значение
         оповещения только декрементировалось (не очищалось в 0),
         и одновременно обрабатывается одно событие отложенной
         обработки прерывания. См. пример 2, где дана более
         практичная реализация. */
      ulNotifiedValue = ulTaskNotifyTake (pdFALSE,
                                          xBlockTime);
      if (ulNotifiedValue > 0)
      {
         /* Выполняется любая обработка, заказанная прерыванием. */
         xEvent = xQueryPeripheral();
 
         if (xEvent != NO_MORE_EVENTS)
         {
            vProcessPeripheralEvent (xEvent);
         }
      }
      else
      {
         /* За ожидаемое время не было получено оповещение. */
         vCheckForErrorConditions();
      }
   }
}

[Пример 2]

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

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

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

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

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

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

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

Пример:

/* Этот пример показывает одну задачу RTOS, используемую для обработки
   событий, поступающих от двух разных ISR - прерывание передачи и
   прерывание приема. Множество периферийных устройств используют
   один и тот же обработчик для обоих этих событий, в этом случае
   регистр состояния периферийного устройства может быть просто наложен
   операцией ИЛИ на значение оповещения задачи.
 
   Первые биты определены для представления каждого из источников
   прерывания: */
#define TX_BIT    0x01
#define RX_BIT    0x02
 
/* Дескриптор задачи, которая будет получать оповещения из прерываний.
   Значение этого дескриптора было получено при создании этой задачи. */
static TaskHandle_t xHandlingTask;
 
/* Реализация ISR прерывания передачи. */
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);
}
 
/* Реализация ISR прерывания приема идентична, отличие только в том,
   что установится бит RX_BIT в значении оповещения задачи. */
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)
         {
            /* ISR установил бит передачи. */
            prvProcessTx();
         }
 
         if ((ulNotifiedValue & RX_BIT) != 0)
         {
            /* ISR установил бит приема. */
            prvProcessRx();
         }
      }
      else
      {
         /* За ожидаемое время не было получено оповещение. */
         prvCheckForErrors();
      }
   }
}

Оповещения задачи RTOS (task notifications) можно использовать для отправки данных в задачу, однако здесь больше ограничений по сравнению с использованием очереди:

1. Могут быть отправлены только 32-битные значения.
2. Значение сохраняется как task's notification value, и в любой момент времени может быть только одно значение оповещения.

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

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

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

Для примеров см. документацию на соответствующие API-функции.

[Ссылки]

1. RTOS Task Notifications site:freertos.org.
2FreeRTOS: семафоры со счетчиком и оповещения задач.
3ESP-IDF FreeRTOS Task API.
4FreeRTOS: указатели в TLS.
5FreeRTOS: xTaskNotifyWait / xTaskNotifyWaitIndexed.
6. FreeRTOS: функции группы бит событий.

 

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


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

Top of Page