任务通知概述
任务通知并不像队列、信号量、互斥量这些方法需要在使用之前先进行创建队列、创建信号量、创建互斥量。任务通知的相关存储空间,在创建任务时就已经分配。
在调用xTaskCreate创建任务时,会将任务以TCB_t类型的结构体进行管理,在这个结构体中分配了一部分空间给任务通知使用,下面为TCB_t结构体中关于任务通知的部分:
#if ( configUSE_TASK_NOTIFICATIONS == 1 )
volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
#endif
可以看到,对于任务通知的空间,只有一个通知值和一个通知状态,并没有任何像队列结构体那些定义了等待读入任务链表、等待写入任务链表。因此任务通知的阻塞也不同于队列:
- 对于发送任务通知的任务,不会进入到阻塞状态,只会发送成功或发送失败
- 对于接收任务通知的任务,可以进入阻塞状态进行等待通知。有任务发送通知时,解除阻塞。
任务通知的状态有三种取值:
/* 任务没有在等待通知 */
#define taskNOT_WAITING_NOTIFICATION ( ( uint8_t ) 0 )
/* 任务在等待通知 */
#define taskWAITING_NOTIFICATION ( ( uint8_t ) 1 )
/* 任务接收到了通知(pending) */
#define taskNOTIFICATION_RECEIVED ( ( uint8_t ) 2 )
状态转变的过程如下(假设任务A向任务B发送任务通知):
当任务B调用接收任务通知的函数时,通知状态被设置成 “ 任务在等待通知 ”。当任务A调用发送任务通知的函数时,任务B的通知值val被改变,通知状态被设置成 “ 任务接收到了通知 ”。此时任务B被唤醒,不再阻塞,并且将通知状态设置成 “ 任务没有在等待通知 ”。
相关函数
在接下来的函数描述中,假设任务A向任务B发送通知,通知值用val描述
任务通知的收发函数有两组,第一组是简单的,第二组是复杂的。实质上第一组是第二组的一部分
发送任务通知(1)
函数声明如下:
/* 这是一个宏,发送任务通知 */
xTaskNotifyGive( xTaskToNotify )
/* 宏定义 */
#define xTaskNotifyGive( xTaskToNotify ) \
xTaskGenericNotify( ( xTaskToNotify ), \
( tskDEFAULT_INDEX_TO_NOTIFY ), \
( 0 ), \
eIncrement, \
NULL ) \
/* 实际调用函数 */
BaseType_t xTaskGenericNotify( TaskHandle_t xTaskToNotify,
UBaseType_t uxIndexToNotify,
uint32_t ulValue,
eNotifyAction eAction,
uint32_t * pulPreviousNotificationValue )
xTaskToNotify:目标任务的句柄,即任务B
该函数并没有设置发送的通知值是什么,最终操作是让任务B的val值+1
接收任务通知(1)
函数声明如下:
/* 这是一个宏,接收任务通知 */
ulTaskNotifyTake( xClearCountOnExit, xTicksToWait )
/* 宏定义 */
#define ulTaskNotifyTake( xClearCountOnExit, xTicksToWait ) \
ulTaskGenericNotifyTake( ( tskDEFAULT_INDEX_TO_NOTIFY ),\
( xClearCountOnExit ), \
( xTicksToWait ) ) \
/* 实际调用函数 */
uint32_t ulTaskGenericNotifyTake( UBaseType_t uxIndexToWait,
BaseType_t xClearCountOnExit,
TickType_t xTicksToWait )
返回值:任务B中获得到的通知值val,这个val是清除之前的val
xClearCountOnExit:函数退出时是否清除val,传入值为pdFALSE、pdTRUE
- 传入pdFALSE时,函数退出时,val--
- 传入pdTRUE时,函数退出时,val=0
发送任务通知(2)
函数声明如下:
/* 这是一个宏,发送任务通知 */
#define xTaskNotify( xTaskToNotify, ulValue, eAction )
/* 宏定义 */
#define xTaskNotify( xTaskToNotify, ulValue, eAction ) \
xTaskGenericNotify( ( xTaskToNotify ), \
( tskDEFAULT_INDEX_TO_NOTIFY ), \
( ulValue ), \
( eAction ), \
NULL )
/* 实际调用函数 */
BaseType_t xTaskGenericNotify( TaskHandle_t xTaskToNotify,
UBaseType_t uxIndexToNotify,
uint32_t ulValue,
eNotifyAction eAction,
uint32_t * pulPreviousNotificationValue )
xTaskToNotify:目标任务的句柄,即任务B
ulValue:传入的通知值,该值是否起作用,取决于eAction的值
eAction:控制传入的通知值,取值及效果如下:
- eNoAction:A给B发个通知而已,不会修改B中的val值。这时传入的val没用
- eSetBits:B中的val = 原来的val | 传入的val值
- eIncrement:B中的val++,此时与xTaskNotifyGive效果一致
- eSetValueWithoutOverwrite:第二次的通知值不覆盖第一次没有被取走的通知值
- eSetValueWithOverwrite:第二次的通知值覆盖第一次没有被取走的通知值
接收任务通知(2)
函数声明如下:
/* 这是一个宏,接收任务通知 */
xTaskNotifyWait( ulBitsToClearOnEntry,
ulBitsToClearOnExit,
pulNotificationValue,
xTicksToWait ) \
/* 宏定义 */
#define xTaskNotifyWait( ulBitsToClearOnEntry,
ulBitsToClearOnExit,
pulNotificationValue,
xTicksToWait ) \
xTaskGenericNotifyWait( tskDEFAULT_INDEX_TO_NOTIFY,
( ulBitsToClearOnEntry ),
( ulBitsToClearOnExit ),
( pulNotificationValue ),
( xTicksToWait ) )
/* 实际调用函数 */
BaseType_t xTaskGenericNotifyWait( UBaseType_t uxIndexToWait,
uint32_t ulBitsToClearOnEntry,
uint32_t ulBitsToClearOnExit,
uint32_t * pulNotificationValue,
TickType_t xTicksToWait )
ulBitsToClearOnEntry:入口处(进入函数时)是否清除某些位
ulBitsToClearOnExit:出口处(退出函数时)是否清除某些位
- 这两个参数都以bit传入,实际上的运算为:val &= ~bits。
- 如果传入0表示不清除任何位,如果传入1<<3代表清除第3位
pulNotificationValue:获取在出口处清除val之前的val值
xTicksToWait:超时
验证实验
实验内容为:使用任务通知实现轻量级信号量,思路如下:
- 利用xTaskNotifyGive可以实现将任务B中的val++,这类似于放入信号量。
- 利用ulTaskNotifyTake可以实现将任务B中的val--,这类似于拿出信号量
具体代码实现如下:
TaskHandle_t xHandleTaskA;
TaskHandle_t xHandleTaskB;
void TaskAFunction(void *param){
int i=0;
while(1){
for(i=0;i<10;i++){
/* 向TaskB发送任务通知,TaskB的val++ */
xTaskNotifyGive(xHandleTaskB);
}
/* 发送信号完成,自杀 */
vTaskDelete(NULL);
}
}
void TaskBFunction(void *param){
uint32_t val;
while(1){
/* 获取通知,pdFALSE代表每次val--而不是清零 */
val = ulTaskNotifyTake(pdFALSE,portMAX_DELAY);
printf("val = %d\r\n",val);
}
}
int main( void )
{
prvSetupHardware();
SerialPortInit();
printf("UART TEST\r\n");
xTaskCreate(TaskAFunction,"TaskA",100,(void*)NULL,2,&xHandleTaskA);
xTaskCreate(TaskBFunction,"TaskB",100,(void*)NULL,1,&xHandleTaskB);
vTaskStartScheduler();
return 0;
}
运行结果如下:
可以看到,当取完val = 1后(相当于信号量没有了),Task2进入到了阻塞状态。这说明实现了类似计数型信号量的功能。