У каждой системы реального времени (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;
/* Функция передачи драйвера периферийного устройства. */
voidStartTransmission (uint8_t*pcData, size_t xDataLength)
{
/* В этом месте xTaskToNotify должен быть NULL, поскольку
передача не происходит. При необходимости может
использоваться мьютекс для защиты доступа к периферийному
Семафор со счетчиком это такой семафор, у которого есть значение счетчика, которое может иметь значение от 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. */
if (xEvent != NO_MORE_EVENTS)
{
vProcessPeripheralEvent (xEvent);
}
}
else
{
/* За ожидаемое время не было получено оповещение. */
vCheckForErrorConditions();
}
}
}
[Пример 2]
В этом примере показана более практичная и эффективная реализация задачи RTOS. Здесь значение, возвращенное из ulTaskNotifyTake(), используется для того чтобы узнать, сколько ожидающих событий ISR должно быть обработано, позволяя значению счетчика оповещений задачи быть очищенным каждый раз, когда вызывается ulTaskNotifyTake(). Подразумевается, что в этом примере используется ISR из примера 1.
/* Используемый индекс в массиве оповещений целевой задачи. */
const UBaseType_t xArrayIndex =0;
/* Задача, которая блокируется в ожидании поступления оповещения о необходимости
выполнить действия по обслуживанию периферийного устройства. */
for( ;; )
{
/* Как и раньше, здесь происходит блокировка на ожидании оповещения
от ISR. Однако в этот раз первый параметр установлен в pdTRUE,
что очищает в 0 значение оповещения. Это значит, что все
отложенные оповещения должны быть обработаны до того, как
снова будет вызвана ulTaskNotifyTake(). */
ulNotifiedValue = ulTaskNotifyTakeIndexed (xArrayIndex,
pdTRUE,
xBlockTime);
if (ulNotifiedValue ==0)
{
/* Не было получено оповещение в течение ожидаемого времени. */
vCheckForErrorConditions();
}
else
{
/* ulNotifiedValue хранит количество ожидающих обработки
прерываний. Обработка их всех каждой прокруткой цикла. */while (ulNotifiedValue >0)
{
xEvent = xQueryPeripheral();
Группа событий (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
/* Дескриптор задачи, которая будет получать оповещения из прерываний.
Значение этого дескриптора было получено при создании этой задачи. */
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-функции.