FreeRTOS 中的 任务通知(Task Notification) 是一种轻量级的高效通信机制,允许任务之间或中断直接向特定任务发送事件或数据。相比队列、信号量等传统机制,任务通知具有 更快的速度和更低的内存开销(无需额外创建对象),但功能相对受限(如仅支持单向通信)。以下是任务通知相关的 API 函数详解及使用指南。
任务通知的核心特性
- 轻量高效:每个任务自带通知值(32位),无需额外内存分配。
- 多种模式:支持数值更新、事件标志位、计数器等多种操作模式。
- 直接通信:可直接指定目标任务,无需中间对象(如队列)。
- 适用场景:
- 替代二值信号量/计数信号量。
- 轻量级事件标志组。
- 单次数据传输(类似轻量队列)。
任务通知 API 函数详解
1. 发送任务通知
基础发送函数
BaseType_t xTaskNotify(
TaskHandle_t xTaskToNotify, // 目标任务句柄
uint32_t ulValue, // 通知值(或掩码)
eNotifyAction eAction // 操作类型
);
- 功能:向指定任务发送通知,并根据
eAction
更新目标任务的通知值。 - 参数:
eAction
:指定更新方式,可选:eNoAction
:仅触发通知,不修改通知值。eSetBits
:按位或(ulValue
为掩码)。eIncrement
:通知值加 1(ulValue
被忽略)。eSetValueWithOverwrite
:直接覆盖通知值。eSetValueWithoutOverwrite
:仅当通知值未被读取时覆盖。
- 返回值:
pdPASS
:发送成功(某些模式下可能返回失败,如eSetValueWithoutOverwrite
时通知值已存在)。
中断安全版本
BaseType_t xTaskNotifyFromISR(
TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction,
BaseType_t *pxHigherPriorityTaskWoken
);
pxHigherPriorityTaskWoken
:标记是否需触发上下文切换(调用portYIELD_FROM_ISR()
)。
简化发送函数(类似信号量)
void vTaskNotifyGiveFromISR(
TaskHandle_t xTaskToNotify,
BaseType_t *pxHigherPriorityTaskWoken
);
BaseType_t xTaskNotifyGive(TaskHandle_t xTaskToNotify);
- 等效于
xTaskNotify(..., eIncrement)
,用于替代计数信号量。
2. 接收任务通知
等待通知(阻塞)
BaseType_t xTaskNotifyWait(
uint32_t ulBitsToClearOnEntry, // 进入前清除的位掩码
uint32_t ulBitsToClearOnExit, // 退出前清除的位掩码
uint32_t *pulNotificationValue, // 存储接收到的通知值
TickType_t xTicksToWait // 阻塞时间
);
- 功能:等待任务通知到达,并可按掩码清除通知值的某些位。
- 返回值:
pdTRUE
:成功接收到通知。pdFALSE
:超时或任务句柄无效。
快速接收(非阻塞)
uint32_t ulTaskNotifyTake(
BaseType_t xClearCountOnExit, // 是否清零计数器(pdTRUE清零,pdFALSE减1)
TickType_t xTicksToWait // 阻塞时间
);
- 功能:等待通知值递增(类似计数信号量),返回接收前的计数值。
- 典型用途:替代
xSemaphoreTake()
。
3. 查询通知状态
uint32_t ulTaskNotifyValueClear(
TaskHandle_t xTask,
uint32_t ulBitsToClear
);
- 功能:清除目标任务通知值的指定位。
- 示例:
ulTaskNotifyValueClear(NULL, 0xFF)
清除当前任务通知值的低8位。
任务通知使用示例
替代二值信号量
// 任务A发送通知(释放信号量)
void vTaskSender(void *pvParam) {
while (1) {
xTaskNotifyGive(xReceiverTaskHandle); // 等效于释放信号量
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
// 任务B等待通知(获取信号量)
void vTaskReceiver(void *pvParam) {
while (1) {
// 等待通知(类似xSemaphoreTake)
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
// 处理事件...
}
}
传递数据(轻量队列)
// 发送端
void vTaskSender(void *pvParam) {
uint32_t data = 42;
xTaskNotify(xReceiverTaskHandle, data, eSetValueWithOverwrite);
}
// 接收端
void vTaskReceiver(void *pvParam) {
uint32_t receivedData;
xTaskNotifyWait(0, 0, &receivedData, portMAX_DELAY);
// 使用 receivedData...
}
事件标志组
// 设置事件位(发送端)
xTaskNotify(xTaskHandle, (1 << EVENT_BIT), eSetBits);
// 等待事件位(接收端)
uint32_t flags;
xTaskNotifyWait(0, (1 << EVENT_BIT), &flags, portMAX_DELAY);
if (flags & (1 << EVENT_BIT)) {
// 事件触发...
}
注意事项
-
单接收者限制
每个通知只能由一个任务接收,无法像队列一样广播给多个任务。 -
不可在中断中等待通知
任务通知的接收函数(如xTaskNotifyWait
)不能在中断服务程序(ISR)中调用。 -
数据竞争风险
若多次快速发送通知(如使用eSetValueWithOverwrite
),可能导致数据覆盖。需根据场景选择合适的eAction
。 -
替代信号量的限制
- 任务通知无法在多个任务间共享(如信号量可被多个任务获取)。
- 无法查询当前计数值(需自行维护状态)。
-
任务句柄管理
需确保发送方持有正确的目标任务句柄(可通过xTaskGetHandle()
获取)。
任务通知 vs 传统机制
特性 | 任务通知 | 队列/信号量 |
---|---|---|
内存占用 | 无额外内存(内建于任务TCB) | 需要动态/静态分配内存 |
速度 | 更快(直接操作任务TCB) | 较慢(需通过中间对象) |
通信方向 | 单向(一对一) | 多向(多对多) |
功能复杂度 | 简单(轻量级) | 复杂(支持多种同步模式) |
通过合理使用任务通知,可以在资源受限或性能敏感的场景中显著提升效率,但需注意其适用边界,避免滥用导致代码可维护性下降。
ulBitsToClearOnEntry(进入前清除的位掩码):
这是一个位掩码,用于指定在函数开始执行并任务进入阻塞状态之前,应该清除任务通知值中的哪些位。如果某个位在掩码中被设置为1,那么在任务开始等待之前,相应的位会被清除(设置为0)。
例如,如果通知值是 0b10101010,而 ulBitsToClearOnEntry 设置为 0b00001111,那么在任务开始等待之前,通知值会被更新为 0b10100000。
ulBitsToClearOnExit(退出前清除的位掩码):
这同样是一个位掩码,用于指定在任务因为接收到通知而退出阻塞状态之前,应该清除任务通知值中的哪些位。如果某个位在掩码中被设置为1,那么在任务退出等待之前,相应的位会被清除(设置为0)。
例如,如果任务在等待期间收到了一个通知,并且通知值变为 0b11001100,而 ulBitsToClearOnExit 设置为 0b11110000,那么在任务退出等待状态之前,通知值会被更新为 0b00001100。
位掩码:
位掩码是一个用于操作位序列(如一个整数的位)的数值,其中每一位的值(0或1)对应于一个特定的操作或属性。在位掩码中,位设置为1表示需要执行某个操作或检查某个条件,而位设置为0则表示不执行或不检查。
例如,一个8位的位掩码 0b00101010 可以用来表示“操作第2位、第5位,不操作其他位”。
在FreeRTOS的任务通知机制中,位掩码提供了一种灵活的方式来指定哪些特定的通知位是相关的,以及如何在不同的阶段(进入和退出阻塞状态)对这些位进行操作。