Freertos——任务通知

任务通知

任务通知是 FreeRTOS 内置的轻量级任务间同步通信机制,它的内核对象不需要单独创建,因为每个任务的任务控制块中天生自带一个 32 位的通知值和通知状态,其他任务或中断可以直接向指定任务发送通知,目标任务可以阻塞等待通知

它是 FreeRTOS 中性能最高、内存开销最小的 IPC 机制,官方测试数据显示,它比二值信号量的同步速度快约 45%,核心原因是省去了内核对象的创建、链表挂载等额外开销

优势与局限性

任务通知的优势
  • 效率更高:使用任务通知来发送事件、数据给某个任务时,效率更高
  • 更节省内存:使用其他方法时都要先创建对应的结构体,使用任务通知时无需额外创建结构体
  • 功能复用性强:一套机制可模拟二值信号量、计数信号量、事件组、轻量消息邮箱四种常用场景
任务通知的局限性
  • 一对一接收:只有目标任务自己能等待自己的通知,不能多个任务同时等待同一个通知源
  • 无数据缓冲:仅一个 32 位通知值,不支持多条数据排队缓存;未处理的新通知要么覆盖旧值,要么发送失败
  • 发送永不阻塞:发送通知是直接写目标任务的通知值,永远立即返回,发送方不会阻塞
  • 定向发送:发送时必须明确指定目标任务的句柄,不能像信号量那样

任务通知机制

  • 每个任务都有一个结构体:TCB,里面有2个成员:
    一个是uint8_t类型,用来表示通知状态
    一个是uint32_t类型,用来表示通知值
c 复制代码
typedef struct tskTaskControlBlock
{
    ......
    /* configTASK_NOTIFICATION_ARRAY_ENTRIES = 1 */
    volatileuint32_tulNotifiedValue[configTASK_NOTIFICATION_ARRAY_ENTRIES];
    volatileuint8_tucNotifyState[configTASK_NOTIFICATION_ARRAY_ENTRIES];
    ......
}tskTCB;
  • 通知状态有3种取值:
    taskNOT_WAITING_NOTIFICATION:任务没有在等待通知
    taskWAITING_NOTIFICATION:任务在等待通知
    taskNOTIFICATION_RECEIVED:任务接收到了通知

当任务处于未等待状态时,可以自己进入等待状态阻塞,直到接收到了通知退出阻塞同时重新回到未等待状态。

当任务处于未等待状态时,接收到了通知,任务会进入接收状态,当任务自己要进入等待状态时不会阻塞,而是直接回到为等待状态。

任务通知的使用

简化版 专业版
发出通知 xTaskNotifyGive xTaskNotify
中断发出通知 vTaskNotifyGiveFromISR xTaskNotifyFromISR
取出通知 ulTaskNotifyTake xTaskNotifyWait
简化版
c 复制代码
BaseType_t xTaskNotifyGive(TaskHandle_t xTaskToNotify);
voidvTaskNotifyGiveFromISR(TaskHandle_t xTaskHandle,BaseType_t*pxHigherPriorityTa
skWoken);
uint32_tulTaskNotifyTake(BaseType_t xClearCountOnExit,TickType_t xTicksToWait);

在任务中使用xTaskNotifyGive函数,在ISR中使用vTaskNotifyGiveFromISR函数,都是直接给其他任务发送通知:

  • 使得通知值加一
  • 并使得通知状态变为"pending",也就是
    taskNOTIFICATION_RECEIVED,表示有数据了、待处理

可以使用ulTaskNotifyTake函数来取出通知值:

  • 如果通知值等于0,则阻塞(可以指定超时时间)
  • 当通知值大于0时,任务从阻塞态进入就绪态
  • 在ulTaskNotifyTake返回之前,还可以做些清理工作:把通知值减一,或者把通知值清零

xTaskNotifyGive

参数 说明
xTaskToNotify 任务句柄,给哪个任务发通知
返回值 必定返回pdPASS

vTaskNotifyGiveFromISR

参数 说明
xTaskToNotify 任务句柄,给哪个任务发通知
pxHigherPriorityTaskWoken 被通知的任务,可能正处于阻塞状态。此函数发出通知后,会把它从阻塞状态切换为就绪态。如果被唤醒的任务的优先级,高于当前任务的优先级,则"*pxHigherPriorityTaskWoken"被 设 置 为pdTRUE,这表示在中断返回之前要进行任务切换

ulTaskNotifyTake

参数 说明
xClearCountOnExit 函数返回前是否清零:pdTRUE:把通知值清零;pdFALSE:如果通知值大于0,则把通知值减一
xTicksToWait 任务进入阻塞态的超时时间,它在等待通知值大于0。0:不等待,即刻返回;portMAX_DELAY:一直等待,直到通知值大于0;其他值:Tick Count,可以用pdMS_TO_TICKS()把ms转换为Tick Count
返回值 函数返回之前,在清零或减一之前的通知值。如果xTicksToWait非0,则返回值有2种情况:1.大于0:在超时前,通知值被增加了 2.等于0:一直没有其他任务增加通知值,最后超时返回0
专业版
c 复制代码
BaseType_t xTaskNotify(TaskHandle_t xTaskToNotify,uint32_tulValue,eNotifyActio
n eAction);

BaseType_t xTaskNotifyFromISR(TaskHandle_t xTaskToNotify,
uint32_tulValue,
eNotifyAction eAction,
BaseType_t*pxHigherPriorityTaskWoken);

BaseType_t xTaskNotifyWait(uint32_tulBitsToClearOnEntry,
uint32_tulBitsToClearOnExit,
uint32_t*pulNotificationValue,
TickType_t xTicksToWait);

