三、任务通知源码分析
任务通知的创建就不用说了,任务被创建时则就便有了任务通知,而且FreeRTOS默认任务通知是开启的。
1.xTaskNotify函数
这个函数其实也是个宏定义,实际上调用的是xTaskGenericNotify这个函数
#define xTaskNotify( xTaskToNotify, ulValue, eAction ) \
xTaskGenericNotify( ( xTaskToNotify ), ( tskDEFAULT\_INDEX\_TO\_NOTIFY ), ( ulValue ), ( eAction ), NULL )
具体的原码介绍,看中文注释
#if ( configUSE\_TASK\_NOTIFICATIONS == 1 )//配置任务通知相关宏
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 获得任务的原始状态
ucOriginalNotifyState = pxTCB->ucNotifyState[ uxIndexToNotify ];
//任务的状态设置为接收通知状态
pxTCB->ucNotifyState[ uxIndexToNotify ] = taskNOTIFICATION_RECEIVED;
switch( eAction )
/\*eAction具有五个模式typedef enum
{
eNoAction = 0, /\* 通知任务而不更新其通知值
eSetBits, /\* 在任务的通知值中设置位。
eIncrement, /\* 增加任务的通知值。
eSetValueWithOverwrite, /\* 将任务的通知值设置为特定值,即使任务尚未读取以前的值。
eSetValueWithoutOverwrite /\* 如果任务已读取前一个值,则设置任务的通知值。
} eNotifyAction;
\*/
{
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 )
//如果原始的任务状态就是等待通知状态,就立刻从阻塞链表中移除加入就绪链表中
{
( void ) uxListRemove( &( pxTCB->xStateListItem ) );
prvAddTaskToReadyList( pxTCB );
/\*该任务不应出现在事件列表中。 \*/
configASSERT( listLIST\_ITEM\_CONTAINER( &( pxTCB->xEventListItem ) ) == NULL );
#if ( configUSE\_TICKLESS\_IDLE != 0 )
{
/\* 如果任务被阻止等待通知,则xNextTaskUnblockTime可能会设置为被阻止任务的超时时间。如果由于超时以外的原因取消阻止任务,xNextTaskUnblockTime通常保持不变,因为当勾号计数等于xNextTTaskUnblocktime时,它将自动重置为新值。然而,如果使用了无障碍空转,那么尽早进入睡眠模式可能更为重要——因此,请在此处重置xNextTaskUnblockTime,以确保其尽早更新。 \*/
prvResetNextTaskUnblockTime();
}
#endif
if( pxTCB->uxPriority > pxCurrentTCB->uxPriority )
{
/\* 通知的任务的优先级高于当前执行的任务,实现任务切换 \*/
taskYIELD\_IF\_USING\_PREEMPTION();
}
else
{
mtCOVERAGE\_TEST\_MARKER();
}
}
else
{
mtCOVERAGE\_TEST\_MARKER();
}
}
taskEXIT\_CRITICAL();
return xReturn;
}
#endif
其实也不难理解,前面的部分中通过switch case分支对任务的值进行了修改,之后就是对状态的判断,如果任务原始状态就是等待通知,此时发送了通知给他,则立刻将这个任务从阻塞态唤醒,并且修改它的任务状态。
2.xTaskNotifyWait函数
这个函数也是一个宏定义,实际调用的是xTaskGenericNotifyWait这个函数
#define xTaskNotifyWait( ulBitsToClearOnEntry, ulBitsToClearOnExit, pulNotificationValue, xTicksToWait ) \
xTaskGenericNotifyWait( tskDEFAULT\_INDEX\_TO\_NOTIFY, ( ulBitsToClearOnEntry ), ( ulBitsToClearOnExit ), ( pulNotificationValue ), ( xTicksToWait ) )
xTaskGenericNotifyWait介绍一下它的参数:
uxIndexToWait:要等待的通知的索引,使用相同的索引与xTaskGenericNotify()函数发出的通知相对应;
ulBitsToClearOnEntry:在等待过程中要清除的通知位,在等待开始时使用,可以防止在等待之前已经发生的通知导致等待提前结束;
ulBitsToClearOnExit:在退出等待状态时要清除的通知位,使用该参数可以确保在通知发送时只提供一个等待任务许可。如果该参数是0,则预期任意通知都可以退出该函数;
pulNotificationValue:指向存储前一个通知值的指针,如果不使用,则可以将其设置为0;
xTicksToWait:等待任务通知的时间,如果等待超时,则函数返回pdFAIL。
以下为源码介绍:
#if ( configUSE\_TASK\_NOTIFICATIONS == 1 )
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 );
if( pulNotificationValue != NULL )//判断前一个通知值是否为空
{
/\* 覆盖当前通知值,该值可能已更改,也可能未更改 \*/
\*pulNotificationValue = pxCurrentTCB->ulNotifiedValue[ uxIndexToWait ];
}
/\* 如果设置了ucNotifyValue,则任务从未进入阻止状态(因为通知已挂起),或者由于通知而取消阻止任务。否则,由于超时,任务被取消阻止。 \*/
if( pxCurrentTCB->ucNotifyState[ uxIndexToWait ] != taskNOTIFICATION_RECEIVED )//判断当前任务的状态是否为接收状态,如果不等于接收状态,则返回pdFALSE,表示并未收到通知值
{
/\* 未收到通知\*/
xReturn = pdFALSE;
}
else
{
/\* 通知已挂起,或者在任务等待时收到通知。 \*/
pxCurrentTCB->ulNotifiedValue[ uxIndexToWait ] &= ~ulBitsToClearOnExit;
xReturn = pdTRUE;
}
//将状态设置为taskNOT\_WAITING\_NOTIFICATION
pxCurrentTCB->ucNotifyState[ uxIndexToWait ] = taskNOT_WAITING_NOTIFICATION;
}
taskEXIT\_CRITICAL();
return xReturn;
}
#endif
直接看代码注释,一定要接收/发送两个通知函数一起分析,这样理解起来轻松多了。
最后
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此收集整理了一份《2024年嵌入式&物联网开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上嵌入式&物联网开发知识点,真正体系化!
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新!!
片转存中…(img-n2HP3yVf-1715759053209)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上嵌入式&物联网开发知识点,真正体系化!
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新!!