写在前面:RTOS任务通知与内存优化入门
在嵌入式实时操作系统(RTOS)领域,队列和信号量几乎是开发者绕不开的核心概念,尤其是对初学者而言,掌握它们是入门必备技能。
不过,大多数场景下,大家习惯借助“中介对象”完成任务间通信,而非直接传递消息。所谓中介对象,就是队列、信号量这类中间环节:发送任务把数据写入中介,接收任务从中读取。每一组中介对象都会分配独立的内存区域,例如消息缓冲区或流缓冲区。问题随之而来:当队列或信号量数量增多时,内存开销也会显著攀升。而采用“直接消息”通信模式,则能有效节省RAM资源。
什么是直接任务通知?
在FreeRTOS中,队列通信是一个典型例子。创建队列前,需要先定义队列句柄:
QueueHandle_t xQueue; xQueue = xQueueCreate(10, sizeof( /* 长度 */ ) );
该队列背后挂载着一系列中介对象,其结构如下:

不妨算一笔账:这样一套中介对象结构,会占用多少RAM?从代码逻辑理解中介对象通信的过程,可以这样描述:

而直接任务通知则简洁得多——顾名思义,发送任务直接将通知发送给接收任务,中间不再经过任何中介。其原理示意如下:

从FreeRTOS V10.4.0版本起,每个任务都自带一套完整的通知机制。每条通知包含一个32位值和一个布尔状态,总共仅占用5字节的RAM空间。具体来说,就像任务可以阻塞等待二进制信号量变为“可用”状态一样,任务也能阻塞等待通知变为“待处理”状态。同样,任务可以阻塞等待计数信号量的计数值非零,也能阻塞等待通知的值非零——下面的第一个示例正是演示这一场景。通知不仅用于传递事件,还能通过多种方式传输数据。
进一步分析直接任务通知
对比FreeRTOS V10.4.0与早期版本,你会发现新版增加了多个API,例如ulTaskNotifyTake / ulTaskNotifyTakeIndexed:

官网对这些API及其使用代码示例都有详细的介绍与说明:

使用直接任务通知:性能优势与使用限制
任务通知的灵活性体现在:它完全可以替代以往需要单独创建队列、二进制信号量、计数信号量或事件组的场景。根据官方数据,与使用信号量这类中介对象来解除任务阻塞相比,采用直接通知的效率可提升45%,并且占用的RAM也大幅减少。然而,这些性能优势也伴随着一定的使用限制:
首先,只有当事件仅有一个接收任务时,才能使用RTOS任务通知。幸运的是,大多数实际场景都满足这一条件——例如在中断中让某个任务处理数据,整个链路自始至终只有一个接收者。其次,如果用它替代队列:接收任务可以在阻塞状态下等待通知(不占用CPU),但发送任务无法在阻塞状态下等待消息。如果发送无法立即完成,则发送操作会失败。
使用方法
使用方法并不复杂,只要熟悉RTOS的队列和信号量,查看官方示例基本就能快速上手。下面直接引用官方例子加以说明:
/* main() 创建的两个任务的原型 */ static void prvTask1( void *pvParameters ); static void prvTask2( void *pvParameters );
