FreeRTOS 任务通知浅析
FreeRTOS 任务通知浅析
概述
FreeRTOS 提供了任务间传递信息的机制。任务可以等待一个通知信息进入阻塞状态,并在通知信息到来时自动解除阻塞,进入运行的状态。
任务通知是实现任务之间消息传递的机制之一。通常可以让一个A任务进入等待通知的状态,待B任务通知 A 任务后,A 任务继续执行:
如前所述,任务创建时将分配一块任务控制块(TCB),用于记录该任务的状态、优先级等信息。在该任务控制块中,还记录了与任务通知相关的信息:
volatile uint32_t ulNotifiedValue;// 用来表示通知值
volatile uint8_t ucNotifyState;// 用来表示该任务的通知状态
因此每个任务在创建时都自动地包含一个用来记录当前通知值的 uint32_t 类型的 NotifiedValue,以及一个 记录当前任务通知状态的 uint8_t 类型的 NotifyState。任务之间可以通过更改、查询 NotifiedValue 传递消息。
其中 NotifyState 的取值为:
#define taskNOT_WAITING_NOTIFICATION ( ( uint8_t ) 0 ) /* 初始状态,不使用任务通知 */ #define taskWAITING_NOTIFICATION ( ( uint8_t ) 1 ) /* 使用任务通知,且当前任务处于等待通知的阻塞状态 */ #define taskNOTIFICATION_RECEIVED ( ( uint8_t ) 2 ) /* 使用任务通知,且当前任务处于接收到通知的解除阻塞状态,也称为 "pending"(即解除阻塞,有数据未读) 状态*/
任务通知主要有两组 API 组成:
加减1版本 | 操作任意值版本 | |
---|---|---|
发送通知 | xTaskNotifyGive | xTaskNotify |
接收通知 | xTaskNotifyTake | xTaskNotifyWait |
两组 API 的主要区别在于 Give\Take 版本一次只能对 NotifiedValue 加\减1,而Notify\Wait 版本可以对 NotifiedValue 执行更多的运算。
这些 API 的简介如下:
1)
/ *
该函数调用后的行为:
1)使得通知值加一
2)将对应 Task 由阻塞状态解除为非阻塞状态
*/
xTaskNotifyGive(xTaskToNotify // 要通知的任务的句柄
)
2)
/ *
该函数调用后的行为:
1)进入阻塞,直到接收到通知或者时间超时
2)返回值:函数返回之前,在清零或减一之前的通知值。
*/
ulTaskNotifyTake(xClearCountOnExit, // 退出该函数时如何处理 NotifiedValue,pdTRUE:把通知值清零;pdFALSE:如果通知值大于0,则把通知值减一
xTicksToWait) // 等待通知的时间
3)
/ *
该函数调用后的行为:
1)使得通知值加一
2)将对应 Task 由阻塞状态解除为非阻塞状态
*/
xTaskNotify(xTaskToNotify, // 要通知的任务的句柄
ulValue, // 要通知的任务的NotifiedValue的值
eAction) // 对 NotifiedValue 的处理,详细取值见下
其中 eAction 的行为定义如下:
eNoAction // 仅仅是更新通知状态为"pending"(即解除阻塞),未使用ulValue。这个选项相当于轻量级的、更高效的二进制信号量。
eSetBits // 通知值 = 原来的通知值 | ulValue,按位或。相当于轻量级的、更高效的事件组。
eIncrement // 通知值 = 原来的通知值 + 1,未使用ulValue。相当于轻量级的、更高效的二进制信号量、计数型信号量。相当于xTaskNotifyGive()函数。
eSetValueWithoutOverwrite // 不覆盖。如果通知状态为"pending"(表示有数据未读),则此次调用xTaskNotify不做任何事,返回pdFAIL。如果通知状态不是"pending"(表示没有新数据),则通知值 = ulValue。
eSetValueWithOverwrite // 覆盖。无论如何,不管通知状态是否为"pendng",通知值 = ulValue.
4)
/ *
该函数调用后的行为:
1)使得通知值加一
2)将对应 Task 由阻塞状态解除为非阻塞状态
*/
xTaskNotifyWait(ulBitsToClearOnEntry, /* 进入后,要清除哪些位,通知值 = 通知值 & ~(ulBitsToClearOnEntry)。
比如传入0x01,表示清除通知值的bit0;传入0xffffffff 即ULONG_MAX,表示清除所有位,即把值设置为0 */
ulBitsToClearOnExit, /* 退出时,要清除哪些位,通知值 = 通知值 & ~(ulBitsToClearOnExit)。
在清除某些位之前,通知值先被赋给"*pulNotificationValue"。比如入0x03,表示清除通知值的bit0、bit1;传入0xffffffff即ULONG_MAX,表示清除所有位,即把值设置为0。*/
pulNotificationValue, /* 通知值 = 通知值 & ~(ulBitsToClearOnExit)。
在清除某些位之前,通知值先被赋给"*pulNotificationValue"。比如入0x03,表示清除通知值的bit0、bit1;传入0xffffffff即ULONG_MAX,表示清除所有位,即把值设置为0 */
xTicksToWait) /* 任务进入阻塞态的超时时间 */
需求及功能解析
示例演示了两组任务通知 API 的使用方法。其中 Task1 与 Task3 之间使用 Give\Tick 机制,Task2 和 Task1 之间使用 Notify\Wait 机制。
示例解析
示例输出:
This is esp32 chip with 2 CPU core(s), WiFi/BT/BLE, Minimum free heap size: 295348 bytes
TASK2: task2_flag = 0
TASK1: notify num: 1
TASK3: task3_flag = 0, ret = 1
TASK2: task2_flag = 1
TASK1: notify num: 2
TASK3: task3_flag = 1, ret = 1
TASK2: task2_flag = 2
示例中,三个任务的执行均是 Task2 驱动,只有 Task2 通知 Task1,Task1 才能解除阻塞,运行下面的代码。同理,只有 Task1 通知 Task3 ,Task3 才能解除阻塞,得到执行。因此整体的执行流为 Task2->Task1->Task3。Task1、Task3 完全是“事件”驱动的,并且在任务代码中不含任何延时,只有对应的事件触发后,才能继续向下执行。
讨论
Task Notify,即任务通知的使用方法和 API 还有很多,本小节作为入门篇简介其基本原理和用法。重要的是,读者可以理解这种事件驱动的编程思想,体会任务通知的方法和特点。对于已经熟悉 FreeRTOS 的读者还可能想了解任务通知与其他通信组件的优缺点。
Task Notifications 的优缺点
优点
- 消息传递的更快,效率更高。
- 每个任务创建时会自动创建该通信机制,内存消耗比其它IPC通信相比,明显少很多。
缺点
- 只能从ISR 发送event或者data 到task, 而不能从task发送至ISR.
- 发出的消息只能被一个task接收,但这个限制很少,因为很少有多个task从同一个通信对象获取消息。
- 不能缓冲消息,只能保存一个值。而queue可以
- 不能向多个task广播消息,而event group可以
- 发送方无等待,即task Notification 不会因为taks的状态为正在阻塞而去等待发送完成后,再发送。而是会直接覆盖掉。
总结
1)FreeRTOS 提供了任务间传递信息的机制。任务可以等待一个通知信息进入阻塞状态,并在通知信息到来时自动解除阻塞,进入运行的状态。
2)任务通知是实现任务之间消息传递的机制之一,每个任务在创建时都自动地包含一个用来记录当前通知值的 uint32_t 类型的 NotifiedValue,以及一个 记录当前任务通知状态的 uint8_t 类型的 NotifyState。任务之间可以通过更改、查询 NotifiedValue 传递消息。
3)任务通知机制主要是两组 API:加减1版本的 xTaskNotifyGive\xTaskNotifyTake,操作任意值版本的 xTaskNotify\xTaskNotifyWait。
资源链接
1)Learning-FreeRTOS-with-esp32 系列博客介绍
2)对应示例的 code 链接 (点击直达代码仓库)
3)下一篇: