上一章已经了解了事件标志组,这章学习的依旧是任务之间的通信方式(任务通知)。如果有错误的地方,请批评指正,不胜感激!!
**
提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
本文学习的也是最后一个通信方式,也就是任务通知,也可以称作“通知任务”。本文的重点主要有两点:1.搞明白任务通知的三个状态(实现任务通知的关键),2.明白任务通知的优缺点,以及任务通知模拟出来的队列、信号量、事件组与真实的有何区别
一、任务通知是什么
FreeRTOS中,任务通知机制用于实现任务之间的同步与通信。任务通知可以用来通知其他任务某些事件的发生,例如某个资源的可用性或者某个数据的更新。
任务可以使用xTaskNotify()函数发送任务通知,其他任务可以使用xTaskNotifyWait()函数来等待任务通知。任务通知可以携带一个32位的数值作为参数,可以用来传递通知的具体内容。
任务通知机制可以用于多种场景,包括生产者消费者模型、状态机等。它是一种比较高效的任务间通信方式,相较于使用队列或者消息传递,它对系统资源的占用更少,延迟更小,响应速度更快。
它定义在task.c中,定义在每一个任务的任务控制块内
#if ( configUSE\_TASK\_NOTIFICATIONS == 1 )
volatile uint32\_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
volatile uint8\_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
#endif
#ifndef configTASK\_NOTIFICATION\_ARRAY\_ENTRIES
#define configTASK\_NOTIFICATION\_ARRAY\_ENTRIES 1
#endif
可以看到其实是两个数组,一个存储的是32位的数值,一个存储的是8位的状态,为什么要有状态呢?状态又是什么,有什么作用?我们一一来介绍
二、任务的状态
任务的通知状态:任务通知有三种状态
未等待通知状态:就是任务的初始状态
等待通知状态:当任务在没有通知的时候接收通知时(也就是任务没有接收到通知的时候调用了接收通知的函数,则此时必定接收不到通知,把该任务标记为等待通知状态(去等别的任务发给我通知),任务进入阻塞态),这样做的用处是什么呢? 答:当另外一个任务发通知给该任务时,此时发现任务处于等待通知的状态,然后就可以即可把该任务唤醒。
等待接收通知状态:当有其他任务向任务发送通知,但任务还未接收这一通知的这段期间内(当其他任务给该任务发了通知,但是该任务还没有接收,则将该任务标记为等待接收通知状态),这样做的用处就是当该任务调用了接收通知的函数,发现自身的状态为等待接收通知状态,则不用进入阻塞,直接接收通知值。
为什么要搞一个这样的通知状态?
答:
1.为了判断任务是否接收到了通知
2.不需要一个链表来挂载因等不到通知而阻塞任务,可以直接将任务挂入阻塞链表,因为当调用发送通知函数去唤醒该任务时只需要判断它是否处于等待通知状态(因等待通知进入阻塞)。像队列它有一个当前消息个数的变量可以知道队列中是否有消息,像信号量0就是没消息
那为什么任务通知不能以通知值是否为0判断是否有消息呢?
确实模拟信号量确实是怎么做的,但是如果是模拟队列的话,就不能怎么搞了,因为我发送一个0,0也算是数据,所以需要一个
如果现在还搞不懂这三个状态什么意思,没关系看后面的源码就懂了。
三、任务通知源码分析
任务通知的创建就不用说了,任务被创建时则就便有了任务通知,而且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这个函数
最后
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此收集整理了一份《2024年嵌入式&物联网开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上嵌入式&物联网开发知识点,真正体系化!
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新!!
片转存中…(img-8bh8Cx51-1715759088438)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上嵌入式&物联网开发知识点,真正体系化!
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新!!