目录
1. 概述
1.1 任务通知概念
1.2 任务通知控制结构
2. 发送任务通知
2.1 任务级发送
2.2 中断级发送
2.2.1 xTaskNotifyFromISR函数
2.2.2 vTaskNotifyGiveFromISR函数
3. 获取任务通知
3.1 ulTaskNotifyTake函数
3.2 xTaskNotifyWait函数
4. 任务通知使用场景
4.1 模拟二值信号量
4.2 模拟计数信号量
4.3 模拟消息邮箱
4.4 模拟事件标志组
1. 概述
1.1 任务通知概念
① FreeRTOS从v8.2.0版本开始新增任务通知功能
② 任务通知功能可以在一些场合中替代二值信号量、计数信号量、消息队列和事件标志组,且性能更好(函数调用更少),RAM占用更少(无需新建内核对象)
可以将任务通知机制理解为一种轻量级任务间通信的实现方式
③ 凡事皆有利弊,任务通知机制也有如下限制,
a. 只能有一个任务接收通知消息(之所以叫任务通知,就是只能向指定的任务发送消息)
b. 只有等待通知的任务可以被阻塞,发送通知的任务不会因为发送失败而阻塞(作为比较,消息队列的发送任务也可以设置延时阻塞)
1.2 任务通知控制结构
任务通知作为一种轻量级任务间通信机制,无需新建内核对象,而是在TCB中新增字段,
typedef struct tskTaskControlBlock
{
// 其他字段
#if( configUSE_TASK_NOTIFICATIONS == 1 )
// 任务通知值
volatile uint32_t ulNotifiedValue;
// 任务通知状态
volatile uint8_t ucNotifyState;
#endif
} tskTCB;
说明1:任务通知值ulNotifiedValue
通过对任务通知值的不同使用方法,实现对二值信号量、计数信号量、消息队列和事件标志组的模拟
说明2:任务通知状态ucNotifyState
任务通知状态共有如下3种,定义在tasks.c中,
/* 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 )
说明3:对任务通知值 & 任务通知状态的使用,均需要通过临界段进行保护
2. 发送任务通知
2.1 任务级发送
// xTaskNotify:任务级通用任务通知发送函数
#define xTaskNotify( xTaskToNotify, ulValue, eAction ) \
xTaskGenericNotify( ( xTaskToNotify ), ( ulValue ), ( eAction ), NULL )
// xTaskNotifyAndQuery:任务级返回之前任务通知值的通用任务通知发送函数
#define xTaskNotifyAndQuery( xTaskToNotify, ulValue, eAction, \
pulPreviousNotifyValue ) \
xTaskGenericNotify( ( xTaskToNotify ), ( ulValue ), ( eAction ), \
( pulPreviousNotifyValue ) )
// xTaskNotifyGive:任务级专门用于模拟信号量的任务通知发送函数
#define xTaskNotifyGive( xTaskToNotify ) \
xTaskGenericNotify( ( xTaskToNotify ), ( 0 ), eIncrement, NULL )
可见任务级发送函数都是对xTaskGenericNotify函数的封装,下面就分析该函数,
// xTaskToNotify:要通知的任务
// ulValue:任务通知值
// eAction:任务通知值更新方式,为一个枚举值
// pulPreviousNotificationValue:用于保存通知前的任务通知值
BaseType_t xTaskGenericNotify( TaskHandle_t xTaskToNotify,
uint32_t ulValue, eNotifyAction eAction,
uint32_t *pulPreviousNotificationValue )
{
TCB_t * pxTCB;
BaseType_t xReturn = pdPASS;
uint8_t ucOriginalNotifyState;
configASSERT( xTaskToNotify );
pxTCB = ( TCB_t * ) xTaskToNotify;
// 进入临界段
taskENTER_CRITICAL();
{
// 保存通知前的任务通知值
if( pulPreviousNotificationValue != NULL )
{
*pulPreviousNotificationValue = pxTCB->ulNotifiedValue;
}
// 取出当前任务通知状态,是为了后续判断任务通知状态
ucOriginalNotifyState = pxTCB->ucNotifyState;
// 将任务通知状态设置为接收到通知消息
pxTCB->ucNotifyState = taskNOTIFICATION_RECEIVED;
// 处理不同的任务通知值更新方式
switch( eAction )
{
// 位或设置任务通知值
// 可用于模拟事件标志组
case eSetBits:
pxTCB->ulNotifiedValue |= ulValue;
break;
// 任务通知值递增
// 可用于模拟二值信号量 & 计数信号量
case eIncrement:
( pxTCB->ulNotifiedValue )++;
break;
// 覆盖设置任务通知值
// 可用于模拟长度为1的消息队列,且为覆盖模式
case eSetValueWithOverwrite:
pxTCB->ulNotifiedValue = ulValue;
break;
// 不覆盖设置任务通知值
// 如果之前的任务通知尚未被处理,则此次设置失败
case eSetValueWithoutOverwrite:
if( ucOriginalNotifyState != taskNOTIFICATION_RECEIVED )
{
pxTCB->ulNotifiedValue = ulValue;
}
else
{
// 只有这一种情况会返回pfFAIL
xReturn = pdFAIL;
}
break;
// 不操作任务通知值
case eNoAction:
break;
}
// 如果要通知的任务处于阻塞状态,则将其唤醒
if( ucOriginalNotifyState == taskWAITING_NOTIFICATION )
{
// 任务状态列表项目前处于延时列表或阻塞列表
( void ) uxListRemove( &( pxTCB->xStateListItem ) );
prvAddTaskToReadyList( pxTCB );
// 由于被唤醒的任务在等待任务通知,所以任务事件列表项应该为空,
// 即不应该在等待其他内核对象
configASSERT( listLIST_ITEM_CONTAINER(
&( pxTCB->xEventListItem ) ) == NULL );
#if( configUSE_TICKLESS_IDLE != 0 )
{
// tickless模式下,尽快更新下一任务解锁时间
prvResetNextTaskUnblockTime();
}
#endif
if( pxTCB->uxPriority > pxCurrentTCB->uxPriority )
{
// 如果被唤醒的任务优先级更高,则触发任务调度
taskYIELD_IF_USING_PREEMPTION();
}
}
}
taskEXIT_CRITICAL();
return xReturn;
}
说明1:从xTaskGenericNotify函数的实现可见,任务通知机制的函数调用关系更简单,也不需要遍历等待列表(因为已有明确的唤醒对象),因此效率更高
说明2:任务通知既可以实现基于条件的通知,也可以实现基于资源的通知
2.2 中断级发送
2.2.1 xTaskNotifyFromISR函数
// xTaskNotifyFromISR:中断级通用任务通知发送函数
#define xTaskNotifyFromISR( xTaskToNotify, ulValue, eAction, \
pxHigherPriorityTaskWoken ) \
xTaskGenericNotifyFromISR( ( xTaskToNotify ), ( ulValue ), \
( eAction ), NULL, ( pxHigherPriorityTaskWoken ) )
// xTaskNotifyAndQueryFromISR:中断级返回之前任务通知值的通用任务通知发送函数
#define xTaskNotifyAndQueryFromISR( xTaskToNotify, ulValue, eAction,\
pulPreviousNotificationValue, pxHigherPriorityTaskWoken ) \
xTaskGenericNotifyFromISR( ( xTaskToNotify ), ( ulValue ), ( eAction ), \
( pulPreviousNotificationValue ), ( pxHigherPriorityTaskWoken ) )
中断级通用发送函数都是对xTaskGenericNotifyFromISR函数的封装,
BaseType_t xTaskGenericNotifyFromISR( TaskHandle_t xTaskToNotify,
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 );
// 判断当前中断的有效性,是否在FreeRTOS管理之下
portASSERT_IF_INTERRUPT_PRIORITY_INVALID();
pxTCB = ( TCB_t * ) xTaskToNotify;
// 进入临界段
uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
{
if( pulPreviousNotificationValue != NULL )
{
*pulPreviousNotificationValue = pxTCB->ulNotifiedValue;
}
ucOriginalNotifyState = pxTCB->ucNotifyState;
pxTCB->ucNotifyState = taskNOTIFICATION_RECEIVED;
switch( eAction )
{
case eSetBits:
pxTCB->ulNotifiedValue |= ulValue;
break;
case eIncrement:
( pxTCB->ulNotifiedValue )++;
break;
case eSetValueWithOverwrite:
pxTCB->ulNotifiedValue = ulValue;
break;
case eSetValueWithoutOverwrite:
if( ucOriginalNotifyState != taskNOTIFICATION_RECEIVED )
{
pxTCB->ulNotifiedValue = ulValue;
}
else
{
// 也只有这一种情况返回pdFAIL
xReturn = pdFAIL;
}
break;
case eNoAction:
break;
}
// 至此的处理,中断级与任务级都是相同的
// 差别在于唤醒阻塞任务
if( ucOriginalNotifyState == taskWAITING_NOTIFICATION )
{
configASSERT( listLIST_ITEM_CONTAINER(
&( pxTCB->xEventListItem ) ) == NULL );
if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
{
( void ) uxListRemove( &( pxTCB->xStateListItem ) );
prvAddTaskToReadyList( pxTCB );
}
else
{
// 唤醒任务时,如果调度器被挂起,
// 则将任务事件列表项加入挂起解除就绪列表
vListInsertEnd( &( xPendingReadyList ),
&( pxTCB->xEventListItem ) );
}
if( pxTCB->uxPriority > pxCurrentTCB->uxPriority )
{
// 如果被唤醒的任务优先级更高,则通过出参带出
if( pxHigherPriorityTaskWoken != NULL )
{
*pxHigherPriorityTaskWoken = pdTRUE;
}
else
{
// 为防止用户忽略该出参,通过xYieldPending进行标识
// 这样在SysTick中断中,就可以触发任务调度
// 这里就充分体现了RTOS的实时性
xYieldPending = pdTRUE;
}
}
}
}
portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );
return xReturn;
}
2.2.2 vTaskNotifyGiveFromISR函数
void vTaskNotifyGiveFromISR( TaskHandle_t xTaskToNotify,
BaseType_t *pxHigherPriorityTaskWoken )
{
TCB_t * pxTCB;
uint8_t ucOriginalNotifyState;
UBaseType_t uxSavedInterruptStatus;
configASSERT( xTaskToNotify );
portASSERT_IF_INTERRUPT_PRIORITY_INVALID();
pxTCB = ( TCB_t * ) xTaskToNotify;
uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
{
ucOriginalNotifyState = pxTCB->ucNotifyState;
pxTCB->ucNotifyState = taskNOTIFICATION_RECEIVED;
// 递增任务通知值
( pxTCB->ulNotifiedValue )++;
if( ucOriginalNotifyState == taskWAITING_NOTIFICATION )
{
configASSERT( listLIST_ITEM_CONTAINER(
&( pxTCB->xEventListItem ) ) == NULL );
if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
{
( void ) uxListRemove( &( pxTCB->xStateListItem ) );
prvAddTaskToReadyList( pxTCB );
}
else
{
vListInsertEnd( &( xPendingReadyList ),
&( pxTCB->xEventListItem ) );
}
if( pxTCB->uxPriority > pxCurrentTCB->uxPriority )
{
if( pxHigherPriorityTaskWoken != NULL )
{
*pxHigherPriorityTaskWoken = pdTRUE;
}
else
{
xYieldPending = pdTRUE;
}
}
}
}
portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );
}
是不是看了这个函数也很难理解FreeRTOS的处理方式,只是实现为无返回值,其实有返回值又能怎样呢 ? 猜想是一个历史遗留问题
3. 获取任务通知
说明:获取任务通知只有任务级,没有中断级
3.1 ulTaskNotifyTake函数
ulTaskNotifyTask函数专门用于模拟信号量的任务通知获取函数
// xClearCountOnExit:指定任务通知值消费方式
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit,
TickType_t xTicksToWait )
{
uint32_t ulReturn;
taskENTER_CRITICAL();
{
// 任务通知值为0,说明无有效"信号量"
// 如果设置了等待时间,则进入延时阻塞状态
// 如果通知值不为0,说明有有效"信号量"
// 则直接进入消费信号量的处理环节
if( pxCurrentTCB->ulNotifiedValue == 0UL )
{
// 设置任务通知状态为等待通知
pxCurrentTCB->ucNotifyState = taskWAITING_NOTIFICATION;
if( xTicksToWait > ( TickType_t ) 0 )
{
prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE );
portYIELD_WITHIN_API();
}
}
}
// 如果上文进入了延时阻塞状态,并触发了任务调度
// 在此处退出临界段之后,就会进行任务切换
taskEXIT_CRITICAL();
taskENTER_CRITICAL();
{
// 返回值为消费之前的任务通知值
ulReturn = pxCurrentTCB->ulNotifiedValue;
// 如果有"信号量",则根据设置消费任务通知值
if( ulReturn != 0UL )
{
if( xClearCountOnExit != pdFALSE )
{
// 将任务通知值清零
// 用于模拟二值信号量
pxCurrentTCB->ulNotifiedValue = 0UL;
}
else
{
// 将任务通知值减1
// 用于模拟计数信号量
pxCurrentTCB->ulNotifiedValue = ulReturn - 1;
}
}
// 设置任务通知状态为任务没有在等待通知
pxCurrentTCB->ucNotifyState = taskNOT_WAITING_NOTIFICATION;
}
taskEXIT_CRITICAL();
return ulReturn;
}
3.2 xTaskNotifyWait函数
xTaskNotifyWait函数为通用任务通知获取函数
// ulBitsToClearOnEntry:进入函数时要清除的任务通知值比特位
// ulBitsToClearOnExit:退出函数时要清除的任务通知值比特位
// pulNotificationValue:保存消费前的任务通知值
BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry,
uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue,
TickType_t xTicksToWait )
{
BaseType_t xReturn;
taskENTER_CRITICAL();
{
// 如果任务尚未接收到通知,且设置了等待时间
// 则任务进入延时阻塞状态
if( pxCurrentTCB->ucNotifyState != taskNOTIFICATION_RECEIVED )
{
pxCurrentTCB->ulNotifiedValue &= ~ulBitsToClearOnEntry;
pxCurrentTCB->ucNotifyState = taskWAITING_NOTIFICATION;
if( xTicksToWait > ( TickType_t ) 0 )
{
prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE );
portYIELD_WITHIN_API();
}
}
}
taskEXIT_CRITICAL();
taskENTER_CRITICAL();
{
// 返回消费前的任务通知值
if( pulNotificationValue != NULL )
{
*pulNotificationValue = pxCurrentTCB->ulNotifiedValue;
}
// xReturn返回的是是否接收到通知消息
if( pxCurrentTCB->ucNotifyState == taskWAITING_NOTIFICATION )
{
xReturn = pdFALSE;
}
else
{
// 根据设置,消费任务通知值
pxCurrentTCB->ulNotifiedValue &= ~ulBitsToClearOnExit;
xReturn = pdTRUE;
}
pxCurrentTCB->ucNotifyState = taskNOT_WAITING_NOTIFICATION;
}
taskEXIT_CRITICAL();
return xReturn;
}
说明:xTaskNotifyWait函数可以用于模拟消息队列和事件标志组
4. 任务通知使用场景
4.1 模拟二值信号量
// 获取二值信号量
u32 NotifyValue = 0;
NotifyValue = ulTaskNotifyTake(pdTRUE, timeToWait);
if (NotifyValue > 0)
{
// 说明获取到的信号量,而不是等待超时
}
// 释放二值信号量
xTaskNotifyGive 或 vTaskNotifyGiveFromISR
说明1:使用任务通知模拟二值信号量的语义与真实的二值信号量是一致的,虽然释放信号量的一端可以多次释放,但是消费信号量的任务会将任务通知值清零,因此只会处理一次
而真实的二值信号量,则是多次释放会失败,因为消息队列的长度只有1
说明2:虽然使用通用任务通知函数也能模拟二值信号量,但是系统提供了模拟函数,为啥不直接使用呢
从获取信号量的操作分析,调用xTaskNotifyWait函数实现二值信号量的语义反而比较麻烦,不如直接调用ulTaskNotifyTask函数
而使用xTaskNotifyWait函数还无法实现计数信号量的语义
4.2 模拟计数信号量
// 获取计数信号量
// 只会将任务通知值减1
u32 NotifyValue = 0;
NotifyValue = ulTaskNotifyTake(pdFALSE, timeToWait);
if (NotifyValue > 0)
{
// 说明获取到的信号量,而不是等待超时
}
// 释放计数信号量
xTaskNotifyGive 或 vTaskNotifyGiveFromISR
说明:使用任务通知模拟计数信号量,没有真实信号量的最大值限制(使用上限为uint32_t类型最大值)
4.3 模拟消息邮箱
任务通知也可以用来向任务发送数据,相较于消息队列,使用任务通知向任务发送消息有如下限制,
① 只能发送32位的数据值(也就是任务通知值)
② 消息被保存为任务的任务通知值,而且一次只能保存一个任务通知值,相当于队列长度为1,所以被称作消息邮箱(而非消息队列)
// 等待消息
BaseType_t err = pdFALSE;
err = xTaskNptifyWait((uint32_t)0x00, // 进入函数时,不清除任务通知值比特位
(uint32_t)0xFFFFFFFF, // 退出函数时,清除任务通知值所有比特位
(uint32_t *)¬ifyValue, // 保存接收到的消息值
(TickType_t)timeToWait); // 阻塞时间
if (err == pdTRUE)
{
// 说明接收到任务通知
}
// 发送消息
BaseType_t err = pdFALSE;
err = xTaskNotify((TaskHandle_t)handle, // 要通知的任务句柄
(uint32_t)value, // 任务通知值
eSetValueWithOverWrite); // 覆写方式,也可以使用非覆写方式
if (err == pfFAIL)
{
// 说明任务通知失败
}
4.4 模拟事件标志组
当任务通知用作事件标志组时,任务通知值就相当于事件组,任务通知值的每个bit用作事件标志位
#define EVENT_0 (1 << 0)
#define EVENT_1 (1 << 1)
// 设置事件标志位
xTaskNotify((TaskHandle_t)handle,
(uint32_t)EVENT_0,
eSetBits);
xTaskNotify((TaskHandle_t)handle,
(uint32_t)EVENT_0,
eSetBits);
// 等待事件标志
BaseType_t err = pdFALSE;
err = xTaskNotifyWait((uint32_t)0x00, // 进入函数时,不清除任务通知值比特位
(uint32_t)0xFFFFFFFF, // 退出函数时,清除任务通知值所有比特位
(uint32_t *)¬ifyValue, // 保存接收到的消息值
(TickType_t)timeToWait); // 阻塞时间
if (err == pdTRUE)
{
// 说明接收到事件
// 此时要判断发生的是哪些事件
}
说明:在使用任务通知模拟事件标志组时,功能相对较弱,不能设置"逻辑与" & "逻辑或"类型的等待,而是需要用户自行处理事件发生后的逻辑
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)