FreeRTOS 从版本 V8.2.0开始提供任务通知这个功能,每个任务都有一个32位的通知值。按照 FreeRTOS 官方的说法,使用消息通知比通过二进制信号量方式解除阻塞任务快 45%, 并且更加省内存(无需创建队列)。
FreeRTOS 提供以下几种方式发送通知给任务 :
- 发送消息给任务, 如果有通知未读, 不覆盖通知值
- 发送消息给任务,直接覆盖通知值
- 发送消息给任务,设置通知值的一个或者多个位
- 发送消息给任务,递增通知值
通过对以上方式的合理使用,可以在一定场合下替代原本的信号量,队列等。
虽然任务通知速度更快并且占用内存更少,但它也有一些限制:
- 只能有一个任务接收通知事件,通知必须指定任务。
- 接收通知的任务可以因为等待通知而进入阻塞状态,但是发送通知的任务即便不能立即完成通知发送也不能进入阻塞状态。
1. 任务通知 API
FreeRTOS 关于任务通知的 API 如下:
API |
功能 |
xTaskNotifyGive() |
发送通知,没有通知值 (信号量类型) |
ulTaskNotifyTake() |
获取通知,(对应 Give) |
xTaskNotify() |
发送通知, 带通知值 |
xTaskNotifyAndQuery() |
发送通知,带通知值,并且返回原通知值 |
xTaskNotifyWait() |
等待通知 |
vTaskNotifyGiveFromISR() |
xTaskNotifyGive 的中断版本 |
xTaskNotifyAndQueryFromISR() |
xTaskNotifyAndQuery 的中断版本 |
xTaskNotifyFromISR() |
ulTaskNotifyTake 的中断版本 |
xTaskNotifyStateClear() |
清除所有未读消息 |
可能你会想,消息通知就一个发送一个接收 API 不就好了,为什么要搞出这么多个 API ?
实际上, 以上的 API,有的是宏定义,而如此实现是方便特定情况下使用,比如用通知去实现轻量化的二进制信号量,计数信号量,队列等。
2. 数据结构
方便下文叙述,先介绍下实现的相关变量定义。
为了实现任务通知,任务控制块 TCB_t 结构体中有两个任务通知的相关变量, 默认情况下, 任务通知这个功能是打开的,也就是宏 configUSE_TASK_NOTIFICATIONS 设置为 1
#if( configUSE_TASK_NOTIFICATIONS == 1 )
volatile uint32_t ulNotifiedValue;
volatile uint8_t ucNotifyState;
#endif
- 变量 ulNotifiedValue 存储任务通知的数值, 初始化为 0。
- 变量 ucNotifyState 存储当前任务通知的状态,对应存在以下三种状态
// 1 没有未读通知,任务没有等待通知
#define taskNOT_WAITING_NOTIFICATION ( ( uint8_t ) 0 )
// 2 任务等待通知
#define taskWAITING_NOTIFICATION ( ( uint8_t ) 1 )
// 3 通知等待任务读取
#define taskNOTIFICATION_RECEIVED ( ( uint8_t ) 2 )
该变量初始化为 taskNOT_WAITING_NOTIFICATION。
文章开头提到发送任务通知的几种方式,对应系统源码中定义了如下 5 种命令类型 :
typedef enum
{
// 1 发送通知,但是没有更新通知值
eNoAction = 0,
// 2 发送通知,将新通知值与原通知值或操作(置位)
eSetBits,
// 3 发送通知,原通知值加 1
eIncrement,
// 4 发送通知,直接修改通知值(不过上次通知是否已经读取)
eSetValueWithOverwrite,
// 5 发送通知,如果没有未读消息,设置通知值
eSetValueWithoutOverwrite
} eNotifyAction;