FreeRTOS 任务通知
- 任务通知简介
- 一 、发送通知
- 1.1 xTaskNotify()
- 1.2 xTaskNotifyFromISR()
- 1.3 xTaskNotifyGive()
- 1.4 xTaskNotifyAndQuery()
- 1.5 xTaskNotifyAndQueryFromISR()
- 二、接收通知
- 2.1 ulTaskNotifyTake()
- 2.2 xTaskNotifyWait()
- 三、清除通知状态和值
- 3.1 xTaskNotifyStateClear()
- 3.2 ulTaskNotifyValueClear()
- 四、eAction 值和相关操作
FreeRTOS 中的任务通知 (Task Notifications) 是一种极其高效、轻量级的任务间通信 (IPC) 和同步机制。它允许一个任务或中断服务程序 (ISR) 直接向另一个任务发送事件通知,并可选地附带一个 32 位的值。相比于传统的队列、信号量、事件组等机制,任务通知通常更快且占用更少的内存,因为它利用了任务控制块 (TCB) 中已有的字段。
大多数任务间通信方法借助中间对象,如队列、信号量 或 事件组。发送任务写入通信对象,而接收任务从 通信对象中读取。使用直接任务通知时,顾名思义,发送 任务直接向接收任务发送通知,无需借助中间对象, 使用 FreeRTOS 任务通知替代信号量方案, RAM占用更小且速度快了高达 45%。
Each RTOS task has an array of task notifications. Each task notification has a notification state that can be either ‘pending’ or ‘not pending’, and a 32-bit notification value.
每个 RTOS 任务都有一组任务通知,每个任务通知都有一个 通知状态,可以是“待处理”或“未待处理”,以及一个 32 位 通知值。
任务通知简介
- 每个任务拥有一个通知值: 每个任务都有一个 32 位的
ulNotifiedValue
字段(在 TCB 中)。 - 通知状态: 每个任务还有一个通知状态字段 (
ucNotifyState
),可以是:taskNOT_WAITING_NOTIFICATION
: 任务没有在等待通知。taskWAITING_NOTIFICATION
: 任务正在阻塞等待通知 (ulTaskNotifyTake
或xTaskNotifyWait
)。taskNOTIFICATION_RECEIVED
: 任务收到了一个通知(通知值已更新),但任务尚未取走它(对于某些 API 模式)。
- 发送者: 任务或 ISR 通过调用
xTaskNotifyGive()
,vTaskNotifyGiveFromISR()
,xTaskNotify()
,xTaskNotifyFromISR()
,xTaskNotifyAndQuery()
,xTaskNotifyAndQueryFromISR()
等 API 来更新目标任务的ulNotifiedValue
和ucNotifyState
。 - 接收者: 目标任务通过调用
ulTaskNotifyTake()
或xTaskNotifyWait()
来查询、等待并消费通知。
⭐️ 在 FreeRTOS V10.4.0 之前,每项任务只有一个“通知值”, 所有任务通知 API 函数都只能操作这一个值。而从V10.4 开始,任务通知变为 了一组数组,用户可以通过配置
configTASK_NOTIFICATION_ARRAY_ENTRIES
来决定了 每项任务的任务通知数组中的索引数。
在 V10.5.1 tskTaskControlBlock 结构体中可以查看到任务通知变量已变为了一个数组:
#if ( configUSE_TASK_NOTIFICATIONS == 1 )volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
#endif
而在V10.3.1 中,tskTaskControlBlock 中任务通知变量声明如下:
#if( configUSE_TASK_NOTIFICATIONS == 1 )volatile uint32_t ulNotifiedValue;volatile uint8_t ucNotifyState;
#endif
xTaskNotify()
是原始 API 函数, 为保持向后兼容, 调用xTaskNotify()
相当于调用xTaskNotifyIndexed()
, 其uxIndexToNotify
参数设置为 0,其他的函数同此类似。
一 、发送通知
通知发送函数如下:
函数 | 描述 |
---|---|
xTaskNotify() / xTaskNotifyIndexed() | 任务中发送通知,携带通知值并且不保留接收任务原来的通知值 |
xTaskNotifyFromISR() / xTaskNotifyFromISRIndexed() | 中断中发送通知, |
xTaskNotifyGive() / xTaskNotifyGiveFromISR() | 任务中发送通知,不带通知值并且不保留接收任务原来的通知值, |
xTaskNotifyAndQuery() / xTaskNotifyAndQueryIndexed() | 任务中发送通知,带有通知量并且保留接收任务的原来的通知值 |
xTaskNotifyAndQueryFromISR / xTaskNotifyAndQueryFromISRIndexed() | xTaskNotifyAndQuery() 的中断版本 |
1.1 xTaskNotify()
xTaskNotify()
和 xTaskNotifyIndexed()
是等效函数,唯一区别在于 xTaskNotifyIndexed()
可以操作任务通知数组中的任何任务通知,而 xTaskNotify()
总是操作 数组中索引为 0 的任务通知。
BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify, // 任务句柄uint32_t ulValue, // 通知值eNotifyAction eAction ); // 通知状态
参数:
-
xTaskToNotify
接收通知的 RTOS 任务的句柄,通知值会递增。可通过以下方法获取任务句柄: 使用
xTaskCreate()
创建任务,并通过pxCreatedTask
参数获取句柄; 使用xTaskCreateStatic()
创建任务,并返回值作为句柄; 调用xTaskGetHandle()
,通过任务名称获取句柄。当前正在执行的 RTOS 任务的句柄 由xTaskGetCurrentTaskHandle()
API 函数返回。 -
ulValue:用于更新目标任务的通知值。
-
eAction 任务通知更新的方法。枚举类型,可以取下列任一值,以执行相关操作
在task.h
中有如下的定义:
/* Actions that can be performed when vTaskNotify() is called. */
typedef enum
{eNoAction = 0, /* Notify the task without updating its notify value. */eSetBits, /* Set bits in the task's notification value. */eIncrement, /* Increment the task's notification value. */eSetValueWithOverwrite, /* Set the task's notification value to a specific value even if the previous value has not yet been read by the task. */eSetValueWithoutOverwrite /* Set the task's notification value if the previous value has been read by the task. */
} eNotifyAction;
对应的解释如下:
eNoAction | 不会更改通知值(只发信号,不传数据) |
---|---|
eSetBits | 设置通知值的指定bit置一,类似事件组的用法 |
eIncrement | 通知值加一,类似计数信号量 |
eSetValueWithOverwrite | 覆盖之前通知值的方式更新通知值 |
eSetValueWithoutOverwrite | 不覆盖之前的通知值 |
返回值:
如果 eAction
设置为 eSetValueWithoutOverwrite ,且目标任务已有通知pending(待处理),则其通知值不会更新, 以免之前的值在使用前被覆盖。在这种情况下,调用 xTaskNotify() 会失败, 返回 pdFALSE。通过这种方式,RTOS 任务通知机制可以 在长度为 1 的队列上作为 xQueueSend()
的轻量级替代方案。
其他情况下均返回 pdPASS。
BaseType_t xTaskNotifyIndexed( TaskHandle_t xTaskToNotify,UBaseType_t uxIndexToNotify,uint32_t ulValue,eNotifyAction eAction );
xTaskNotifyIndexed
比 xTaskNotify()
多了一个 uxIndexToNotify
的参数,可以指定到目标任务的通知数组具体索引。
1.2 xTaskNotifyFromISR()
BaseType_t xTaskNotifyFromISR( TaskHandle_t xTaskToNotify,uint32_t ulValue,eNotifyAction eAction,BaseType_t *pxHigherPriorityTaskWoken );
作为 xTaskNotify()
的中断版本,可以用在中断函数中。
参数:
-
pxHigherPriorityTaskWoken
*pxHigherPriorityTaskWoken 必须初始化为 pdFALSE。 如果发送通知导致任务解除阻塞,并且解除阻塞的任务的优先级高于当前正在运行的任务, 则 xTaskNotifyFromISR() 会将 *pxHigherPriorityTaskWoken 设置为 pdTRUE。如果 xTaskNotifyFromISR() 将此值设置为 pdTRUE,则应在退出中断前 请求上下文切换。pxHigherPriorityTaskWoken 是可选参数, 可设置为 NULL。
返回值:
如果 eAction
设置为 eSetValueWithoutOverwrite ,且目标任务已有通知在pending(待处理),则其通知值不会更新, 以免之前的值在使用前被覆盖。在这种情况下,调用 xTaskNotify() 会失败, 返回 pdFALSE。通过这种方式,RTOS 任务通知机制可以 在长度为 1 的队列上作为 xQueueSend()
的轻量级替代方案。
其他情况下均返回 pdPASS。
同样的,当任务通知扩展到数组时,下面这个函数和 xTaskNotifyFromISR()
等价。
BaseType_t xTaskNotifyIndexedFromISR( TaskHandle_t xTaskToNotify,UBaseType_t uxIndexToNotify,uint32_t ulValue,eNotifyAction eAction,BaseType_t *pxHigherPriorityTaskWoken );
参数:
-
uxIndexToNotify
指定发送到目标任务的通知数组具体索引处。 uxIndexToNotify 必须小于
configTASK_NOTIFICATION_ARRAY_ENTRIES
。 xTaskNotifyFromISR() 没有此参数,并且总是将通知发送到索引 0。
1.3 xTaskNotifyGive()
xTaskNotifyGive() 宏可视为速度更快的轻量级二进制或计数信号量的替代方案。
⚠️ 当任务通知值用作二进制或计数信号量的等效物时, 接收通知的任务应该使用
ulTaskNotifyTake()
API 函数来等待通知, 而不是使用xTaskNotifyWait()
API 函数。
在任务中发送通知
xTaskNotifyGive()
与 xTaskNotifyGiveIndexed()
是等效宏,唯一区别在于 xTaskNotifyGiveIndexed()
可以操作数组中的任何任务通知,而 xTaskNotifyGive()
总是操作数组中索引为 0 的任务通知。
BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify );
BaseType_t xTaskNotifyGiveIndexed( TaskHandle_t xTaskToNotify, UBaseType_t uxIndexToNotify );
-
xTaskToNotify
接收通知的 RTOS 任务的句柄,通知值会递增。可通过以下方法获取任务句柄: 使用
xTaskCreate()
创建任务,并通过pxCreatedTask
参数获取句柄; 使用xTaskCreateStatic()
创建任务,并返回值作为句柄; 调用xTaskGetHandle()
,通过任务名称获取句柄。当前正在执行的 RTOS 任务的句柄 由xTaskGetCurrentTaskHandle()
API 函数返回。 -
uxIndexToNotify
要向目标任务的通知值数组中发送的通知索引。
uxIndexToNotify
必须小于configTASK_NOTIFICATION_ARRAY_ENTRIES
。xTaskNotifyGive()
没有此参数,并且总是将通知发送到索引 0。
中断版本
void vTaskNotifyGiveFromISR( TaskHandle_t xTaskToNotify,BaseType_t *pxHigherPriorityTaskWoken );void vTaskNotifyGiveIndexedFromISR( TaskHandle_t xTaskHandle, UBaseType_t uxIndexToNotify, BaseType_t *pxHigherPriorityTaskWoken );
可在中断服务程序 (ISR) 中使用的 xTaskNotifyGive()
和 xTaskNotifyGiveIndexed()
版本 。
1.4 xTaskNotifyAndQuery()
BaseType_t xTaskNotifyAndQuery( TaskHandle_t xTaskToNotify,uint32_t ulValue,eNotifyAction eAction,uint32_t *pulPreviousNotifyValue );BaseType_t xTaskNotifyAndQueryIndexed( TaskHandle_t xTaskToNotify,UBaseType_t uxIndexToNotify,uint32_t ulValue,eNotifyAction eAction,uint32_t *pulPreviousNotifyValue );
xTaskNotifyAndQueryIndexed()
执行的操作与 xTaskNotifyIndexed()
相同, 另外还可通过额外的 pulPreviousNotifyValue
参数返回目标任务之前的通知值 (函数被调用时的通知值,而不是函数返回时的通知值) 。
xTaskNotifyAndQuery()
执行的操作与 xTaskNotify()
相同, 另外还可通过额外的 pulPreviousNotifyValue
参数返回目标任务之前的通知值 (函数被调用时的通知值,而不是函数返回时的通知值) 。
参数:
-
pulPreviousNotifyValue
可用于在
xTaskNotifyAndQuery()
修改任何位之前传出目标任务的通知值。pulPreviousNotifyValue
是可选参数,如果不需要,可设置为 NULL。如果不使用pulPreviousNotifyValue
, 可以考虑使用xTaskNotify()
替代xTaskNotifyAndQuery()
。 -
返回值:
如果
eAction
设置为 eSetValueWithoutOverwrite ,且此时目标任务已有的通知pending,则其通知值不会更新, 以免之前的值在使用前被覆盖。在这种情况下,调用xTaskNotify()
会失败, 返回 pdFALSE。通过这种方式,RTOS 任务通知机制可以 在长度为 1 的队列上作为xQueueSend()
的轻量级替代方案。其他情况下均返回 pdPASS。
1.5 xTaskNotifyAndQueryFromISR()
BaseType_t xTaskNotifyAndQueryFromISR(TaskHandle_t xTaskToNotify,uint32_t ulValue,eNotifyAction eAction,uint32_t *pulPreviousNotifyValue,BaseType_t *pxHigherPriorityTaskWoken );BaseType_t xTaskNotifyAndQueryIndexedFromISR(TaskHandle_t xTaskToNotify,UBaseType_t uxIndexToNotifyuint32_t ulValue,eNotifyAction eAction,uint32_t *pulPreviousNotifyValue,BaseType_t *pxHigherPriorityTaskWoken );
xTaskNotifyAndQueryFromISR()
执行的操作与 xTaskNotifyFromISR()
相同, 另外还可通过额外的 pulPreviousNotifyValue
参数返回目标任务之前的通知值 (函数被调用时的通知值,而不是函数返回时的通知值) 。
二、接收通知
任务通知接收函数如下:
函数 | 描述 |
---|---|
ulTaskNotifyTake / ulTaskNotifyTakeIndexed | 获取任务通知,可设置在退出此函数时将任务通知值清零或者减一。当任务通知用作二值信号量或者计数信号量的时候使用此函数来获取信号量。 |
xTaskNotifyWait / xTaskNotifyWaitIndexed | 等待任务通知,功能比 ulTaskNotifyTake() 更齐全。 |
2.1 ulTaskNotifyTake()
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit,TickType_t xTicksToWait );uint32_t ulTaskNotifyTakeIndexed( UBaseType_t uxIndexToWaitOn, BaseType_t xClearCountOnExit, TickType_t xTicksToWait );
ulTaskNotifyTake()
和 ulTaskNotifyTakeIndexed()
是等效的, 唯一的区别 是 ulTaskNotifyTakeIndexed()
可以在操作数组中的任何任务通知, 而 ulTaskNotifyTake()
只能操作数组索引 0 处的任务通知。
参数:
-
uxIndexToWaitOn
调用任务的通知值数组中的索引, 调用任务将在该索引上等待非零通知。
uxIndexToWaitOn
必须小于configTASK_NOTIFICATION_ARRAY_ENTRIES
。xTaskNotifyTake()
没有此参数,默认在索引 0 处等待通知。 -
xClearCountOnExit
如果收到 RTOS 任务通知,且
xClearCountOnExit
设置为pdFALSE
,那么 RTOS 任务的 通知值将在ulTaskNotifyTake()
退出前递减。这相当于 成功调用
xSemaphoreTake()
后,计数信号量的值被递减。如果收到 RTOS 任务通知 且xClearCountOnExit
设置为pdTRUE,则 RTOS 任务的通知值 将在
ulTaskNotifyTake()
退出前重置为 0。这等同于 在成功调用xSemaphoreTake()
后,将二进制信号量的值保留为 0。
-
xTicksToWait
表示如果调用
ulTaskNotifyTake()
时尚未收到通知,在阻塞状态下等待收到通知的最长时间。处于阻塞状态的 RTOS 任务不会消耗 任何 CPU 时间。时间以 RTOS 滴答周期为单位。pdMS_TO_TICKS()
宏可用于 将以毫秒为单位的时间转换为以滴答为单位的时间。
返回:
- 被递减或清除之前的任务通知值的值(原来的任务通知值)
2.2 xTaskNotifyWait()
该函数比 ulTaskNotifyTake()
功能更为强大,不管任务通知用作二值信号量、计数信号量、队列和事件标志组中的哪一种,都可以使用此函数来获取任务通知。
xTaskNotifyWait()
和xTaskNotifyWaitIndexed()
是等效宏,唯一区别在于 xTaskNotifyWaitIndexed()
可以操作数组中的任何任务通知, 而 xTaskNotifyWait()
只能操作数组中索引为 0 的任务通知。
BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry,uint32_t ulBitsToClearOnExit,uint32_t *pulNotificationValue,TickType_t xTicksToWait );BaseType_t xTaskNotifyWaitIndexed( UBaseType_t uxIndexToWaitOn,uint32_t ulBitsToClearOnEntry,uint32_t ulBitsToClearOnExit,uint32_t *pulNotificationValue,TickType_t xTicksToWait );
参数:
-
ulBitsToClearOnEntry
接收任务通知之前,先进行按位清除通知值。当没有接收到任务通知的时候,将任务通知值与此参数的取反值进行按位与运算。当此参数设为0xffffffff 或者 ULONG_MAX 的时候,就会将任务通知值清零。
-
ulBitsToClearOnExit
退出此函数之前,先按位清除通知值。如果接收到任务通知,在退出此函数之前,将任务通知值与此参数的取反值进行按位与运算。当此参数设为0xffffffff 或者 ULONG_MAX 的时候,就会将任务通知值清零。
-
*pulNotificationValue
用于传出 RTOS 任务的通知值,复制到 *pulNotificationValue 的值是 RTOS 任务的通知值,该值是在应用 ulBitsToClearOnExit 设置清除任何位 之前的值。如果无需保存通知值,可以将 pulNotificationValue 设置为 NULL。
-
xTicksToWait 阻塞时间
返回:
如果收到了通知,或者在调用 xTaskNotifyWait()
时通知已经在pending(待处理)状态, 则返回 pdTRUE。
如果调用 xTaskNotifyWait()
超时且在超时前没有收到通知, 则返回 pdFALSE。
三、清除通知状态和值
3.1 xTaskNotifyStateClear()
在 FreeRTOS 中,xTaskNotifyStateClear()
是一个用于管理任务通知状态的重要函数。它的核心作用是:清除目标任务的通知pending (待处理)状态,而不会修改通知值本身。
BaseType_t xTaskNotifyStateClear( TaskHandle_t xTask );BaseType_t xTaskNotifyStateClearIndexed( TaskHandle_t xTask, UBaseType_t uxIndexToClear );
3.2 ulTaskNotifyValueClear()
用于清除任务通知的指定标志位ulBitsToClear
uint32_t ulTaskNotifyValueClear( TaskHandle_t xTask, uint32_t ulBitsToClear );uint32_t ulTaskNotifyValueClearIndexed( TaskHandle_t xTask, UBaseType_t uxIndexToClear,uint32_t ulBitsToClear );
ulTaskNotifyValueClear()
和 ulTaskNotifyValueClearIndexed()
是等效的宏。唯一的区别 是 ulTaskNotifyValueClearIndexed()
可以指定任务通知数组任意索引处, 而ulTaskNotifyValueClear()
始终在数组索引 0 处的任务通知上运行。
- 返回值:
ulBitsToClear
指定位清零前目标任务的通知值。
四、eAction 值和相关操作
-
eNoAction
目标任务接收事件,但其通知值不会更新。在这种情况下, 不会使用 ulValue。
-
eSetBits
目标任务的通知值将与 ulValue 进行按位“或”操作。例如,如果 ulValue 设置为 0x01,则目标任务通知值中的第 0 位将被设置。同样,如果 ulValue 设置为 0x04,则目标任务通知值中的第 2 位将被设置。通过这种方式,RTOS 任务 通知机制可以作为事件组的轻量级替代方案。
-
eIncrement
目标任务的通知值将增加 1,这样调用 xTaskNotifyFromISR() 相当于调用 vTaskNotifyGiveFromISR()。在这种情况下,不会使用 ulValue。
-
eSetValueWithOverwrite
目标任务的通知值无条件设置为 ulValue。通过这种方式,RTOS 任务 通知机制可以作为 xQueueOverwrite() 的轻量级替代方案。
-
eSetValueWithoutOverwrite
如果目标任务当前没有通知pending,则其通知值 将设置为 ulValue。 如果目标任务已有通知在pending,则其通知值 不会更新,以免之前的值在使用前被覆盖。在这种情况下, 调用 xTaskNotify() 会失败,返回 pdFALSE。 通过这种方式,RTOS 任务通知机制可以 在长度为 1 的队列上作为 xQueueSend() 的轻量级替代方案。