news 2026/5/19 4:04:04

FreeRTOS源码解析(9)任务通知

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
FreeRTOS源码解析(9)任务通知

1.任务通知

本质:直接操作目标任务的 TCB 字段。它不自带控制块、不分配独立存储、不维护自己的等待列表——全程只做一件事:读写目标任务 TCB 里已有的ulNotifiedValueucNotifyState,必要时将对方从延迟列表移到就绪列表。正因如此,它成为 FreeRTOS 中最快、最省 RAM 的通信机制。

1.1 发送通知

功能:向任务xTaskToNotify发送一个通知,并可选择性地更新该任务的通知值。根据eAction的不同,可以模拟二值信号量、计数信号量、事件组、消息邮箱等多种行为。如果目标任务正在等待通知,则唤醒它。

参数

  • xTaskToNotify:目标任务句柄。

  • uxIndexToNotify:通知索引(任务可以有多个通知,由configTASK_NOTIFICATION_ARRAY_ENTRIES定义数量)。

  • ulValue:通知携带的值,具体含义由eAction决定。

  • eAction:通知动作类型,决定如何更新目标任务的通知值:

    • eSetBits:将ulValue的位与目标任务的通知值进行按位或。

    • eIncrement:将目标任务的通知值加 1(ulValue被忽略)。

    • eSetValueWithOverwrite:直接用ulValue覆盖目标任务的通知值。

    • eSetValueWithoutOverwrite:仅在目标任务的通知未被消费(状态不为taskNOTIFICATION_RECEIVED)时,才写入ulValue;否则操作失败返回pdFAIL

    • eNoAction:仅唤醒任务,不修改通知值。

  • pulPreviousNotificationValue:可选指针,用于获取更新前目标任务的通知值(可为NULL)。

返回值

  • pdPASS:操作成功。

  • pdFAIL:操作失败(仅eSetValueWithoutOverwrite时可能失败)。

