FreeRTOS任务通知(Task Notifiy)概述
任务通知是FreeRTOS提供的一种轻量级通信机制,允许任务间直接发送事件或数据。相比队列、信号量等传统IPC机制,任务通知更高效,因为其直接操作任务控制块(TCB)中的通知字段,无需额外数据结构。
任务通知的核心机制
每个任务都有一个32位的通知值(ulNotifiedValue)和一个通知状态(eNotifyState)字段,存储在TCB中。通知状态包括:
eNotWaitingNotification:任务未等待通知。eWaitingNotification: 任务在等待通知。eNotifificationReceived:任务已收到通知。
主要API函数分析
TCB:
typedef struct tskTaskControlBlock
{
volatile StackType_t * pxTopOfStack;
ListItem_t xStateListItem;
ListItem_t xEventListItem;
UBaseType_t uxPriority;
StackType_t * pxStack;
char pcTaskName[ configMAX_TASK_NAME_LEN ];
.
.
.
.
/***********************任务通知就设置/读取这两个值*********************************/
#if ( configUSE_TASK_NOTIFICATIONS == 1 )
volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
#endif
/*********************************************************************************/
.
.
.
.
} tskTCB;
通知状态:
/* Values that can be assigned to the ucNotifyState member of the TCB. */
#define taskNOT_WAITING_NOTIFICATION ( ( uint8_t ) 0 ) //不等待通知
#define taskWAITING_NOTIFICATION ( ( uint8_t ) 1 ) //等待通知
#define taskNOTIFICATION_RECEIVED ( ( uint8_t ) 2 ) //收到通知,但未处理
通知方式:
/* Actions that can be performed when vTaskNotify() is called. */
typedef enum
{
eNoAction = 0, /* Notify the task without updating its notify value. */
eSetBits, /* Set bits in the task's notification value. */
eIncrement, /* Increment the task's notification value. */
eSetValueWithOverwrite, /* Set the task's notification value to a specific value even if the previous value has not yet been read by the task. */
eSetValueWithoutOverwrite /* Set the task's notification value if the previous value has been read by the task. */
} eNotifyAction;
支持五种操作模式:
eNoAction:仅更新通知状态,不修改值。eSetBits:按位设置(类似事件组)。eIncrement:递增通知值。eSetValueWithOverwrite/eSetValueWithoutOverwrite:覆盖或保留原值。
1. xTaskNotifyGive() / xTaskNotify()
/**************xTaskNotifyGive()实际为函数xTaskGenericNotify()********************/
#define xTaskNotifyGive( xTaskToNotify )
xTaskGenericNotify( ( xTaskToNotify ), ( tskDEFAULT_INDEX_TO_NOTIFY ), ( 0 ), eIncrement, NULL )
/****************xTaskNotify()实际为函数xTaskGenericNotify()*********************/
#define xTaskNotify( xTaskToNotify, ulValue, eAction ) \
xTaskGenericNotify( ( xTaskToNotify ), ( tskDEFAULT_INDEX_TO_NOTIFY ), ( ulValue ), ( eAction ), NULL )
/********************************************************************************/
/*
参数:
1.要送送给谁?->任务句柄
2.写value的位置
3.
4.通知的方式(枚举)
5.以前的通知值存储位置
*/
BaseType_t xTaskGenericNotify( TaskHandle_t xTaskToNotify,
UBaseType_t uxIndexToNotify,
uint32_t ulValue,
eNotifyAction eAction,
uint32_t * pulPreviousNotificationValue )
{
TCB_t * pxTCB;
BaseType_t xReturn = pdPASS;
uint8_t ucOriginalNotifyState;
/*****************断言:1.写value位置不能越界 2.任务句柄不能为空********************/
configASSERT( uxIndexToNotify < configTASK_NOTIFICATION_ARRAY_ENTRIES );
configASSERT( xTaskToNotify );
pxTCB = xTaskToNotify;
taskENTER_CRITICAL(); //进入临界保护区(关中断)
{
/***************************写入之前是否保存原值**********************************/
if( pulPreviousNotificationValue != NULL )
{
*pulPreviousNotificationValue = pxTCB->ulNotifiedValue[ uxIndexToNotify ];
}
/***************************写入之前读出前一个通知的状态**************************/
ucOriginalNotifyState = pxTCB->ucNotifyState[ uxIndexToNotify ];
/***************************写入新的通知状态-RECV********************************/
pxTCB->ucNotifyState[ uxIndexToNotify ] = taskNOTIFICATION_RECEIVED;
/*************************按照不同的通知方式,给value赋值*************************/
/*
1. 类似事件组,'|'形式写入(按位操作)
2. 类似计数信号量,value++
3. 覆盖写入(无论上一个数据是否处理)
4. 覆盖写入(上一个数据未处理不写入且退出,返回错误;已处理则写入)
5. 之前的通知值和新的通知值一样,不用做操作
*/
switch( eAction )
{
case eSetBits:
pxTCB->ulNotifiedValue[ uxIndexToNotify ] |= ulValue;
break;
case eIncrement:
( pxTCB->ulNotifiedValue[ uxIndexToNotify ] )++;
break;
case eSetValueWithOverwrite:
pxTCB->ulNotifiedValue[ uxIndexToNotify ] = ulValue;
break;
case eSetValueWithoutOverwrite:
if( ucOriginalNotifyState != taskNOTIFICATION_RECEIVED )
{
pxTCB->ulNotifiedValue[ uxIndexToNotify ] = ulValue;
}
else
{
/* The value could not be written to the task. */
xReturn = pdFAIL;
}
break;
case eNoAction:
/* The task is being notified without its notify value being
* updated. */
break;
default:
configASSERT( xTickCount == ( TickType_t ) 0 );
break;
}
traceTASK_NOTIFY( uxIndexToNotify );
/**********************************************************************************/
/***************如果接受通知的任务因为等待通知处于阻塞状态,释放它*********************/
if( ucOriginalNotifyState == taskWAITING_NOTIFICATION )
{
listREMOVE_ITEM( &( pxTCB->xStateListItem ) );
prvAddTaskToReadyList( pxTCB );
/* The task should not have been on an event list. */
configASSERT( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) == NULL );
// #if ( configUSE_TICKLESS_IDLE != 0 )
// {
// prvResetNextTaskUnblockTime();
// }
// #endif
/********************依据任务优先级,查看是否需要切换上下文***************************/
if( pxTCB->uxPriority > pxCurrentTCB->uxPriority )
{
/* The notified task has a priority above the currently
* executing task so a yield is required. */
taskYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/**********************************************************************************/
}
else //如果接受通知的任务不阻塞等待,当前任务继续
{
mtCOVERAGE_TEST_MARKER();
}
}
taskEXIT_CRITICAL(); //退出临界区
return xReturn;
}
xTaskNotify()比较灵活,有更多的可配置选项,可以模仿信号量/事件组/消息队列。xTaskNotifyGive()只能模仿信号量。
2. vTaskNotifyGiveFromISR() / xTaskNotifyFromISR()
#define xTaskNotifyFromISR( xTaskToNotify, ulValue, eAction, pxHigherPriorityTaskWoken ) \
xTaskGenericNotifyFromISR( ( xTaskToNotify ), ( tskDEFAULT_INDEX_TO_NOTIFY ), ( ulValue ), ( eAction ), NULL, ( pxHigherPriorityTaskWoken ) )
/***************************
参数:
1.通知任务的句柄
2.写入value/status的位置
3.写入的值
4.写入方式
5.先前值是否保存,保存位置
6.是否唤醒高优先级任务
***************************/
BaseType_t xTaskGenericNotifyFromISR( TaskHandle_t xTaskToNotify,
UBaseType_t uxIndexToNotify,
uint32_t ulValue,
eNotifyAction eAction,
uint32_t * pulPreviousNotificationValue,
BaseType_t * pxHigherPriorityTaskWoken )
{
/*********************以下部分和非中断函数操作一样,参考上面就可以**************************/
TCB_t * pxTCB;
uint8_t ucOriginalNotifyState;
BaseType_t xReturn = pdPASS;
UBaseType_t uxSavedInterruptStatus;
configASSERT( xTaskToNotify );
configASSERT( uxIndexToNotify < configTASK_NOTIFICATION_ARRAY_ENTRIES );
/***************************************************************************************/
portASSERT_IF_INTERRUPT_PRIORITY_INVALID();//检查此中断是否是可管理,如果不是->卡死
pxTCB = xTaskToNotify;
//这句没看懂,好像是提高此中断优先级到可管理的最高优先级
uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
{
/***************************和非终端的函数操作一样,参考上面就行了************************/
if( pulPreviousNotificationValue != NULL )
{
*pulPreviousNotificationValue = pxTCB->ulNotifiedValue[ uxIndexToNotify ];
}
ucOriginalNotifyState = pxTCB->ucNotifyState[ uxIndexToNotify ];
pxTCB->ucNotifyState[ uxIndexToNotify ] = taskNOTIFICATION_RECEIVED;
switch( eAction )
{
case eSetBits:
pxTCB->ulNotifiedValue[ uxIndexToNotify ] |= ulValue;
break;
case eIncrement:
( pxTCB->ulNotifiedValue[ uxIndexToNotify ] )++;
break;
case eSetValueWithOverwrite:
pxTCB->ulNotifiedValue[ uxIndexToNotify ] = ulValue;
break;
case eSetValueWithoutOverwrite:
if( ucOriginalNotifyState != taskNOTIFICATION_RECEIVED )
{
pxTCB->ulNotifiedValue[ uxIndexToNotify ] = ulValue;
}
else
{
/* The value could not be written to the task. */
xReturn = pdFAIL;
}
break;
case eNoAction:
/* The task is being notified without its notify value being
* updated. */
break;
default:
/* Should not get here if all enums are handled.
* Artificially force an assert by testing a value the
* compiler can't assume is const. */
configASSERT( xTickCount == ( TickType_t ) 0 );
break;
}
traceTASK_NOTIFY_FROM_ISR( uxIndexToNotify );
if( ucOriginalNotifyState == taskWAITING_NOTIFICATION )
{
/* The task should not have been on an event list. */
configASSERT( listLIST_ITEM_CONTAINER( &(pxTCB->xEventListItem))==NULL );
/****************************以此网上和非中断得出函数一样******************************/
//如果调度器没有挂起
if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
{
listREMOVE_ITEM( &( pxTCB->xStateListItem ) );
prvAddTaskToReadyList( pxTCB );
}
//如果调度器挂起,DelayList和ReadyList无法访问,放入另一个链表,调度回复后放入
//ReadyList
else
{
listINSERT_END( &( xPendingReadyList ), &( pxTCB->xEventListItem ) );
}
/**************************当前任务和通知任务优先级对比******************************/
if( pxTCB->uxPriority > pxCurrentTCB->uxPriority ) //被通知的任务优先级高
{
if( pxHigherPriorityTaskWoken != NULL )
{
*pxHigherPriorityTaskWoken = pdTRUE; //存储-发生了更高优先级唤醒
}
xYieldPending = pdTRUE;
}
else //被通知任务优先级低
{
mtCOVERAGE_TEST_MARKER(); //继续当前任务
}
}
}
/****************应该是中断优先级降会原来的优先级************************************/
portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );
return xReturn;
}
#define vTaskNotifyGiveFromISR( xTaskToNotify, pxHigherPriorityTaskWoken ) \
vTaskGenericNotifyGiveFromISR( ( xTaskToNotify ), ( tskDEFAULT_INDEX_TO_NOTIFY ), ( pxHigherPriorityTaskWoken ) )
/************就是上面xTaskGenericNotifyFromISR()函数的删减版,参考上面就可以了*********/
void vTaskGenericNotifyGiveFromISR( TaskHandle_t xTaskToNotify,
UBaseType_t uxIndexToNotify,
BaseType_t * pxHigherPriorityTaskWoken )
{
..................................................
}
/***********************************************************************************/
3. xTaskNotifyWait()
#define xTaskNotifyWait( ulBitsToClearOnEntry, ulBitsToClearOnExit, pulNotificationValue, xTicksToWait ) \
xTaskGenericNotifyWait( tskDEFAULT_INDEX_TO_NOTIFY, ( ulBitsToClearOnEntry ), ( ulBitsToClearOnExit ), ( pulNotificationValue ), ( xTicksToWait ) )
/*
参数:
1.等待那个位置的数据
2.进入时清除哪些位的数据
3.退出时清除哪些位的数据
4.当前值存储位置
5.阻塞时间
/*
BaseType_t xTaskGenericNotifyWait( UBaseType_t uxIndexToWait,
uint32_t ulBitsToClearOnEntry,
uint32_t ulBitsToClearOnExit,
uint32_t * pulNotificationValue,
TickType_t xTicksToWait )
{
BaseType_t xReturn;
//断言:当前读取位置是否正确
configASSERT( uxIndexToWait < configTASK_NOTIFICATION_ARRAY_ENTRIES );
//进入临界区-关中断
taskENTER_CRITICAL();
{
/******************************如果没有收到通知****************************************/
if( pxCurrentTCB->ucNotifyState[ uxIndexToWait ] != taskNOTIFICATION_RECEIVED )
{
//清空原先的通知值
pxCurrentTCB->ulNotifiedValue[ uxIndexToWait ] &= ~ulBitsToClearOnEntry;
//将任务通知状态转换为正在等待
pxCurrentTCB->ucNotifyState[ uxIndexToWait ] = taskWAITING_NOTIFICATION;
//如果有等待时间
if( xTicksToWait > ( TickType_t ) 0 )
{
//阻塞任务
prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE );
traceTASK_NOTIFY_WAIT_BLOCK( uxIndexToWait );
portYIELD_WITHIN_API();
}
//如果不等待
else
{
mtCOVERAGE_TEST_MARKER();
}
}
/**********************************收到通知*****************************************/
else
{
mtCOVERAGE_TEST_MARKER();
}
}
taskEXIT_CRITICAL(); //退出临界区
taskENTER_CRITICAL(); //进入临界区
{
traceTASK_NOTIFY_WAIT( uxIndexToWait );
/**************************如果存储等待值位置不为NULL*******************************/
if( pulNotificationValue != NULL )
{
//将任务通知值存入pulNotificationValue
*pulNotificationValue = pxCurrentTCB->ulNotifiedValue[ uxIndexToWait ];
}
/************************如果不是因为收到通知推出的阻塞*****************************/
if( pxCurrentTCB->ucNotifyState[ uxIndexToWait ]!= taskNOTIFICATION_RECEIVED )
{
xReturn = pdFALSE;
}
/************************通知已挂起或在任务等待期间收到通知*************************/
else
{
//清除掉与ulBitsToClearOnExit对应的位
pxCurrentTCB->ulNotifiedValue[ uxIndexToWait ] &= ~ulBitsToClearOnExit;
xReturn = pdTRUE;
}
/*****************************设置任务通知状态************************************/
pxCurrentTCB->ucNotifyState[ uxIndexToWait ] = taskNOT_WAITING_NOTIFICATION;
}
taskEXIT_CRITICAL(); //退出临界区
return xReturn;
}
实现原理
-
发送流程:
- 检查目标任务状态,若为
eWaitingNotification,则唤醒任务。 - 根据
eAction修改ulNotifiedValue。 - 触发任务调度(若优先级更高)。
- 检查目标任务状态,若为
-
接收流程:
- 若通知已到达(
eNotifificationReceived),直接返回通知值。 - 若未到达,任务进入阻塞状态,直到通知到来或超时。
- 若通知已到达(
性能优势
- 速度更快:相比队列,省去了数据拷贝和临界区操作。
- 内存更少:无需额外存储对象,仅占用TCB的少量字段。
- 灵活性: 支持数值传递、位操作和计数信号量模式。
使用场景
-
替代二值信号量:通过
xTaskNotifyGive()实现。// 发送端 xTaskNotifyGive(xTaskHandle); // 接收端 ulTaskNotifyTake(pdTRUE, portMAX_DELAY); -
事件标志组:使用xTaskNotify()实现,由
eSetBits动作。// 发送事件标志 xTaskNotify(xTaskHandle, 0x01, eSetBits); // 等待事件 xTaskNotifyWait(0, 0xFFFFFFFF, &ulValue, portMAX_DELAY);
-
消息队列:使用xTaskNotify()实现,通过
eSetValueWithOverwrite发送32位数据。xTaskNotify(xTaskHandle, data, eSetValueWithOverwrite);
可以参考我另一篇文章:
FreeRTOS任务通知使用以及例程-CSDN博客
注意事项
- 单接收者限制:每个通知只能唤醒一个任务(与消息队列不同)。
- 非队列特性: 仅保存最后一次通知值,历史数据可能丢失。
- ISR安全: 需使用
FromISR版本并处理上下文切换标志。
FreeRTOS任务通知机制详解
1173

被折叠的 条评论
为什么被折叠?