xTaskNotify函数功能更强大,可以使用不同参数实现各类功能,比如:

  • 让接收任务的通知值加一:这时xTaskNotify()等同于xTaskNotifyGive()
  • 设置接收任务的通知值的某一位、某些位,这就是一个轻量级的、更高效的
    事件组
  • 把一个新值写入接收任务的通知值:上一次的通知值被读走后,写入才成
    功。这就是轻量级的、长度为1的队列
  • 用一个新值覆盖接收任务的通知值:无论上一次的通知值是否被读走,覆盖
    都成功。类似xQueueOverwrite()函数,这就是轻量级的邮箱。

xTaskNotify()比xTaskNotifyGive()更灵活、强大,使用上也就更复杂。xTaskNotifyFromISR()是它对应的ISR版本。这两个函数用来发出任务通知,使用哪个函数来取出任务通知呢?使用xTaskNotifyWait()函数!它比ulTaskNotifyTake()更复杂:

  • 可以让任务等待(可以加上超时时间),等到任务状态为"pending"(也就是有数
    据)
  • 还可以在函数进入、退出时,清除通知值的指定位

xTaskNotify

参数 说明
xTaskToNotify 任务句柄,给哪个任务发通知
ulValue 怎么使用ulValue,由eAction参数决定
eAction 见下表
返回值 pdPASS:成功,大部分调用都会成功pdFAIL:只有一种情况会失败,当eAction为eSetValueWithoutOverwrite,并且通知状态为"pending"(表示有新数据未读),这时就会失败

eNotifyAction

eNotifyAction取值 说明
eNoAction 仅仅是更新通知状态为"pending",未使用ulValue。 这个选项相当于轻量级的、更高效的二进制信号量。
eSetBits 通知值=原来的通知值相;当于轻量级的、更高效的事件组。
eIncrement 通知值=原来的通知值+ 1,未使用ulValue。相当于轻量级的、更高效的二进制信号量、计数型信号量。相当于xTaskNotifyGive()函数。
eSetValueWithoutOverwrite 不覆盖。如果通知状态为"pending"(表示有数据未读),则此次调用xTaskNotify不做任何事,返回pdFAIL。如果通知状态不是"pending"(表示没有新数据),则:通知值= ulValue。
eSetValueWithOverwrite 覆盖。无论如何,不管通知状态是否为"pendng",通知值= ulValue。

xTaskNotifyFromISR

跟xTaskNotify很类似,就多 了最后一个参数pxHigherPriorityTaskWoken。在很多ISR函数中,这个参数的作用都是类似的,使用场景如下:

  • 被通知的任务,可能正处于阻塞状态
  • xTaskNotifyFromISR函数发出通知后,会把接收任务从阻塞状态切换为就
    绪态
  • 如果被唤醒的任务的优先级,高于当前任务的优先级,则"*pxHigherPriorityTaskWoken"被设置为pdTRUE,这表示在中断返回之前要进行任务切换。
    xTaskNotifyWait
参数 说明
ulBitsToClearOnEntry 在xTaskNotifyWait入口处,要清除通知值的哪些位?通知状态不是"pending"的情况下,才会清除。它的本意是:我想等待某些事件发生,所以先把"旧数据"的某些位清零。能清零的话:通知值=通知值& ~(ulBitsToClearOnEntry)。比如传入0x01,表示清除通知值的bit0;传入0xffffffff即ULONG_MAX,表示清除所有位,即把值设置为0
ulBitsToClearOnExit 在xTaskNotifyWait出口处,如果不是因为超时推出,而是因为得到了数据而退出时:通知值=通知值& ~(ulBitsToClearOnExit)。在清除某些位之前,通知值先被赋给"*pulNotificationValue"。比如入0x03,表示清除通知值的bit0、bit1;传入0xffffffff即ULONG_MAX,表示清除所有位,即把值设置为0
pulNotificationValue 用来取出通知值。在函数退出时,使用ulBitsToClearOnExit清除之前,把通知值赋给"*pulNotificationValue"。如果不需要取出通知值,可以设为NULL。
xTicksToWait 任务进入阻塞态的超时时间,它在等待通知状态变为"pending"。0:不等待,即刻返回;portMAX_DELAY:一直等待,直到通知状态变为"pending";其他值:Tick Count,可以用pdMS_TO_TICKS()把ms转换为Tick Count
返回值 1. pdPASS:成功这表示xTaskNotifyWait成功获得了通知:可能是调用函数之前,通知状态就是"pending";也可能是在阻塞期间,通知状态变为了"pending"。 2. pdFAIL:没有得到通知。
相关推荐
十月的皮皮19 小时前
stm20260719-STM32F103RCT6_HAL库_三人抢答器
c语言·stm32·单片机·嵌入式硬件·stm32cubemx·hal库
HPT_Lt19 小时前
告别传统二极管损耗!ZCC5050-1替代LM5050-1,冗余电源效率跃升新高度
嵌入式硬件
π同学20 小时前
热插拔控制器 + 外置功率 MOSFET组合电路设计
嵌入式硬件·热插拔·mosfet
wuyk55520 小时前
53.嵌入式开发中栈溢出的深度解析:从HardFault到解决方案
c语言·stm32·单片机
&Hello Code21 小时前
MCU项目软件分层设计规范--以 MCU 项目为例
单片机·嵌入式硬件
piaoyiren1 天前
DS18B20 温度采集 + LCD1602 分页显示
单片机·嵌入式硬件·usart·ds18b20·lcd1602
piaoyiren1 天前
STM32 使用LCD1602简单显示字符
stm32·单片机·嵌入式硬件·lcd1602
小羊先生car1 天前
软件:STM32-F1系列-读取Flash(2026/7/14)
stm32·单片机·嵌入式硬件
weixin_464078072 天前
Altium Designer自定义变压器
嵌入式硬件