1.任务通知本质直接操作目标任务的 TCB 字段。它不自带控制块、不分配独立存储、不维护自己的等待列表——全程只做一件事读写目标任务 TCB 里已有的ulNotifiedValue和ucNotifyState必要时将对方从延迟列表移到就绪列表。正因如此它成为 FreeRTOS 中最快、最省 RAM 的通信机制。1.1 发送通知功能向任务xTaskToNotify发送一个通知并可选择性地更新该任务的通知值。根据eAction的不同可以模拟二值信号量、计数信号量、事件组、消息邮箱等多种行为。如果目标任务正在等待通知则唤醒它。参数xTaskToNotify目标任务句柄。uxIndexToNotify通知索引任务可以有多个通知由configTASK_NOTIFICATION_ARRAY_ENTRIES定义数量。ulValue通知携带的值具体含义由eAction决定。eAction通知动作类型决定如何更新目标任务的通知值eSetBits将ulValue的位与目标任务的通知值进行按位或。eIncrement将目标任务的通知值加 1ulValue被忽略。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 cant 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 tasks 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 通知取出计数模式功能当前任务等待并获取由发送端xTaskNotifyGive或xTaskGenericNotify使用eIncrement生成的计数值。其行为等同于一个专属于本任务的计数信号量 Take 操作。如果当前计数值为 0任务可选择阻塞等待。参数uxIndexToWait要等待的通知索引。xClearCountOnExitpdTRUE返回时清零通知值类似于获取信号量时“拿走所有”计数值。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 tasks 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【位模式】核心定位计数信号量接收端等待通知值可模拟事件组、消息传递匹配发送动作eIncrementeSetBits、eSetValueWithOverwrite、eSetValueWithoutOverwrite、eNoAction数据处理整数计数器清零或减1位掩码或完整数据字按掩码清除返回值通知值本身0 无通知布尔值pdTRUE/pdFALSE值输出方式函数返回值指针参数pulNotificationValue消费方式清零取走全部计数或减1取走一个计数按ulBitsToClearOnExit掩码清除指定位进入前清理无ulBitsToClearOnEntry可先清除旧位典型场景任务间计数、资源管理、事件次数统计多事件位等待、覆盖/非覆盖式写入一个值、传递数据计数场景用Take位操作/数据传递用Wait。2.任务通知模拟信号量2.1 发送通知2.1.1 源码解析功能向指定任务发送一个“计数信号量”通知实现类似xSemaphoreGive的释放操作。该宏是对xTaskGenericNotify的封装每次调用将目标任务的通知值加 1完成一次轻量级的私有计数信号量“Give”。参数xTaskToNotify要通知的目标任务句柄。调用内部固定展开为uxIndexToNotifytskDEFAULT_INDEX_TO_NOTIFY通常为 0使用任务通知的默认槽适用于绝大多数单通知场景。ulValue0。在eIncrement动作下该值被忽略实际效果是对通知值执行操作因此传 0 即可。eActioneIncrement指定通知动作为“递增”专用于实现计数信号量的 Give。pulPreviousNotificationValueNULL表示不关心递增前的旧值无需获取历史计数。【该宏定义具体指向的发送通知函数已在本篇章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表示无限等待。内部固定展开为uxIndexToWaittskDEFAULT_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通知动作枚举决定如何更新目标任务的通知值见上述说明。内部固定展开为uxIndexToNotifytskDEFAULT_INDEX_TO_NOTIFY通常为 0使用任务通知的默认槽。pulPreviousNotificationValueNULL不返回更新前的旧值。【该宏定义具体指向的发送通知函数已在本篇章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与xTaskNotify、xTaskNotifyGive等宏的发送端保持一致。【该宏定义具体指向的接收通知函数已在本篇章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 选型速查表需求场景推荐方案不推荐单任务计数信号量xTaskNotifyGiveulTaskNotifyTake—单任务事件组OR 等待xTaskNotifyeSetBitsxTaskNotifyWait—单任务事件组AND 累积xTaskNotifyeSetBitsxTaskNotifyWait 本地变量—单任务最新值传递xTaskNotifyeSetValueWithOverwritexTaskNotifyWait—中断中发通知vTaskNotifyGiveFromISR/xTaskNotifyFromISR任务版 API多任务等同一事件事件标志组任务通知需要优先级继承互斥量任务通知模拟信号量需要FIFO 缓冲队列任务通知同时等待多个独立对象队列集任务通知4.3 定位任务通知是 FreeRTOS IPCInter-Process Communication进程间通信 工具箱中最轻最快的单线通信手段——专为单接收者、无缓冲、高频场景优化。它不是队列/信号量/事件组的替代品而是它们的互补工具。选型时记住一个原则只要场景中存在“多个接收者”或“需要缓冲历史数据”就回到传统 IPC。5.声明1Copyright (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过程中的源码解读笔记欢迎大家在评论区讨论指正。】【如果本篇内容对你有帮助不妨点个关注你的支持是我持续更新的动力】
FreeRTOS源码解析(9)任务通知
1.任务通知本质直接操作目标任务的 TCB 字段。它不自带控制块、不分配独立存储、不维护自己的等待列表——全程只做一件事读写目标任务 TCB 里已有的ulNotifiedValue和ucNotifyState必要时将对方从延迟列表移到就绪列表。正因如此它成为 FreeRTOS 中最快、最省 RAM 的通信机制。1.1 发送通知功能向任务xTaskToNotify发送一个通知并可选择性地更新该任务的通知值。根据eAction的不同可以模拟二值信号量、计数信号量、事件组、消息邮箱等多种行为。如果目标任务正在等待通知则唤醒它。参数xTaskToNotify目标任务句柄。uxIndexToNotify通知索引任务可以有多个通知由configTASK_NOTIFICATION_ARRAY_ENTRIES定义数量。ulValue通知携带的值具体含义由eAction决定。eAction通知动作类型决定如何更新目标任务的通知值eSetBits将ulValue的位与目标任务的通知值进行按位或。eIncrement将目标任务的通知值加 1ulValue被忽略。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 cant 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 tasks 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 通知取出计数模式功能当前任务等待并获取由发送端xTaskNotifyGive或xTaskGenericNotify使用eIncrement生成的计数值。其行为等同于一个专属于本任务的计数信号量 Take 操作。如果当前计数值为 0任务可选择阻塞等待。参数uxIndexToWait要等待的通知索引。xClearCountOnExitpdTRUE返回时清零通知值类似于获取信号量时“拿走所有”计数值。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 tasks 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【位模式】核心定位计数信号量接收端等待通知值可模拟事件组、消息传递匹配发送动作eIncrementeSetBits、eSetValueWithOverwrite、eSetValueWithoutOverwrite、eNoAction数据处理整数计数器清零或减1位掩码或完整数据字按掩码清除返回值通知值本身0 无通知布尔值pdTRUE/pdFALSE值输出方式函数返回值指针参数pulNotificationValue消费方式清零取走全部计数或减1取走一个计数按ulBitsToClearOnExit掩码清除指定位进入前清理无ulBitsToClearOnEntry可先清除旧位典型场景任务间计数、资源管理、事件次数统计多事件位等待、覆盖/非覆盖式写入一个值、传递数据计数场景用Take位操作/数据传递用Wait。2.任务通知模拟信号量2.1 发送通知2.1.1 源码解析功能向指定任务发送一个“计数信号量”通知实现类似xSemaphoreGive的释放操作。该宏是对xTaskGenericNotify的封装每次调用将目标任务的通知值加 1完成一次轻量级的私有计数信号量“Give”。参数xTaskToNotify要通知的目标任务句柄。调用内部固定展开为uxIndexToNotifytskDEFAULT_INDEX_TO_NOTIFY通常为 0使用任务通知的默认槽适用于绝大多数单通知场景。ulValue0。在eIncrement动作下该值被忽略实际效果是对通知值执行操作因此传 0 即可。eActioneIncrement指定通知动作为“递增”专用于实现计数信号量的 Give。pulPreviousNotificationValueNULL表示不关心递增前的旧值无需获取历史计数。【该宏定义具体指向的发送通知函数已在本篇章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表示无限等待。内部固定展开为uxIndexToWaittskDEFAULT_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通知动作枚举决定如何更新目标任务的通知值见上述说明。内部固定展开为uxIndexToNotifytskDEFAULT_INDEX_TO_NOTIFY通常为 0使用任务通知的默认槽。pulPreviousNotificationValueNULL不返回更新前的旧值。【该宏定义具体指向的发送通知函数已在本篇章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与xTaskNotify、xTaskNotifyGive等宏的发送端保持一致。【该宏定义具体指向的接收通知函数已在本篇章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 选型速查表需求场景推荐方案不推荐单任务计数信号量xTaskNotifyGiveulTaskNotifyTake—单任务事件组OR 等待xTaskNotifyeSetBitsxTaskNotifyWait—单任务事件组AND 累积xTaskNotifyeSetBitsxTaskNotifyWait 本地变量—单任务最新值传递xTaskNotifyeSetValueWithOverwritexTaskNotifyWait—中断中发通知vTaskNotifyGiveFromISR/xTaskNotifyFromISR任务版 API多任务等同一事件事件标志组任务通知需要优先级继承互斥量任务通知模拟信号量需要FIFO 缓冲队列任务通知同时等待多个独立对象队列集任务通知4.3 定位任务通知是 FreeRTOS IPCInter-Process Communication进程间通信 工具箱中最轻最快的单线通信手段——专为单接收者、无缓冲、高频场景优化。它不是队列/信号量/事件组的替代品而是它们的互补工具。选型时记住一个原则只要场景中存在“多个接收者”或“需要缓冲历史数据”就回到传统 IPC。5.声明1Copyright (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过程中的源码解读笔记欢迎大家在评论区讨论指正。】【如果本篇内容对你有帮助不妨点个关注你的支持是我持续更新的动力】