目录
- 1.特性
-
- [1.1 优势以及限制](#1.1 优势以及限制)
- [1.2 TCB结构体中的存储](#1.2 TCB结构体中的存储)
- 2.函数
-
- [2.1 两类函数](#2.1 两类函数)
- [2.2 简化版](#2.2 简化版)
-
- [2.2.1 Give](#2.2.1 Give)
- [2.2.2 Take](#2.2.2 Take)
- [2.3 专业版](#2.3 专业版)
-
- [2.3.1 Give](#2.3.1 Give)
- [2.3.2 Take](#2.3.2 Take)
- 3.实现轻量级信号量
- 4.实现轻量级队列
- 5.实现轻量级事件组
- 6.源码分析
-
- [6.1 等待通知](#6.1 等待通知)
- [6.2 发送通知](#6.2 发送通知)

所谓"任务通知",你可以反过来读"通知任务"。发送者和接收者是多对1的关系
我们使用队列、信号量、事件组等等方法时,并不知道对方是谁。使用任务通知时,可以明确指定:通知哪个任务。
使用队列、信号量、事件组时,我们都要事先创建对应的结构体,双方通过中间的结构体通信:

使用任务通知时,任务结构体TCB中就包含了内部对象,可以直接接收别人发过来的"通知":
1.特性
任务通知(Task Notification)是 FreeRTOS 为任务间通信和同步提供的一种轻量级机制,其主要特点在于直接嵌入在任务的控制块(TCB)中,不需要额外创建队列、信号量或事件组结构,因此具有高效率和低内存占用的优势。
1.1 优势以及限制
优势:
- 任务通知操作直接更新目标任务的TCB中的通知值和状态,省去了复杂的数据拷贝和对象管理过程。
- 相比于队列、信号量或事件组,任务通知所需的CPU周期更少,适用于需要频繁发送通知的场景。
- 任务通知的数据(通常为一个32位整数)嵌入在任务的TCB中,不需要额外分配单独的结构体内存。
- 这对于嵌入式系统或资源受限的环境尤为重要,可以减少系统整体内存开销。
限制:
- 不能发送数据给ISR:
-
- 由于 ISR 没有自己的 TCB,因此无法直接使用任务通知机制发送数据给 ISR。
- 不过 ISR 可以通过任务通知的 API(例如 xTaskNotifyFromISR())将数据发送给任务,从而实现中断到任务的通信。
- 数据只能给目标任务独享:
-
- 通知的数据存放在目标任务的 TCB 中,只有该任务能读取和操作。
- 如果需要广播或共享数据,使用队列、信号量或事件组更合适。
- 无法缓冲多个数据:
-
- 每个任务的通知存储区域仅能保存一个32位通知值,相当于深度为1的缓冲区。
- 如果通知还未被读取而又发送了新的通知,之前的通知数据会被覆盖(视具体 API 行为而定)。
- 无法广播给多个任务:
-
- 任务通知只能针对单个任务进行,不能像事件组那样同时唤醒多个任务。
- 这意味着当需要将同一通知发送给多个任务时,任务通知就不适用了。
- 发送方无法阻塞等待:
-
- 如果目标任务的通知区域正被使用,发送任务不能像队列那样进入阻塞状态等待目标任务清空通知值。
- 发送操作会立即返回错误,发送方需要自行处理这种情况。
1.2 TCB结构体中的存储
在TCB结构体中:
c
typedef struct tskTaskControlBlock /* The old naming convention is used to prevent breaking kernel aware debuggers. */
{
//省略
#if ( configUSE_TASK_NOTIFICATIONS == 1 )
volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
#endif
//省略
} tskTCB;
ulNotifiedValue,通知值:
ulNotifiedValue
是一个volatile uint32_t
数组。每个数组元素存储一个任务通知值( 存储实际的通知数据,可以表示一个计数、一个位域(类似于事件组)或任意32位数值 )。volatile
关键字确保编译器不会对这些变量进行优化,因为它们可能在任务切换或中断中被修改。- 通知值由发送任务或 ISR 写入,接收任务可以读取这个值以获取额外信息。
- 数组大小由宏
configTASK_NOTIFICATION_ARRAY_ENTRIES
决定。默认情况下这个值通常为 1,但也可以配置为大于1,从而让任务拥有多个通知槽(数组中的每个槽都可以独立使用)。
ucNotifyState,通知状态:
- taskNOT_WAITING_NOTIFICATION (0): 表示任务当前没有在等待通知。
- taskWAITING_NOTIFICATION (1): 表示任务正在等待通知。
- taskNOTIFICATION_RECEIVED (2): 表示任务已接收到通知(处于 pending 状态)。

2.函数
2.1 两类函数
任务通知在 FreeRTOS 中提供了一种轻量级的任务间通信与同步机制。为了满足不同场景的需求,任务通知提供了两套 API:简化版 和专业版。

2.2 简化版
2.2.1 Give
xTaskNotifyGive 与 vTaskNotifyGiveFromISR
这两个函数用于向目标任务发送通知,主要特点是:
- 将目标任务的通知值增加 1(类似于计数器),
- 更新通知状态为 "pending",表明有通知等待处理。
xTaskNotifyGive:
- 在任务上下文中调用,用于将通知值加 1,表示向目标任务发送一个简单的通知或事件。
c
BaseType_t xTaskNotifyGive(TaskHandle_t xTaskToNotify);
// xTaskToNotify:目标任务的句柄(通常在创建任务时获得)。它指明了通知应该发给哪个任务。
// 返回值:此函数必定返回 pdPASS,表示通知已成功发送。
- 当发送者任务想通知目标任务"有新事件发生"或"计数加1"时,调用该函数非常高效。
- 不需要传递复杂数据,只是简单地增加计数并改变通知状态。
vTaskNotifyGiveFromISR:
- 在中断服务例程(ISR)中调用,功能与 xTaskNotifyGive 类似,用于在中断中向任务发送通知。
c
void vTaskNotifyGiveFromISR(TaskHandle_t xTaskHandle, BaseType_t *pxHigherPriorityTaskWoken);
/*
xTaskHandle:目标任务的句柄。
pxHigherPriorityTaskWoken:一个指向 BaseType_t 的指针,供 ISR 使用,若通知导致某个等待任务进入就
绪态且其优先级高于当前任务,则将该变量设置为 pdTRUE,指示在中断结束后
需要进行上下文切换。
返回值:此函数不返回值,主要通过 pxHigherPriorityTaskWoken 参数反馈信息。
*/
- 当中断发生时,需要快速通知任务(例如传感器数据就绪、外设事件等),使用该函数可以迅速将通知发送给任务,并确保高优先级任务能在中断退出前被调度。
2.2.2 Take
该函数用于在任务中等待并取出通知,是任务获取通知的主要接口之一,类似于"获取"操作。
- 如果通知值为 0,则任务会阻塞等待,直到在指定超时时间内有通知值增加。
- 当通知值大于0时,任务从阻塞状态恢复,并在返回之前,根据参数选择将通知值清零或仅减一。
c
uint32_t ulTaskNotifyTake(BaseType_t xClearCountOnExit, TickType_t xTicksToWait);
参数:
-
xClearCountOnExit:
-
- pdTRUE :在函数返回前,将目标任务的通知值清零。
这种方式适用于二进制信号量风格,即接收到通知后立即清除。 - pdFALSE :在返回前,不会完全清零,而是将通知值减 1。
这适用于计数型信号量的场景,允许连续接收多个通知。
- pdTRUE :在函数返回前,将目标任务的通知值清零。
-
xTicksToWait:
-
- 指定任务进入阻塞等待的时间(以 Tick 为单位)。
- 如果设为 0,则函数立即返回当前通知值,不进行阻塞。
- 如果设为 portMAX_DELAY,则任务将无限等待直到收到通知。
- 其他值则表示等待的 Tick 数,可以使用 pdMS_TO_TICKS() 将毫秒转换为 Tick 数。
返回值:
-
返回在清零或减一之前的通知值:
-
- 如果在等待期间有通知到来,则返回大于 0 的值(表示接收到的通知数量);
- 如果一直没有通知到来而超时,则返回 0。
当一个任务需要等待某个事件或计数器增加时,调用 ulTaskNotifyTake() 可使任务阻塞,直到事件发生。
例如,生产者任务使用 xTaskNotifyGive() 发出通知,消费者任务则调用 ulTaskNotifyTake() 来等待通知并"消费"该通知。

2.3 专业版
2.3.1 Give
任务上下文:
c
BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction );
ISR:
c
BaseType_t xTaskNotifyFromISR( TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction,
BaseType_t *pxHigherPriorityTaskWoken );
-
xTaskToNotify / 任务句柄:指定要发送通知的目标任务。这个句柄在任务创建时获得。
-
ulValue:32 位数值,其具体作用由 eAction 决定:
-
- 当使用 eNoAction 时,不会对通知值做任何修改,只是更新通知状态为"pending"(即有通知)。
- 当使用 eSetBits 时,通知值将按位"或"(OR)运算,即:
新通知值 = 原通知值 | ulValue
。
这种方式适合将各个事件以位的形式累积,类似轻量级事件组。 - 当使用 eIncrement 时,忽略 ulValue,通知值将递增 1。
这种方式实现了计数功能,与 xTaskNotifyGive() 的行为一致。 - 当使用 eSetValueWithoutOverwrite 时,只有当目标任务的通知状态不是"pending"(表示没有未读通知)时,才将通知值设为 ulValue;如果当前已有未处理通知,则此次调用不会更新通知值,并返回 pdFAIL。
这种方式适用于需要确保新通知不会覆盖旧通知的场合。 - 当使用 eSetValueWithOverwrite 时,无论当前通知状态如何,通知值都将被设为 ulValue,即直接覆盖之前的通知值。
这种方式类似于轻量级邮箱,可以确保写入新值,不受旧通知的影响。
-
eAction (eNotifyAction)
指定如何操作通知值,上述说明中列出的各个取值分别实现不同的通知行为。
-
pxHigherPriorityTaskWoken (仅用于 ISR 版本)
是一个指针,指向一个 BaseType_t 变量。
-
- 当 ISR 发送通知后,如果被通知的任务正处于阻塞状态且其优先级高于当前任务,则该变量将被设置为 pdTRUE,提示中断服务例程在退出时进行上下文切换。
-
返回值:
-
- 对于大多数调用,返回值为 pdPASS 表示成功。
- 唯一可能返回 pdFAIL 的情况是在使用 eSetValueWithoutOverwrite 时,如果目标任务的通知状态已经处于"pending",说明之前的通知还未被读取,此时调用将失败,不更新通知值。
使用场景:
- 作为事件组:
如果希望将不同事件以位的形式累加到同一通知值中,可以使用 eSetBits。多个通知可以同时累加,不会相互覆盖。 - 作为计数型信号量:
使用 eIncrement 使通知值递增 1,每次通知代表一个事件或资源释放。 - 作为邮箱或单深度队列:
使用 eSetValueWithOverwrite 将新的数据写入通知值,无论是否有未读通知;或使用 eSetValueWithoutOverwrite 防止覆盖未处理的数据(如果需要确保数据不被覆盖)。 - 在 ISR 中发送通知:
使用 xTaskNotifyFromISR,可以在中断上下文中快速发送通知给任务,同时利用 pxHigherPriorityTaskWoken 实现实时响应。
2.3.2 Take
xTaskNotifyWait 用于在任务中等待通知到来,并提供在进入等待和退出等待时清除通知值特定位的功能。这使得任务在等待期间可以"清理"旧的通知数据,确保只处理新到来的通知。
c
BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry,
uint32_t ulBitsToClearOnExit,
uint32_t *pulNotificationValue,
TickType_t xTicksToWait );
-
ulBitsToClearOnEntry
-
- 在进入等待前,清除通知值的哪些位(仅在通知状态不是"pending"的情况下执行)。
- 例如,传入 0x01 表示在进入等待之前先清除通知值中的 bit0。
- 如果传入 ULONG_MAX,则表示清除所有位(即将通知值清零)。
-
ulBitsToClearOnExit
-
- 当等待成功(非超时返回)时,在函数退出前清除通知值的哪些位。
- 在清除之前,通知值会先被赋值给 *pulNotificationValue,以便任务获取通知值。
- 例如,传入 0x03 表示清除通知值的 bit0 和 bit1;传入 ULONG_MAX 则表示清除所有位。
-
pulNotificationValue
-
- 用来取出通知值。
- 在函数退出时,使用ulBitsToClearOnExit清除之前,把通知值赋给"*pulNotificationValue"。
- 如果不需要取出通知值,可以设为NULL。
-
xTicksToWait
-
- 指定任务等待通知的最大时间(Tick 数)。
- 0 表示不等待,立即返回当前通知值;
- portMAX_DELAY 表示无限等待,直到通知状态变为"pending";
- 其他值表示等待指定的 Tick 数(可以通过 pdMS_TO_TICKS() 转换为 Tick 数)。
返回值:
- 返回 pdPASS 表示任务成功获得通知,即等待期间通知状态变为了"pending",任务解除阻塞。
- 返回 pdFAIL 表示任务在指定时间内没有收到通知而超时返回。
一般工作流程:
-
进入等待之前
-
- 调用 xTaskNotifyWait 时,首先根据 ulBitsToClearOnEntry 清除通知值中指定的位(如果当前通知状态不是 pending),从而丢弃旧通知。
-
阻塞等待
-
- 任务进入阻塞状态,直到目标任务的通知状态变为"pending"(即有新通知到来),或等待时间超时。
-
退出等待前
-
- 在成功获得通知后,函数先将当前通知值保存到 *pulNotificationValue(如果提供),然后根据 ulBitsToClearOnExit 清除通知值中相应的位。
这种机制允许任务在等待通知时同时对通知值进行预处理和后处理,以确保任务接收到的是新鲜的数据或正确的事件标志。
3.实现轻量级信号量
创建信号量时,可以指定最大值、初始值;使用任务通知实现轻量级的信号量时呢
- 不能设置最大值
- 初始值为0,不能指定初始值
- 最小值是0,跟信号量是一样的
c
static int sum = 0;
static volatile int flagCalcEnd = 0;
static volatile int flagUARTused = 0;
static SemaphoreHandle_t xSemCalc;
static SemaphoreHandle_t xSemUART;
static TaskHandle_t xHandleTask2;
void Task1Function(void * param)
{
volatile int i = 0;
while (1)
{
for (i = 0; i < 10000; i++)
sum++;
//printf("1");
for (i = 0; i < 10; i++)
{
// xSemaphoreGive(xSemCalc);
xTaskNotifyGive(xHandleTask2);
}
vTaskDelete(NULL);
}
}
void Task2Function(void * param)
{
int i = 0;
int val;
while (1)
{
//if (flagCalcEnd)
flagCalcEnd = 0;
//xSemaphoreTake(xSemCalc, portMAX_DELAY);
val = ulTaskNotifyTake(pdFALSE, portMAX_DELAY); //pdFALSE:每取出一次通知值减1
flagCalcEnd = 1;
printf("sum = %d, NotifyVal = %d, i = %d\r\n", sum, val, i++);
}
}
/*-----------------------------------------------------------*/
int main( void )
{
TaskHandle_t xHandleTask1;
#ifdef DEBUG
debug();
#endif
prvSetupHardware();
printf("Hello, world!\r\n");
xSemCalc = xSemaphoreCreateCounting(10, 0);
xSemUART = xSemaphoreCreateBinary();
xSemaphoreGive(xSemUART);
xTaskCreate(Task1Function, "Task1", 100, NULL, 1, &xHandleTask1);
xTaskCreate(Task2Function, "Task2", 100, NULL, 1, &xHandleTask2);
/* Start the scheduler. */
vTaskStartScheduler();
/* Will only get here if there was not enough heap space to create the
idle task. */
return 0;
}
4.实现轻量级队列
队列、使用任务通知实现的轻量级队列,有什么异同?
- 队列:可以容纳多个数据,数据大小可以指定
- 任务通知:只有1个数据,数据时32位的
- 队列:写队列时,可以阻塞
- 任务通知:写队列时,不可以阻塞
- 队列:如果队列长度是1,可以选择覆盖队列
- 任务通知:可以覆盖,也可不覆盖
c
static int sum = 0;
static volatile int flagCalcEnd = 0;
static volatile int flagUARTused = 0;
static QueueHandle_t xQueueCalcHandle;
static QueueHandle_t xQueueUARTcHandle;
static TaskHandle_t xHandleTask2;
int InitUARTLock(void)
{
int val;
xQueueUARTcHandle = xQueueCreate(1, sizeof(int));
if (xQueueUARTcHandle == NULL)
{
printf("can not create queue\r\n");
return -1;
}
xQueueSend(xQueueUARTcHandle, &val, portMAX_DELAY);
return 0;
}
void GetUARTLock(void)
{
int val;
xQueueReceive(xQueueUARTcHandle, &val, portMAX_DELAY);
}
void PutUARTLock(void)
{
int val;
xQueueSend(xQueueUARTcHandle, &val, portMAX_DELAY);
}
void Task1Function(void * param)
{
volatile int i = 0;
while (1)
{
for (i = 0; i < 10000; i++)
sum++;
//printf("1");
//flagCalcEnd = 1;
//vTaskDelete(NULL);
for (i = 0; i < 10; i++)
{
//xQueueSend(xQueueCalcHandle, &sum, portMAX_DELAY);
//xTaskNotify(xHandleTask2, sum, eSetValueWithoutOverwrite); // 不覆盖
xTaskNotify(xHandleTask2, sum, eSetValueWithOverwrite); // 覆盖value
sum++;
}
vTaskDelete(NULL);
//sum = 1;
}
}
void Task2Function(void * param)
{
int val;
int i = 0;
while (1)
{
//if (flagCalcEnd)
flagCalcEnd = 0;
//xQueueReceive(xQueueCalcHandle, &val, portMAX_DELAY);
xTaskNotifyWait(0, 0, &val, portMAX_DELAY);
flagCalcEnd = 1;
printf("sum = %d, i = %d\r\n", val, i++);
}
}
/*-----------------------------------------------------------*/
int main( void )
{
TaskHandle_t xHandleTask1;
#ifdef DEBUG
debug();
#endif
prvSetupHardware();
printf("Hello, world!\r\n");
xQueueCalcHandle = xQueueCreate(10, sizeof(int));
if (xQueueCalcHandle == NULL)
{
printf("can not create queue\r\n");
}
InitUARTLock();
xTaskCreate(Task1Function, "Task1", 100, NULL, 1, &xHandleTask1);
xTaskCreate(Task2Function, "Task2", 100, NULL, 1, &xHandleTask2);
/* Start the scheduler. */
vTaskStartScheduler();
/* Will only get here if there was not enough heap space to create the
idle task. */
return 0;
}
5.实现轻量级事件组
任务通知并不能实现真正的事件组,为什么?
- 它不能等待指定的事件
- 它不能等待若干个事件中的任意一个
- 一旦有事件,总会唤醒任务
发送方可以设置事件,但是接收方不能挑选事件,即使不是它关心的事件,它也会被唤醒
- TCB结构体中的uint_32的通知值,不管修改哪一位表示事件的发生,正在等待的接收方都会被通知
c
static int sum = 0;
static int dec = 0;
static volatile int flagCalcEnd = 0;
static volatile int flagUARTused = 0;
static QueueHandle_t xQueueCalcHandle;
static EventGroupHandle_t xEventGroupCalc;
static TaskHandle_t xHandleTask3;
void Task1Function(void * param)
{
volatile int i = 0;
while (1)
{
for (i = 0; i < 100000; i++)
sum++;
xQueueSend(xQueueCalcHandle, &sum, 0);
/* 设置事件0 */
//xEventGroupSetBits(xEventGroupCalc, (1<<0));
xTaskNotify(xHandleTask3, (1<<0), eSetBits);
printf("Task 1 set bit 0\r\n");
vTaskDelete(NULL);
}
}
void Task2Function(void * param)
{
volatile int i = 0;
while (1)
{
for (i = 0; i < 1000000; i++)
dec--;
xQueueSend(xQueueCalcHandle, &dec, 0);
/* 设置事件1 */
//xEventGroupSetBits(xEventGroupCalc, (1<<1));
xTaskNotify(xHandleTask3, (1<<1), eSetBits);
printf("Task 2 set bit 1\r\n");
vTaskDelete(NULL);
}
}
void Task3Function(void * param)
{
int val1, val2;
int bits;
while (1)
{
/*等待事件 */
//xEventGroupWaitBits(xEventGroupCalc, (1<<0)|(1<<1), pdTRUE, pdTRUE, portMAX_DELAY);
xTaskNotifyWait(0, 0, &bits, portMAX_DELAY);
if ((bits & 0x3) == 0x3)
{
vTaskDelay(20);
xQueueReceive(xQueueCalcHandle, &val1, 0);
xQueueReceive(xQueueCalcHandle, &val2, 0);
printf("val1 = %d, val2 = %d\r\n", val1, val2);
}
else
{
vTaskDelay(20);
printf("have not get all bits, get only 0x%x\r\n", bits);
}
}
}
/*-----------------------------------------------------------*/
int main( void )
{
TaskHandle_t xHandleTask1;
#ifdef DEBUG
debug();
#endif
prvSetupHardware();
printf("Hello, world!\r\n");
/* 创建事件组 */
xEventGroupCalc = xEventGroupCreate();
xQueueCalcHandle = xQueueCreate(2, sizeof(int));
if (xQueueCalcHandle == NULL)
{
printf("can not create queue\r\n");
}
xTaskCreate(Task1Function, "Task1", 100, NULL, 1, &xHandleTask1);
xTaskCreate(Task2Function, "Task2", 100, NULL, 1, NULL);
xTaskCreate(Task3Function, "Task3", 100, NULL, 1, &xHandleTask3);
/* Start the scheduler. */
vTaskStartScheduler();
/* Will only get here if there was not enough heap space to create the
idle task. */
return 0;
}
6.源码分析
上面就提到过一个任务的"通知状态"有三种:
- taskNOT_WAITING_NOTIFICATION:任务没有在等待通知
- taskWAITING_NOTIFICATION:任务在等待通知
- taskNOTIFICATION_RECEIVED:任务接收到了通知,也被称为 pending(有数据了,待处理)
一个任务想等待别人发来通知,可以调用ulTaskNotifyTake
或xTaskNotifyWait
:
- 可能别人早就发来通知:"通知状态"为taskNOTIFICATION_RECEIVED,那么函数立刻返回
- 可能别人还没发来通知:这些函数把"通知状态"从taskNOT_WAITING_NOTIFICATION改为taskWAITING_NOTIFICATION,然后休眠
别的任务可以使用xTaskNotifyGive
或xTaskNotify
给某个任务发通知:
- 会马上唤醒对方
- 无条件唤醒对方,不管对方期待什么数据
6.1 等待通知
这里以专业版的 xTaskGenericNotifyWait
进行诉说:
c
BaseType_t xTaskGenericNotifyWait( UBaseType_t uxIndexToWait,
uint32_t ulBitsToClearOnEntry,
uint32_t ulBitsToClearOnExit,
uint32_t * pulNotificationValue,
TickType_t xTicksToWait )
{
BaseType_t xReturn;
/* 确保传入的索引有效,索引必须小于任务通知数组的总条目数 */
configASSERT( uxIndexToWait < configTASK_NOTIFICATION_ARRAY_ENTRIES );
/* 进入临界区,确保接下来的操作原子执行,不被中断 */
taskENTER_CRITICAL();
{
/* 如果当前任务的通知状态未标记为"已接收通知",则说明当前没有通知等待结果
* 只有当通知状态不是 taskNOTIFICATION_RECEIVED 时,才允许任务进入等待状态 */
if( pxCurrentTCB->ucNotifyState[ uxIndexToWait ] != taskNOTIFICATION_RECEIVED )
{
/* 在进入等待之前,根据 ulBitsToClearOnEntry 参数清除任务通知值中的指定位
* 这样做可以在等待开始前清除旧的通知位(例如将通知值清零) */
pxCurrentTCB->ulNotifiedValue[ uxIndexToWait ] &= ~ulBitsToClearOnEntry;
/* 将任务的通知状态标记为"等待通知" */
pxCurrentTCB->ucNotifyState[ uxIndexToWait ] = taskWAITING_NOTIFICATION;
/* 如果指定的等待时间大于0,则任务将进入阻塞状态等待通知 */
if( xTicksToWait > ( TickType_t ) 0 )
{
/* 将当前任务添加到延时列表中,等待 xTicksToWait 个时钟节拍后超时 */
prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE );
/* 跟踪记录任务进入等待通知阻塞状态 */
traceTASK_NOTIFY_WAIT_BLOCK( uxIndexToWait );
/* 尽管处于临界区内,内核各移植层允许在 API 中进行任务切换(yield)
* 这可能会导致立即进行上下文切换,但应用代码一般不会直接调用此 yield */
portYIELD_WITHIN_API();
}
else
{
/* 如果 xTicksToWait 为0,则不进入延时列表 */
mtCOVERAGE_TEST_MARKER();
}
}
else
{
/* 如果任务的通知状态已经是"已接收通知",则无需改变状态 */
mtCOVERAGE_TEST_MARKER();
}
}
/* 退出第一个临界区 */
taskEXIT_CRITICAL();
/* 第二个临界区用于处理解除阻塞后的通知值读取和状态恢复 */
taskENTER_CRITICAL();
{
/* 跟踪记录任务通知等待完成 */
traceTASK_NOTIFY_WAIT( uxIndexToWait );
/* 如果调用者提供了 pulNotificationValue 指针,则将当前任务的通知值写入该指针中
* 这个通知值可能在等待期间被更新 */
if( pulNotificationValue != NULL )
{
*pulNotificationValue = pxCurrentTCB->ulNotifiedValue[ uxIndexToWait ];
}
/* 判断通知状态:
* 如果通知状态不是 taskNOTIFICATION_RECEIVED,则说明任务未因通知解除阻塞,而是因超时解除 */
if( pxCurrentTCB->ucNotifyState[ uxIndexToWait ] != taskNOTIFICATION_RECEIVED )
{
/* 没有收到通知,返回 pdFALSE */
xReturn = pdFALSE;
}
else
{
/* 如果通知状态为 taskNOTIFICATION_RECEIVED,则任务在等待期间收到了通知
* 根据 ulBitsToClearOnExit 参数清除通知值中的相应位 */
pxCurrentTCB->ulNotifiedValue[ uxIndexToWait ] &= ~ulBitsToClearOnExit;
/* 设置返回值为 pdTRUE,表示成功接收到通知 */
xReturn = pdTRUE;
}
/* 无论通知是否接收,将任务的通知状态重置为"未等待通知" */
pxCurrentTCB->ucNotifyState[ uxIndexToWait ] = taskNOT_WAITING_NOTIFICATION;
}
/* 退出第二个临界区 */
taskEXIT_CRITICAL();
/* 返回任务是否成功接收到通知 */
return xReturn;
}
根据当前任务的TCB结构体pxCurrentTCB
中的状态标志来决定需不需要等待
- 如果是
taskNOTIFICATION_RECEIVED
,已经收到通知但是被pending了,不需要等待 - 如果是其它状态,将状态设置为
taskWAITING_NOTIFICATION
,等待通知,然后调用prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE );
将该任务放入到任务等待链表当中,使其等待通知
下半部分则是用于处理解除阻塞后的通知值读取和状态恢复(第二个临界区),注释很清楚了,这里不赘述
6.2 发送通知
还是以专业版的xTaskNotify
为例子:
c
#define xTaskNotify( xTaskToNotify, ulValue, eAction ) \
xTaskGenericNotify( ( xTaskToNotify ), ( tskDEFAULT_INDEX_TO_NOTIFY ), ( ulValue ), ( eAction ), NULL )
c
BaseType_t xTaskGenericNotify( TaskHandle_t xTaskToNotify,
UBaseType_t uxIndexToNotify,
uint32_t ulValue,
eNotifyAction eAction,
uint32_t * pulPreviousNotificationValue )
{
/* 定义指向目标任务控制块(TCB)的指针 */
TCB_t * pxTCB;
/* 默认返回值设置为 pdPASS,表示通知成功 */
BaseType_t xReturn = pdPASS;
/* 用于保存原始通知状态的变量 */
uint8_t ucOriginalNotifyState;
/* 断言检查:确保传入的通知索引在任务通知数组范围内 */
configASSERT( uxIndexToNotify < configTASK_NOTIFICATION_ARRAY_ENTRIES );
/* 断言检查:确保要通知的任务不为空 */
configASSERT( xTaskToNotify );
/* 将任务句柄转换为 TCB 指针,便于访问任务的通知数据 */
pxTCB = xTaskToNotify;
/* 进入临界区,确保以下操作是原子性的,防止中断或任务切换干扰 */
taskENTER_CRITICAL();
{
/* 如果调用者要求获取通知前的值,则将目标任务对应索引的通知值保存到 pulPreviousNotificationValue */
if( pulPreviousNotificationValue != NULL )
{
*pulPreviousNotificationValue = pxTCB->ulNotifiedValue[ uxIndexToNotify ];
}
/* 保存当前任务的通知状态,以便后续判断任务是否处于等待通知状态 */
ucOriginalNotifyState = pxTCB->ucNotifyState[ uxIndexToNotify ];
/* 将任务的通知状态设置为"已接收通知",表示本次调用已触发通知 */
pxTCB->ucNotifyState[ uxIndexToNotify ] = taskNOTIFICATION_RECEIVED;
/* 根据通知操作类型(eAction)来更新任务的通知值 */
switch( eAction )
{
case eSetBits:
/* eSetBits:将 ulValue 指定位与当前通知值进行按位或运算 */
pxTCB->ulNotifiedValue[ uxIndexToNotify ] |= ulValue;
break;
case eIncrement:
/* eIncrement:将当前通知值递增 */
( pxTCB->ulNotifiedValue[ uxIndexToNotify ] )++;
break;
case eSetValueWithOverwrite:
/* eSetValueWithOverwrite:无条件覆盖当前通知值,设置为 ulValue */
pxTCB->ulNotifiedValue[ uxIndexToNotify ] = ulValue;
break;
case eSetValueWithoutOverwrite:
/* eSetValueWithoutOverwrite:仅当通知状态之前未标记为"已接收通知"时才写入新值 */
if( ucOriginalNotifyState != taskNOTIFICATION_RECEIVED )
{
pxTCB->ulNotifiedValue[ uxIndexToNotify ] = ulValue;
}
else
{
/* 如果目标任务已处于"已接收通知"状态,则不允许覆盖,返回失败 */
xReturn = pdFAIL;
}
break;
case eNoAction:
/* eNoAction:不改变通知值,仅更新通知状态,用于仅唤醒任务 */
break;
default:
/* 默认情况:如果传入了未处理的操作类型,则强制断言失败 */
configASSERT( xTickCount == ( TickType_t ) 0 );
break;
}
/* 记录通知事件,用于调试和跟踪 */
traceTASK_NOTIFY( uxIndexToNotify );
/* 检查目标任务是否正处于等待通知的阻塞状态 */
if( ucOriginalNotifyState == taskWAITING_NOTIFICATION )
{
/* 如果是,则将该任务从阻塞列表中移除 */
listREMOVE_ITEM( &( pxTCB->xStateListItem ) );
/* 并将任务添加到就绪列表中,以便它可以被调度执行 */
prvAddTaskToReadyList( pxTCB );
/* 确保该任务不在任何事件列表中 */
configASSERT( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) == NULL );
#if ( configUSE_TICKLESS_IDLE != 0 )
{
/* 如果使用 Tickless Idle 模式,可能需要重置下一个任务解除阻塞时间,
* 以便更快进入睡眠模式 */
prvResetNextTaskUnblockTime();
}
#endif
/* 如果被通知的任务优先级高于当前任务,
* 则立即进行任务切换,使得高优先级任务尽快运行 */
if( pxTCB->uxPriority > pxCurrentTCB->uxPriority )
{
taskYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
/* 如果目标任务原来并不处于等待通知状态,则不需要将其从阻塞列表中移除 */
mtCOVERAGE_TEST_MARKER();
}
}
/* 退出临界区 */
taskEXIT_CRITICAL();
/* 返回操作结果,pdPASS 表示通知成功,pdFAIL 表示未能写入通知值(如 eSetValueWithoutOverwrite 情况) */
return xReturn;
}
通知操作类型(如设置位、递增、覆盖等)更新目标任务的通知值,并在必要时唤醒等待通知的任务 。注释很清楚了,这里就不再额外讲了。看过之前关于队列、信号量、互斥量的相关内部机制讲解,其实看这个也是很简单了