BaseType_t xTaskGenericNotify( TaskHandle_t xTaskToNotify, UBaseType_t uxIndexToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t * pulPreviousNotificationValue ) { TCB_t * pxTCB; //指向目标任务 TCB 的指针,方便访问。 BaseType_t xReturn = pdPASS; //返回值,初始为 pdPASS,仅在写入失败时修改。 uint8_t ucOriginalNotifyState; //保存目标任务通知更新前的状态,用于判断任务是否正在等待通知,决定是否需要唤醒。 /* 断言检查,需用户自定义实现 */ configASSERT( uxIndexToNotify < configTASK_NOTIFICATION_ARRAY_ENTRIES ); //通知索引范围:必须小于任务通知数组的大小,防止越界访问 configASSERT( xTaskToNotify ); //目标任务有效:xTaskToNotify 不能为 NULL pxTCB = xTaskToNotify; //获取目标任务 TCB taskENTER_CRITICAL(); //程序进入临界区,保证对目标任务 TCB 中通知状态和通知值的修改是原子的,防止任务自身或被中断并发访问 { /* 如果调用者提供了 pulPreviousNotificationValue 指针,则将更新前的通知值写入它。 * 这允许发送方获取接收方之前的状态(例如在模拟计数信号量时,可以获取递增前的计数值) */ if( pulPreviousNotificationValue != NULL ) { *pulPreviousNotificationValue = pxTCB->ulNotifiedValue[ uxIndexToNotify ]; } /* 先保存任务通知的原始状态,用于后续判断是否需要唤醒任务 */ ucOriginalNotifyState = pxTCB->ucNotifyState[ uxIndexToNotify ]; /* 无论之前是什么状态,立即将状态设置为 taskNOTIFICATION_RECEIVED,表示该通知槽已被发送方填充,等待接收方消费。 */ pxTCB->ucNotifyState[ uxIndexToNotify ] = taskNOTIFICATION_RECEIVED; /* 根据 eAction 更新通知值 */ switch( eAction ) { /* 用 ulValue 进行按位或操作。这是模拟事件标志组的核心动作,可以同时“设置”多个事件位。 */ case eSetBits: pxTCB->ulNotifiedValue[ uxIndexToNotify ] |= ulValue; break; /* 忽略 ulValue,将通知值加 1。这是实现计数信号量或任务通知版“Give”操作的基础 */ case eIncrement: ( pxTCB->ulNotifiedValue[ uxIndexToNotify ] )++; break; /* 无条件覆盖通知值。模拟消息邮箱(覆盖旧消息) */ case eSetValueWithOverwrite: pxTCB->ulNotifiedValue[ uxIndexToNotify ] = ulValue; break; /* 仅在没有待消费的通知值时才写入。如果目标已经有一个未消费的通知值,操作失败并返回 pdFAIL。 * 这模拟了非覆盖式消息发送,或轻量级零拷贝消息传递 */ case eSetValueWithoutOverwrite: if( ucOriginalNotifyState != taskNOTIFICATION_RECEIVED ) { pxTCB->ulNotifiedValue[ uxIndexToNotify ] = ulValue; //写入通知值 } else { /* The value could not be written to the task. */ xReturn = pdFAIL; //标记失败 } break; /* 不修改通知值,仅利用后续的唤醒逻辑。可用于单纯的任务唤醒,而不携带任何数据。 */ case eNoAction: /* The task is being notified without its notify value being * updated. */ break; /* 通过 xTickCount == 0 的断言触发异常,防止未定义的枚举值传入。这是一个编译时未知、运行时永远为假的断言,用于捕捉非法枚举。 */ default: /* Should not get here if all enums are handled. * Artificially force an assert by testing a value the * compiler can't assume is const. */ configASSERT( xTickCount == ( TickType_t ) 0 ); //断言检查,需用户自定义实现 break; } traceTASK_NOTIFY( uxIndexToNotify ); //调试宏,需用户自定义实现 /* If the task is in the blocked state specifically to wait for a * notification then unblock it now. */ /* 判断原状态:只有目标任务因等待通知而处于阻塞态(taskWAITING_NOTIFICATION)时才执行唤醒逻辑 */ if( ucOriginalNotifyState == taskWAITING_NOTIFICATION ) { listREMOVE_ITEM( &( pxTCB->xStateListItem ) ); //xStateListItem 用于跟踪任务的阻塞时间;将其从延迟列表中移除,因为任务不再需要超时等待。 prvAddTaskToReadyList( pxTCB ); //将任务加入到对应优先级的就绪列表中,使其可以被调度 /* The task should not have been on an event list. */ /* 断言检查,需用户自定义实现 */ /* 任务等待通知时不应同时在事件列表上(xEventListItem 的容器应为 NULL),因为任务通知不依赖事件组或队列,而是直接通过 TCB 中的状态字和值来通信。 * 这确保任务通知的阻塞是“纯”的,没有混合其他内核对象的等待。 */ configASSERT( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) == NULL ); /* 无滴答空闲模式 */ #if ( configUSE_TICKLESS_IDLE != 0 ) { /* If a task is blocked waiting for a notification then * xNextTaskUnblockTime might be set to the blocked task's time * out time. If the task is unblocked for a reason other than * a timeout xNextTaskUnblockTime is normally left unchanged, * because it will automatically get reset to a new value when * the tick count equals xNextTaskUnblockTime. However if * tickless idling is used it might be more important to enter * sleep mode at the earliest possible time - so reset * xNextTaskUnblockTime here to ensure it is updated at the * earliest possible time. */ /* 如果任务是因为收到通知而被唤醒(而非超时),原来为它设置的超时时间 * 可能还残留在 xNextTaskUnblockTime 中。为防止系统在 tickless 模式下 * 根据这个过期的时间点错误地规划休眠时长,这里重新计算所有阻塞任务中 * 最早的超时时间,确保芯片能尽可能早地进入低功耗模式并在正确的时间点唤醒。 */ prvResetNextTaskUnblockTime(); } #endif /* 如果被唤醒的任务优先级高于当前任务,则在临界区退出后立即触发一次上下文切换 */ if( pxTCB->uxPriority > pxCurrentTCB->uxPriority ) { /* The notified task has a priority above the currently * executing task so a yield is required. */ taskYIELD_IF_USING_PREEMPTION(); //任务切换,由于当前正处于临界区内,该函数只是设置一个 xYieldPending 或直接挂起 PendSV,实际的切换会在临界区退出时发生 } else { mtCOVERAGE_TEST_MARKER(); //代码覆盖率测试,实际为空 } } else { mtCOVERAGE_TEST_MARKER(); //代码覆盖率测试,实际为空 } } taskEXIT_CRITICAL(); //程序退出临界区 return xReturn; //返回 pdPASS 或 pdFAIL,告知调用者操作是否成功 }

1.2 接收通知

1.2.1 通知取出(计数模式)

功能:当前任务等待并获取由发送端(xTaskNotifyGivexTaskGenericNotify使用eIncrement)生成的计数值。其行为等同于一个专属于本任务的计数信号量 Take 操作。如果当前计数值为 0,任务可选择阻塞等待。

参数

  • uxIndexToWait:要等待的通知索引。

  • xClearCountOnExit

    • pdTRUE:返回时清零通知值(类似于获取信号量时“拿走所有”计数值)。

    • pdFALSE:返回时将通知值减 1(类似于获取一个信号量)。

  • xTicksToWait:最大阻塞时间。0 表示非阻塞。

返回值:调用时(或唤醒时)的通知值,即发送端通过eIncrement累计的计数。返回值为 0 通常表示超时(见下文细节)。

uint32_t ulTaskGenericNotifyTake( UBaseType_t uxIndexToWait, BaseType_t xClearCountOnExit, TickType_t xTicksToWait ) { uint32_t ulReturn; //用于暂存获取到的通知值,并在函数结束时返回 /* 断言检查,需用户自定义实现 */ configASSERT( uxIndexToWait < configTASK_NOTIFICATION_ARRAY_ENTRIES ); //确保通知索引不越界 taskENTER_CRITICAL(); //程序进入临界区 { /* Only block if the notification count is not already non-zero. */ /* 检查计数值,判断是否需要阻塞 */ if( pxCurrentTCB->ulNotifiedValue[ uxIndexToWait ] == 0UL ) //通知计数为 0:需要阻塞 { /* Mark this task as waiting for a notification. */ /* 将通知状态设为 taskWAITING_NOTIFICATION,以便发送端在调用 xTaskGenericNotify 时识别并唤醒本任务 */ pxCurrentTCB->ucNotifyState[ uxIndexToWait ] = taskWAITING_NOTIFICATION; /* 判断是否需要等待 */ if( xTicksToWait > ( TickType_t ) 0 ) //需要等待 { /* 将当前任务移入延迟列表,并设置超时时间。 */ /* 第二个参数 pdTRUE 表示任务是因为等待事件(通知)而阻塞,而非纯延时。 */ prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE ); /* 调试宏,需用户自定义实现 */ traceTASK_NOTIFY_TAKE_BLOCK( uxIndexToWait ); /* All ports are written to allow a yield in a critical * section (some will yield immediately, others wait until the * critical section exits) - but it is not something that * application code should ever do. */ portYIELD_WITHIN_API(); //抢占式调度开启下,执行任务切换 } else //不为0,不需要等待 { mtCOVERAGE_TEST_MARKER(); //代码覆盖率测试,实际为空 } } else //通知计数不为 0:不需要阻塞 { mtCOVERAGE_TEST_MARKER(); //代码覆盖率测试,实际为空 } } taskEXIT_CRITICAL(); //程序退出临界区 /* 无论任务是被通知唤醒还是超时唤醒,都会执行这段代码 */ /* 通知唤醒:ulReturn != 0,根据 xClearCountOnExit 清零或减 1 * 超时唤醒:ulReturn == 0,跳过修改,直接重置状态 */ taskENTER_CRITICAL(); //程序进入临界区 { traceTASK_NOTIFY_TAKE( uxIndexToWait ); //调试宏,需用户自定义实现 ulReturn = pxCurrentTCB->ulNotifiedValue[ uxIndexToWait ]; //存储获取到的通知值 if( ulReturn != 0UL ) //通知值不为0 { if( xClearCountOnExit != pdFALSE ) //清除通知值 { pxCurrentTCB->ulNotifiedValue[ uxIndexToWait ] = 0UL; //通知值清0 } else //不清除,减1 { pxCurrentTCB->ulNotifiedValue[ uxIndexToWait ] = ulReturn - ( uint32_t ) 1; //通知值减1 } } else //通知值为0 { mtCOVERAGE_TEST_MARKER(); //代码覆盖率测试,实际为空 } /* 最后将通知状态重置为 taskNOT_WAITING_NOTIFICATION,表示本通知槽已空闲,可以接收下一次通知。这是接收端状态机闭环的关键一步,与发送端将状态设为 taskNOTIFICATION_RECEIVED 形成对称。 */ pxCurrentTCB->ucNotifyState[ uxIndexToWait ] = taskNOT_WAITING_NOTIFICATION; } taskEXIT_CRITICAL(); //程序退出临界区 /* 返回 0 通常表示阻塞超时且期间无通知到来。 * 非阻塞调用(xTicksToWait == 0)且计数为 0 时也会返回 0, * 调用者需根据上下文(如是否传入了超时)区分这两种情况。 */ return ulReturn; }

1.2.2 通知等待(位模式)

功能:当前任务等待指定索引的通知。在等待前可选择性清除通知值的指定位,在被唤醒后可再次选择性清除指定位,并通过指针返回收到的通知值。这是任务通知中最灵活的等待函数,可以模拟事件组等待、消息接收等多种场景。

参数

  • uxIndexToWait:要等待的通知索引。

  • ulBitsToClearOnEntry:进入等待前,需要清除的通知值位掩码(用于清零某些旧状态位,为接收新通知做准备)。

  • ulBitsToClearOnExit:成功收到通知后,退出前需要清除的通知值位掩码(常用于“消费”某些事件位,类似事件组的自动清除)。

  • pulNotificationValue:可选指针,用于接收通知值(可为NULL)。

  • xTicksToWait:最大阻塞时间,0 表示非阻塞。

返回值

  • pdTRUE:收到了通知。

  • pdFALSE:超时未收到通知。

BaseType_t xTaskGenericNotifyWait( UBaseType_t uxIndexToWait, uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t * pulNotificationValue, TickType_t xTicksToWait ) { BaseType_t xReturn; //用于标记等待结果:收到通知为 pdTRUE,超时为 pdFALSE。 /* 断言检查确保通知索引不越界,需用户自定义实现 */ configASSERT( uxIndexToWait < configTASK_NOTIFICATION_ARRAY_ENTRIES ); /* 判断是否已有通知,否则阻塞 */ taskENTER_CRITICAL(); //程序进入临界区 { /* Only block if a notification is not already pending. */ if( pxCurrentTCB->ucNotifyState[ uxIndexToWait ] != taskNOTIFICATION_RECEIVED ) //无通知 { /* Clear bits in the task's notification value as bits may get * set by the notifying task or interrupt. This can be used to * clear the value to zero. */ /* 等待前清除用户指定的位 */ /* 典型用法:调用者先清除某些旧事件位,然后再进入等待,确保不会被上一次通知的残留位干扰。这类似于“先清掉不关心的旧状态,再等新事件” */ pxCurrentTCB->ulNotifiedValue[ uxIndexToWait ] &= ~ulBitsToClearOnEntry; /* Mark this task as waiting for a notification. */ /* 设置等待状态:将通知状态设为 taskWAITING_NOTIFICATION,使发送端能识别并唤醒本任务。 */ pxCurrentTCB->ucNotifyState[ uxIndexToWait ] = taskWAITING_NOTIFICATION; if( xTicksToWait > ( TickType_t ) 0 ) //需要等待 { /* 将当前任务加入延迟列表 */ /* 第二个参数 pdTRUE 表示任务是因为等待事件(通知)而阻塞,而非纯延时。 */ prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE ); traceTASK_NOTIFY_WAIT_BLOCK( uxIndexToWait ); //调试宏,需用户自定义实现 /* All ports are written to allow a yield in a critical * section (some will yield immediately, others wait until the * critical section exits) - but it is not something that * application code should ever do. */ portYIELD_WITHIN_API(); // 在临界区内请求一次上下文切换。切换不会立即发生,而是登记后等到 taskEXIT_CRITICAL() 真正退出临界区时执行。 } else { mtCOVERAGE_TEST_MARKER(); //代码覆盖率测试,实际为空 } } else //有通知 { mtCOVERAGE_TEST_MARKER(); //代码覆盖率测试,实际为空 } } taskEXIT_CRITICAL(); //程序退出临界区 /* 读取结果、判断通知、清除退出位 */ taskENTER_CRITICAL(); //程序进入临界区 { traceTASK_NOTIFY_WAIT( uxIndexToWait ); //调试宏,需用户自定义实现 /* 如果调用者提供了 pulNotificationValue 指针,将当前通知值写入。 */ if( pulNotificationValue != NULL ) { /* Output the current notification value, which may or may not * have changed. */ *pulNotificationValue = pxCurrentTCB->ulNotifiedValue[ uxIndexToWait ]; } /* If ucNotifyValue is set then either the task never entered the * blocked state (because a notification was already pending) or the * task unblocked because of a notification. Otherwise the task * unblocked because of a timeout. */ /* 判断超时还是收到了通知 */ if( pxCurrentTCB->ucNotifyState[ uxIndexToWait ] != taskNOTIFICATION_RECEIVED ) // 未收到通知(超时唤醒,或非阻塞调用且无通知到达) { /* A notification was not received. */ /* 标记未收到通知 */ xReturn = pdFALSE; } else //收到通知 { /* A notification was already pending or a notification was * received while the task was waiting. */ /* 成功收到通知后,清除用户指定的位。 */ pxCurrentTCB->ulNotifiedValue[ uxIndexToWait ] &= ~ulBitsToClearOnExit; xReturn = pdTRUE; //标记为收到 } /* 将通知状态重置为 taskNOT_WAITING_NOTIFICATION,使通知槽恢复空闲,准备接收下一次通知。 * 这与发送端将状态设为 taskNOTIFICATION_RECEIVED 形成对称闭环。 */ pxCurrentTCB->ucNotifyState[ uxIndexToWait ] = taskNOT_WAITING_NOTIFICATION; } taskEXIT_CRITICAL(); //程序退出临界区 return xReturn; //返回接收状态 }

1.2.3 对比总结

🔴ulTaskGenericNotifyTake

【计数模式】

🔵xTaskGenericNotifyWait

【位模式】

核心定位计数信号量接收端等待通知值(可模拟事件组、消息传递)
匹配发送动作eIncrementeSetBitseSetValueWithOverwriteeSetValueWithoutOverwriteeNoAction
数据处理整数计数器,清零或减1位掩码或完整数据字,按掩码清除
返回值通知值本身(0 = 无通知)布尔值pdTRUE/pdFALSE
值输出方式函数返回值指针参数pulNotificationValue
消费方式清零(取走全部计数)或减1(取走一个计数)ulBitsToClearOnExit掩码清除指定位
进入前清理ulBitsToClearOnEntry可先清除旧位
典型场景任务间计数、资源管理、事件次数统计多事件位等待、覆盖/非覆盖式写入一个值、传递数据
计数场景用Take,位操作/数据传递用Wait。

2.任务通知模拟信号量

2.1 发送通知

2.1.1 源码解析

功能:向指定任务发送一个“计数信号量”通知,实现类似xSemaphoreGive的释放操作。该宏是对xTaskGenericNotify的封装,每次调用将目标任务的通知值加 1,完成一次轻量级的私有计数信号量“Give”。

参数

  • xTaskToNotify:要通知的目标任务句柄。

  • 调用内部固定展开为:

    • uxIndexToNotify=tskDEFAULT_INDEX_TO_NOTIFY(通常为 0),使用任务通知的默认槽,适用于绝大多数单通知场景。

    • ulValue=0。在eIncrement动作下该值被忽略,实际效果是对通知值执行++操作,因此传 0 即可。

    • eAction=eIncrement,指定通知动作为“递增”,专用于实现计数信号量的 Give。

    • pulPreviousNotificationValue=NULL,表示不关心递增前的旧值,无需获取历史计数。

【该宏定义具体指向的发送通知函数已在本篇章1.1发送通知中进行了讲解分析】

#define xTaskNotifyGive( xTaskToNotify ) \ xTaskGenericNotify( ( xTaskToNotify ), ( tskDEFAULT_INDEX_TO_NOTIFY ), ( 0 ), eIncrement, NULL )

2.1.2 使用示例

BaseType_t res = 0; /* 发送任务通知 */ res = xTaskNotifyGive(task2_handle); if(res == pdPASS) { printf("task1向task2发送任务通知成功...\r\n"); }

2.2 接收通知

2.2.1 源码解析

功能:当前任务等待并获取一个“计数信号量”通知,实现类似xSemaphoreTake的获取操作。该宏是对ulTaskGenericNotifyTake的封装,用于从默认通知槽中取出计数,完成一次轻量级的私有计数信号量“Take”。

参数

  • xClearCountOnExit:控制消费方式。

    • pdTRUE:返回时将通知值清零,相当于一次性取走所有累积的 Give 次数(“取走全部信号量”)。

    • pdFALSE:返回时将通知值减 1,相当于每次取走一个 Give 次数(“取走一个信号量”),实现经典计数信号量的 P 操作。

  • xTicksToWait:最大阻塞时间(系统节拍数)。若当前通知值为 0,任务将进入阻塞态等待通知到达。传 0 表示非阻塞,portMAX_DELAY表示无限等待。

  • 内部固定展开为:

    • uxIndexToWait=tskDEFAULT_INDEX_TO_NOTIFY(通常为 0),使用任务通知的默认槽,与发送端xTaskNotifyGive使用的默认索引一致。

【该宏定义具体指向的接收通知函数已在本篇章1.2.1通知取出(计数模式)中进行了讲解分析】

#define ulTaskNotifyTake( xClearCountOnExit, xTicksToWait ) \ ulTaskGenericNotifyTake( ( tskDEFAULT_INDEX_TO_NOTIFY ), ( xClearCountOnExit ), ( xTicksToWait ) )

2.2.2 使用示例

uint32_t notify_value = 0; notify_value = ulTaskNotifyTake( pdFALSE, // 接受完通知后,是否对通知置清零: pdTRUE 清零, pdFALSE 不清零,通知值-1 portMAX_DELAY // 等待任务通知的最大阻塞时间 ); printf("Task2接收到通知值=%d\r\n",notify_value);

3.任务通知模拟消息队列/事件标志组

3.1 发送通知

3.1.1 源码解析

功能:向指定任务发送一个通用通知,可模拟事件标志组置位、消息传递、信号量释放等多种操作。该宏是对xTaskGenericNotify的便捷封装,使用默认通知槽,适合单一通知场景下的全功能发送。

参数

  • xTaskToNotify:目标接收任务句柄。

  • ulValue:通知携带的值,其含义由eAction决定:

    • eSetBits:将ulValue作为位掩码与目标任务通知值进行按位或操作,实现事件标志组的“置位”。

    • eSetValueWithOverwrite:用ulValue覆盖目标任务当前通知值,无论旧值是否已被消费,实现覆盖式消息传递。

    • eSetValueWithoutOverwrite:仅当目标任务通知槽空闲(无待消费通知)时,才将ulValue写入,否则操作失败返回pdFAIL,实现非覆盖式消息传递。

    • eIncrement:忽略ulValue,将目标任务通知值加 1,模拟计数信号量 Give。

    • eNoAction:忽略ulValue,不修改通知值,仅唤醒可能阻塞的任务(用于纯唤醒目的)。

  • eAction:通知动作枚举,决定如何更新目标任务的通知值,见上述说明。

  • 内部固定展开为:

    • uxIndexToNotify=tskDEFAULT_INDEX_TO_NOTIFY(通常为 0),使用任务通知的默认槽。

    • pulPreviousNotificationValue=NULL,不返回更新前的旧值。

【该宏定义具体指向的发送通知函数已在本篇章1.1发送通知中进行了讲解分析】

#define xTaskNotify( xTaskToNotify, ulValue, eAction ) \ xTaskGenericNotify( ( xTaskToNotify ), ( tskDEFAULT_INDEX_TO_NOTIFY ), ( ulValue ), ( eAction ), NULL )

3.1.2 使用示例

3.1.2.1 模拟消息队列
BaseType_t res = 0; uint8_t key = 1; /* 发送任务通知 */ res = xTaskNotify( task2_handle, // 接收方的任务句柄 key, // 要发送的通知值 eSetValueWithOverwrite // 写入的行为:强行覆盖 ); if (res == pdPASS) { printf("向task2发送任务通知[%d]成功...\r\n", key); }
3.1.2.2 模拟事件标志组
/* 事件位定义 */ #define EVENTBIT_0 (1 << 0) // 传感器数据就绪 #define EVENTBIT_1 (1 << 1) // 按键按下 /* 发送方1:任务或中断中,通知接收任务 EVENTBIT_0 已发生 */ BaseType_t res; res = xTaskNotify( receiver_task_handle, // 接收方的任务句柄 EVENTBIT_0, // 要设置的事件位 eSetBits // 按位“或”,不影响其他已设置的位 ); if (res == pdPASS) { printf("EVENTBIT_0 置位成功\r\n"); } /* 发送方2:另一任务或中断中,通知接收任务 EVENTBIT_1 已发生 */ res = xTaskNotify( receiver_task_handle, EVENTBIT_1, eSetBits // 同样使用 eSetBits,累积事件 ); if (res == pdPASS) { printf("EVENTBIT_1 置位成功\r\n"); }

3.2 接收通知

3.2.1 源码解析

功能:当前任务在默认通知槽上等待通知,并可选择性在等待前后清除指定位。该宏是对xTaskGenericNotifyWait的封装,用于接收事件标志组置位、消息传递等通用通知,是任务通知最灵活的等待接口。

参数

  • ulBitsToClearOnEntry:进入等待前需清除的通知值位掩码。用于清除旧的、不关心的事件位,确保等待时不被上一次残留事件干扰。若无需清除,传 0。

  • ulBitsToClearOnExit:成功收到通知后需清除的通知值位掩码。用于“消费”已处理的事件位,保留未处理的其他位。若无需清除,传 0。

  • pulNotificationValue:可选指针,用于接收通知值(不含控制位)。即使任务因超时唤醒,该指针指向的值也会被更新(此时有效性需结合返回值判断)。若不需要接收通知值,可传NULL

  • xTicksToWait:最大阻塞时间。若进入等待时无待消费的通知,任务将阻塞等待通知到达。传 0 表示非阻塞,portMAX_DELAY表示无限等待。

  • 内部固定使用默认通知槽tskDEFAULT_INDEX_TO_NOTIFY(通常为 0),与xTaskNotifyxTaskNotifyGive等宏的发送端保持一致。

【该宏定义具体指向的接收通知函数已在本篇章1.2.2通知等待(位模式)中进行了讲解分析】

#define xTaskNotifyWait( ulBitsToClearOnEntry, ulBitsToClearOnExit, pulNotificationValue, xTicksToWait ) \ xTaskGenericNotifyWait( tskDEFAULT_INDEX_TO_NOTIFY, ( ulBitsToClearOnEntry ), ( ulBitsToClearOnExit ), ( pulNotificationValue ), ( xTicksToWait ) )

3.2.2 使用示例

3.2.2.1 模拟消息队列
uint32_t notify_value = 0; BaseType_t res = 0; res = xTaskNotifyWait( 0x00000000, // 接收通知前是否清理通知值,全0,表示32bit的都是0,都不清理 0xffffffff, // 接收到通知值后,是否清理通知值, 全1,表示32bit都是1,都要清零 &notify_value, // 用来保存读取到的通知值 portMAX_DELAY); if (res == pdTRUE) { printf("Task2接收到通知值=%d\r\n", notify_value); }
3.2.2.2 模拟事件标志组
/* 示例1:等待任意事件OR,且不清除 */ uint32_t notify_value; BaseType_t res; /* 等待任意事件位被置位(OR 语义) */ res = xTaskNotifyWait( 0, // ulBitsToClearOnEntry: 进入前不清除任何位 0, // ulBitsToClearOnExit: 退出后也不自动清除 &notify_value, // 获取当前所有事件位 portMAX_DELAY ); if (res == pdTRUE) { if (notify_value & EVENTBIT_0) { printf("处理 EVENTBIT_0\r\n"); /* 处理完后可手动清除该位,通过下一次 xTaskNotifyWait 的 ulBitsToClearOnEntry */ } if (notify_value & EVENTBIT_1) { printf("处理 EVENTBIT_1\r\n"); } } /* 示例2:等待两个事件都发生 AND,累积消费 */ #define ALL_EVENTS (EVENTBIT_0 | EVENTBIT_1) uint32_t accumulated = 0; uint32_t consumed = 0; BaseType_t res; while (accumulated != ALL_EVENTS) { /* * 每次进入等待前,清除上次已消费的位,防止重复处理。 * 退出时不自动清除,因为可能还有其他未处理位需要保留。 */ res = xTaskNotifyWait(consumed, 0, &notify_value, portMAX_DELAY); if (res == pdTRUE) { /* 记录本次唤醒后哪些位被处理了 */ uint32_t this_consumed = 0; if (notify_value & EVENTBIT_0) { printf("处理 EVENTBIT_0\r\n"); this_consumed |= EVENTBIT_0; } if (notify_value & EVENTBIT_1) { printf("处理 EVENTBIT_1\r\n"); this_consumed |= EVENTBIT_1; } accumulated |= this_consumed; consumed = this_consumed; // 下次进入前只清除本次消费过的位 } } printf("两个事件都已发生,期望条件满足\r\n");

4.任务通知的局限性与选型指南

任务通知以速度和 RAM 开销见长,但它用“舍弃通用性”换取了“极致轻量”。理解其边界,才能在选型时做出正确决策。

4.1 五大核心局限

局限性🔴说明🔵典型后果
单一接收者每个通知槽只能被发送方指定的一个任务接收,无法广播。多任务需要等待同一事件时,必须改用事件标志组。
无优先级继承模拟信号量时没有互斥量的优先级继承机制。高优先级任务因等通知而阻塞时,低优先级“持有者”无法被临时提升,可能发生优先级反转。
无法同时等待多槽Take/Wait只指定一个索引,不能像select()那样阻塞在多个通知源上。需多通道监听时,必须改用队列集或将事件合并到同一槽的不同位上。
事件累积需应用层配合AND 累积等待(“事件A和B都发生过”)没有原生支持,需任务自行维护累积变量。增加应用层代码复杂度。
无数据缓冲只有一个uint32_t通知值,没有 FIFO 队列缓冲。非覆盖模式下发送过快会丢失后续数据;覆盖模式下会丢失旧数据。

4.2 选型速查表

需求场景🔴推荐方案🔵不推荐
单任务计数信号量xTaskNotifyGive+ulTaskNotifyTake
单任务事件组(OR 等待)xTaskNotifyeSetBits)+xTaskNotifyWait
单任务事件组(AND 累积)xTaskNotifyeSetBits)+xTaskNotifyWait+ 本地变量
单任务最新值传递xTaskNotifyeSetValueWithOverwrite)+xTaskNotifyWait
中断中发通知vTaskNotifyGiveFromISR/xTaskNotifyFromISR任务版 API
多任务等同一事件事件标志组任务通知
需要优先级继承互斥量任务通知模拟信号量
需要FIFO 缓冲队列任务通知

同时等待

多个独立对象

队列集任务通知

4.3 定位

任务通知是 FreeRTOS IPC(Inter-Process Communication进程间通信) 工具箱中最轻最快的单线通信手段——专为单接收者、无缓冲、高频场景优化。它不是队列/信号量/事件组的替代品,而是它们的互补工具。选型时记住一个原则:只要场景中存在“多个接收者”或“需要缓冲历史数据”,就回到传统 IPC。

5.声明

(1)Copyright (C) 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.

(2)文中代码来自FreeRTOS,遵循MIT许可证,许可证可参考:https://opensource.org/licenses/MIT

/* * FreeRTOS Kernel V10.5.1 * Copyright (C) 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * SPDX-License-Identifier: MIT * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in * the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of * the Software, and to permit persons to whom the Software is furnished to do so, * subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * https://www.FreeRTOS.org * https://github.com/FreeRTOS * */

【以上内容为个人在学习FreeRTOS过程中的源码解读笔记,欢迎大家在评论区讨论指正。】
【如果本篇内容对你有帮助,不妨点个关注,你的支持是我持续更新的动力!】

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/19 4:01:16

汽车冲压件螺母漏装检测|AI 视觉项目从需求到交付全流程

正文汽车零部件是工业 AI 视觉落地最大、最稳定的赛道之一&#xff0c;其中冲压件螺母漏装、错装、偏位检测是自动化集成商和算法工程师接单最多的经典项目。很多新手只会训练模型&#xff0c;却不懂从前期需求对接、现场勘查、方案设计、硬件选型、开发训练、现场联调到最终验…

作者头像 李华
网站建设 2026/5/19 4:01:00

2026年团队原型设计工具选型指南:功能、易用性全面评测

2026年团队原型设计工具的选型&#xff0c;本质上是在协作效率、保真度、设计转代码能力和AI生成效率四个维度上找到最优平衡点。根据 UX Tools Spring 2026 调研报告&#xff08;1,478名设计师参与&#xff09;&#xff0c;53%的团队将"可评估工具太多"列为工作流最…

作者头像 李华
网站建设 2026/5/19 3:49:10

WindowTop(窗口管理增强工具)

链接&#xff1a;https://pan.quark.cn/s/5e199b2a7ca0WindowTop是一款运行在Windows上的窗口管理增强工具&#xff0c;体积小巧&#xff0c;支持Win7及以上的系统&#xff0c;用户可以通过该软件将当前窗口设置在顶部&#xff0c;使其变暗&#xff0c;应用透明性&#xff0c;缩…

作者头像 李华
网站建设 2026/5/19 3:49:04

# Agent 是什么?把它想象成一个人

Agent 是什么&#xff1f;把它想象成一个人 很多人第一次接触 Agent 觉得抽象。 其实它的结构和人体一模一样。如下图所示大脑 LLM 推理核心 人靠大脑思考和决策。Agent 靠 LLM。 用户发来一个问题&#xff0c;LLM 先想&#xff1a;这个问题要怎么解决&#xff1f;需要查数据库…

作者头像 李华
网站建设 2026/5/19 3:49:03

Valmet Metso S420154 PLC机架带背板

Valmet Metso S420154 PLC机架带背板是 Valmet DNA 自动化系统的核心基础设施组件&#xff0c;具备以下 15 条特点&#xff1a;为 I/O 模块提供物理支撑和安装平台。集成电源分配功能&#xff0c;为各模块供电。内置信号通信总线&#xff0c;实现模块间数据交换。插槽容量一般为…

作者头像 李华