FreeRTOS的学习系列文章目录
FreeRTOS的学习(一)——STM32上的移植问题
FreeRTOS的学习(二)——任务优先级问题
FreeRTOS的学习(三)——中断机制
FreeRTOS的学习(四)——列表
FreeRTOS的学习(五)——系统延时
FreeRTOS的学习(六)——系统时钟
FreeRTOS的学习(七)——1.队列概念
FreeRTOS的学习(七)——2.队列入队源码分析
FreeRTOS的学习(七)——3.队列出队源码分析
FreeRTOS的学习(八)——1.二值信号量
FreeRTOS的学习(八)——2.计数型信号量
FreeRTOS的学习(八)——3.优先级翻转问题
FreeRTOS的学习(八)——4.互斥信号量
FreeRTOS的学习(九)——软件定时器
FreeRTOS的学习(十)——事件标志组
FreeRTOS的学习(十一)——任务通知
目录
前言
FreeRTOS可以使用任务通知(Task Notifictions)这个功能来代替信号量、消息队列、事件标志组等。
1 任务通知简介
FreeRTOS 的每个任务都有一个 32 位的通知值,任务控制块中的成员变量 ulNotifiedValue就是这个通知值。任务通知是一个事件,假如某个任务通知的接收任务因为等待任务通知而阻塞的话,向这个接收任务发送任务通知以后就会解除这个任务的阻塞状态。也可以更新接收任务的任务通知值,任务通知可以通过如下方法更新接收任务的通知值:
- 不覆盖接收任务的通知值(如果上次发送给接收任务的通知还没被处理)。
- 覆盖接收任务的通知值。
- 更新接收任务通知值的一个或多个 bit。
- 增加接收任务的通知值。
其实可以发现任务通知的功能是揉合了队列、信号量、事件标志组等功能的。所以其在功能上也有相似之处。
优点:
- 合理、灵活的使用任务通知可以在一些场合替代队列、二值信号量、计数型信号量和事件标志组。在官方的测试结果中,任务通知功能在解除任务阻塞的时间上比直接使用二值信号量快45%。故其可以提高速度,减少RAM的使用。
- 并且任务通知是一个函数,不需要构造队列等操作。任务通知的发送使用函数完成,该通知值会抑制保存着,直到接收任务调用函数获取通知值。
缺点:
- FreeRTOS 的任务通知只能有一个接收任务,其实大多数的应用都是这种情况。因此其实任务通知是单对单的,不像信号量可以单对多。
- 接收任务可以因为接收任务通知而进入阻塞态,但是发送任务不会因为任务通知发送失败而阻塞。
2 发送任务通知
2.1 简介
任务通知发送函数有 6 个:
- xTaskNotify
发送通知,带有通知值并且不保留接收任务原通知值,用在任务中。- xTaskNotifyFromISR
发送通知,函数 xTaskNotify()的中断版本。- xTaskNotifyGive
发送通知,不带通知值并且不保留接收任务的通知值,此函数会将接收任务的通知值加一,用于任务中。- vTaskNotifyGiveFromISR
发送通知,函数 xTaskNotifyGive()的中断版本。- xTaskNotifyAndQuery
发送通知,带有通知值并且保留接收任务的原通知值,用在任务中。- xTaskNotiryAndQueryFromISR
发送通知,函数 xTaskNotifyAndQuery()的中断版本,用在中断服务函数中。
2.2 xTaskGenericNotify源码分析
对于任务级任务通知函数实质上都是调用通用发送函数——xTaskGenericNotify。
该函数原型为:
BaseType_t xTaskGenericNotify( TaskHandle_t xTaskToNotify, //任务句柄
UBaseType_t uxIndexToNotify, //任务通知值中的索引,xTaskNotifyGive中没有这个参数,总数发送0代替该索引。可以设置每个任务通知数组的最大索引值(configTASK_NOTIFICATION_ARRAY_ENTRIES),从而使得每个任务有多个通知值。
uint32_t ulValue, //任务通知值
eNotifyAction eAction, //任务通知更新方式
uint32_t *pulPreviousNotificationValue ) //保存更新前的任务通知值
其中关于eAction(任务通知更新方法),其是个枚举类型,定义如下:
typedef enum
{
eNoAction = 0, /* 通知任务且不更新其通知值 */
eSetBits, /* 更新通知值指定的bit */
eIncrement, /* 通知值加一 */
eSetValueWithOverwrite, /* 覆写的方式更新通知值 */
eSetValueWithoutOverwrite /* 不覆写通知值,如果任务通知值没有更新成功会返回pdFAIL */
} eNotifyAction;
xTaskGenericNotify函数的详细代码如下:
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;
configASSERT( uxIndexToNotify < configTASK_NOTIFICATION_ARRAY_ENTRIES );
configASSERT( xTaskToNotify );
pxTCB = xTaskToNotify;
taskENTER_CRITICAL();
{
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
{
xReturn = pdFAIL;
}
break;
case eNoAction:
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( pxTCB->uxPriority > pxCurrentTCB->uxPriority )
{
/* 解除阻塞的任务优先级比当前运行的任务优先级高,所以需要进行任务切换 */
taskYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
taskEXIT_CRITICAL();
return xReturn;
}
上述代码的流程如下:
- 首先需要判断参数pulPreviousNotificationValue 是否有效,因为此参数用来保存更新前的任务通知值。
1.1. 如果参数 pulPreviousNotificationValue 有效的话就用此参数保存更新前的任务通知值。- 保存任务通知状态,因为下面会修改这个状态,后面我们要根据这个状态来确定是否将任务从阻塞态解除。
- 更新任务通知状态为 taskNOTIFICATION_RECEIVED。
- 根据不同的更新方式做不同的处理。
4.1. 如果为 eSetBits 的话就将指定的 bit 置 1。也就是更新接收任务通知值的一个或多个 bit。
4.2. 如果更新方式为 eIncrement 的话就将任务通知值加一。
4.3. 如果更新方式为 eSetValueWithOverwrite 的话就直接覆写原来的任务通知值。
4.4. 如果更新方式为 eSetValueWithoutOverwrite 的话就需要判断原来的任务通知值是否被处理,如果已经被处理了就更新为任务通知值(任务通知值处理改变状态是在接收任务通知的函数部分处理的)。如果此前的任务通知值话没有被处理的话就标记 xReturn 为 pdFAIL,后面会返回这个值。- 根据步骤3中保存的接收任务之前的状态值来判断是否有任务需要解除阻塞,如果在任务通知值被更新前任务处于 taskWAITING_NOTIFICATION 状态的话就说明任务之前一直出于等待通知的状态,此时判断任务的状态,使该任务解除阻塞。
- 将任务从状态列表中移除。
- 将任务重新添加到就绪列表中。
- 、判断刚刚解除阻塞的任务优先级是否比当前正在运行的任务优先级高,如果是的话需要进行一次任务切换。
- 返回 xReturn 的值,pdFAIL或 pdPASS。
3 获取任务通知
3.1 简介
- ulTaskNotifyTake
获取任务通知,可以设置在退出此函数的时候将任务通知值清零或者减一。当任务通知用作二值信号量或者计数信号量的时候使用此函数来获取信号量。其主要作用的函数为ulTaskGenericNotifyTake。- xTaskNotifyWait
等待任务通知,比 ulTaskNotifyTak()更为强大,全功能版任务通知获取函数。其主要作用的函数为xTaskGenericNotifyWait。
3.2 ulTaskGenericNotifyTake源码分析
ulTaskGenericNotifyTake函数原型为:
uint32_t ulTaskGenericNotifyTake( UBaseType_t uxIndexToWait, //ulTaskNotifyTake中,该值始终为0,ulTaskNotifyTakeIndexed函数中,该值对应某个非0索引值,即等待对应的索引下的通知值过来。
BaseType_t xClearCountOnExit, //参数为 pdFALSE 的话在退出函数 ulTaskNotifyTake的时候任务通知值减一,类似计数型信号量。当此参数为 pdTRUE 的话在退出函数的时候任务任务通知值清零,类似二值信号量。
TickType_t xTicksToWait ) //阻塞时间
ulTaskGenericNotifyTake函数的详细代码如下:
uint32_t ulTaskGenericNotifyTake( UBaseType_t uxIndexToWait,
BaseType_t xClearCountOnExit,
TickType_t xTicksToWait )
{
uint32_t ulReturn;
configASSERT( uxIndexToWait < configTASK_NOTIFICATION_ARRAY_ENTRIES );
taskENTER_CRITICAL();
{
/* 仅在对应索引的通知值为零时任务才会进入阻塞. */
if( pxCurrentTCB->ulNotifiedValue[ uxIndexToWait ] == 0UL )
{
pxCurrentTCB->ucNotifyState[ uxIndexToWait ] = taskWAITING_NOTIFICATION;
if( xTicksToWait > ( TickType_t ) 0 )
{
prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE );
traceTASK_NOTIFY_TAKE_BLOCK( uxIndexToWait );
//进行一次任务调度
portYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
taskEXIT_CRITICAL();
taskENTER_CRITICAL();
{
traceTASK_NOTIFY_TAKE( uxIndexToWait );
ulReturn = pxCurrentTCB->ulNotifiedValue[ uxIndexToWait ];
if( ulReturn != 0UL )
{
if( xClearCountOnExit != pdFALSE )
{
pxCurrentTCB->ulNotifiedValue[ uxIndexToWait ] = 0UL;
}
else
{
pxCurrentTCB->ulNotifiedValue[ uxIndexToWait ] = ulReturn - ( uint32_t ) 1;
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
pxCurrentTCB->ucNotifyState[ uxIndexToWait ] = taskNOT_WAITING_NOTIFICATION;
}
taskEXIT_CRITICAL();
return ulReturn;
}
上述代码的流程如下:
- 首先对应index下任务通知值是否为 0,如果为 0 的话说明还没有接收到任务通知。
1.1. 修改任务通知状态为 taskWAITING_NOTIFICATION。
1.2. 如果阻塞时间不为 0 的话就将任务添加到延时列表中,并且进行一次任务调度。如何解除阻塞?在发送任务通知处进行处理的。- 如果任务通知值不为 0 的话就先获取对应index下的任务通知值。
- 任务通知值不为 0时进入if( ulReturn != 0UL )。
3.1. 参数 xClearCountOnExit 不为 pdFALSE,那就将任务通知值清零。
3.2. 如果参数 xClearCountOnExit 为 pdFALSE 的话那就将任务通知值减一。- 更新任务通知状态为 taskNOT_WAITING_NOTIFICATION。
- 注意接收函数没有任务句柄的输入,所以接收函数放在哪个任务中执行就是针对哪个任务进行任务通知。这点与发送不同,所以发送可以有中断级方式。
3.3 xTaskGenericNotifyWait源码分析
xTaskGenericNotifyWait也是用来获取任务通知的,不过此函数比 ulTaskNotifyTake()更为强大,不管任务通知用作二值信号量、计数型信号量、队列和事件标志组中的哪一种,都可以使用此函数来获取任务通知。但是当任务通知用作二值信号量和计数型信号量的时候推荐使用函数ulTaskNotifyTake,因为相对简单一些。
xTaskGenericNotifyWait函数原型为:
BaseType_t xTaskGenericNotifyWait( UBaseType_t uxIndexToWait,
uint32_t ulBitsToClearOnEntry, //当没有接收到任务通知的时候将任务通知值与此参数的取反值进行按位与运算,当此参数为 0xffffffff或者 ULONG_MAX 的时候就会将任务通知值清零。
uint32_t ulBitsToClearOnExit, //如果接收到了任务通知,在做完相应的处理退出函数之前将任务通知值与此参数的取反值进行按位与运算,当此参数为 0xffffffff 或者ULONG_MAX 的时候就会将任务通知值清零。
uint32_t * pulNotificationValue,
TickType_t xTicksToWait )
xTaskGenericNotifyWait函数的详细代码如下:
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();
{
/* 当对应index的通知值还未收到时才会阻塞. */
if( pxCurrentTCB->ucNotifyState[ uxIndexToWait ] != taskNOTIFICATION_RECEIVED )
{
/* Clear bits in the task's notification value as bits may get
* set by the notifying task or interrupt. This can be used to
* clear the value to zero. */
pxCurrentTCB->ulNotifiedValue[ uxIndexToWait ] &= ~ulBitsToClearOnEntry;
/* Mark this task as waiting for a notification. */
pxCurrentTCB->ucNotifyState[ uxIndexToWait ] = taskWAITING_NOTIFICATION;
if( xTicksToWait > ( TickType_t ) 0 )
{
prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE );
traceTASK_NOTIFY_WAIT_BLOCK( uxIndexToWait );
/* All ports are written to allow a yield in a critical
* section (some will yield immediately, others wait until the
* critical section exits) - but it is not something that
* application code should ever do. */
portYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
taskEXIT_CRITICAL();
taskENTER_CRITICAL();
{
traceTASK_NOTIFY_WAIT( uxIndexToWait );
if( pulNotificationValue != NULL )
{
*pulNotificationValue = pxCurrentTCB->ulNotifiedValue[ uxIndexToWait ];
}
if( pxCurrentTCB->ucNotifyState[ uxIndexToWait ] != taskNOTIFICATION_RECEIVED )
{
/* A notification was not received. */
xReturn = pdFALSE;
}
else
{
/* A notification was already pending or a notification was
* received while the task was waiting. */
pxCurrentTCB->ulNotifiedValue[ uxIndexToWait ] &= ~ulBitsToClearOnExit;
xReturn = pdTRUE;
}
pxCurrentTCB->ucNotifyState[ uxIndexToWait ] = taskNOT_WAITING_NOTIFICATION;
}
taskEXIT_CRITICAL();
return xReturn;
}
上述代码的流程如下:
- 首先对应index下任务通知未收到时,会进入if( pxCurrentTCB->ucNotifyState[ uxIndexToWait ] != taskNOTIFICATION_RECEIVED )。
1.1. 将任务通知值与参数 ulBitsToClearOnEntry 的取反值进行按位与运算,进行清零操作。
1.2. 任务通知状态改为 taskWAITING_NOTIFICATION。
1.3. 如果阻塞时间大于 0 的话就要将任务添加到延时列表中,并且进行一次任务切换。- 如果任务通知状态为 taskNOTIFICATION_RECEIVED,并且参数 pulNotificationValue有效的话就保存任务通知值。
- 如果任务通知的状态又不等于taskNOTIFICATION_RECEIVED 的话就标记 xReturn 为pdFALSE。
- 如果任务通知的状态一直为 taskNOTIFICATION_RECEIVED 的话就将任务通知的值与参数 ulBitsToClearOnExit 的取反值进行按位与运算,并且标记 xReturn 为 pdTRUE,表示获取任务通知成功。
- 标记任务通知的状态为 taskNOT_WAITING_NOTIFICATION。
4 总结
值得说明的是,新版本的任务通知的发送和获取都添加了index功能,即多出了一个任务可以添加多个通知值的功能。该功能在发送函数中也提到了。index数组的最大值configTASK_NOTIFICATION_ARRAY_ENTRIES默认设置为1,故此时只有在index设置为0时才不会报错。可以通过设置configTASK_NOTIFICATION_ARRAY_ENTRIES,来调整数组大小。
另外,当任务通知用于做计数型信号量时,只能用于时间计数,因为他的信号量模拟只能出现在单对单任务中,而无法像真正的计数型信号量那样可以用于多个任务访问单个任务时的那种资源管理的效果。