从FreeRTOSv8.2.0版本开始,FreeRTOS新增了任务通知(Task Notifictions)这个功能,可以使用任务通知来代替信号量、消息队列、事件标志组等这些东西。使用任务通知的话效率会更高。
一、任务通知简介
任务通知在FreeRTOS中是一个可选的功能,要使用任务通知的话就需要将宏configUSE_TASK_NOTIFICATIONS定义为1。FreeRTOS的每个任务都有一个32位的通知值,任务控制块中的成员变量ulNotifiedValue就是这个通知值。任务通知是一个事件,假如某个任务通知的接收任务因为等待任务通知而阻塞的话,向这个接收任务发送任务通知以后就会解除这个任务的阻塞状态。也可以更新接收任务的任务通知值,任务通知可以通过如下方法更新接收任务的通知值:
- 不覆盖接收任务的通知值(如果上次发送给接收任务的通知还没被处理)。
- 覆盖接收任务的通知值。
- 更新接收任务通知值的一个或多个bit。
- 增加接收任务的通知值。
合理、灵活的使用上面这些更改任务通知值的方法可以在一些场合中替代队列、二值信号量、计数型信号量和事件标志组。使用任务通知来实现二值信号量功能的时候,解除任务阻塞的时间比直接使用二值信号量要快45%,并且使用的RAM更少!任务通知的发送使用函数xTaskNotify()或者xTaskNotifyGive() (还有此函数的中断版本)来完成,这个通知值会一直被保存着,直到接收任务调用函数xTaskNotifyWait()或者ulTaskNotifyTake()来获取这个通知值。假如接收任务因为等待任务通知而阻塞的话那么在接收到任务通知以后就会解除阻塞态。任务通知虽然可以提高速度,并且减少RAM的使用,但是任务通知也是有使用限制的:
- FreeRTOS的任务通知只能有一个接收任务,其实大多数的应用都是这种情况。
- 接收任务可以因为接收任务通知而进入阻塞态,但是发送任务不会因为任务通知发送失败而阻塞。
二、任务发送通知相关API函数详解
1、任务通知发送函数
任务通知发送函数共有6个,如下表所示:
函数 | 描述 |
---|---|
xTaskNotify() | 发送通知,带有通知值并且不保留接收任务原通知值,用在任务中。 |
xTaskNotifyFromISR() | 发送通知,函数xTaskNotify()的中断版本。 |
xTaskNotifyGive() | 发送通知,不带通知值并且不保留接收任务的通知值,此函数会将接收任务的通知值加一,用于任务中。 |
vTaskNotifyGiveFromISR() | 发送通知,函数xTaskNotifyGive()的中断版本。 |
xTaskNotifyAndQuery() | 发送通知,带有通知值并且保留接收任务的原通知值,用在任务中。 |
XTaskNotiryAndQueryFromISR() | 发送通知,函数xTaskNotifyAndQuery()的中断版本,用在中断服务函数中。 |
- ①、函数xTaskNotify()
此函数用于发送任务通知,此函数发送任务通知的时候带有通知值,此函数是个宏,真正执行的函数xTaskGenericNotify(),函数原型如下:
BaseType_t xTaskNotify(TaskHandle_t xTaskToNotify,
uint32_t ulValue
eNotifyAction eAction)
参数 | 描述 |
---|---|
xTaskToNotify | 任务句柄,指定任务通知是发送给哪个任务的。 |
ulValue | 任务通知值。 |
eAction | 任务通知更新的方法,eNotifyAction是个枚举类型,在文件task.h中有定义 |
返回值 | pdFAIL:当参数eAction设置为eSetValueWithoutOverwrite的时候,如果任务通知值没有更新成功就返回pdFAIL。pdPASS:eAction设置为其他选项的时候统一返回pdPASS。 |
注意:eActiond 取值
typedef enum
{
eNoAction=0,
eSetBits,//更新指定的bit
elncrement,//通知值加一
eSetValueWithOverwrite,//覆写的方式更新通知值
eSetValueWithoutOverwrite//不覆写通知值
}eNotifyAction;
此参数可以选择枚举类型中的任意一个,不同的应用环境其选择也不同。
- ②、函数xTaskNotifyFromISR()
此函数用于发送任务通知,是函数xTaskNotify()的中断版本,此函数是个宏,真正执行的是函数xTaskGenericNotifyFromISR(),此函数原型如下:
BaseType_t xTaskNotifyFromlSR(TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction,
BaseType_t*pxHigherPriorityTaskWoken);
参数 | 描述 |
---|---|
xTaskToNotify | 任务句柄,指定任务通知是发送给哪个任务的。 |
ulValue | 任务通知值。 |
eAction | 任务通知更新的方法。 |
pxHigherPriorityTaskWoken | 记退出此函数以后是否进行任务切换,这个变量的值函数会自动设置的,用户不用进行设置,用户只需要提供一个变量来保存这个值就行了。当此值为pdTRUE的时候在退出中断服务函数之前一定要进行一次任务切换。 |
返回值 | pdFAIL:当参数eAction设置为eSetValueWithoutOverwrite的时候,如果任务通知值没有更新成功就返回pdFAIL。pdPASS:eAction设置为其他选项的时候统一返回pdPASS。 |
- ③、函数xTaskNotifyGive()
发送任务通知,相对于函数xTaskNotify0,此函数发送任务通知的时候不带有通知值。此函数只是将任务通知值简单的加一,此函数是个宏,真正执行的是函数xTaskGenericNotify(),此函数原型如下:
BaseType_txTaskNotifyGive(TaskHandle_t xTaskToNotify);
参数 | 描述 |
---|---|
xTaskToNotify | 任务句柄,指定任务通知是发送给哪个任务的。 |
返回值 | pdPASS:此函数只会返回pdPASS。 |
- ④、函数vTaskNotifyGiveFromISR()
此函数为xTaskNotifyGive()的中断版本,用在中断服务函数中,函数原型如下:
void vTaskNotifyGiveFromlSR(TaskHandle_t xTaskHandle,
BaseType_t* pxHigherPriorityTaskWoken);
参数 | 描述 |
---|---|
xTaskToNotify | 任务句柄,指定任务通知是发送给哪个任务的。 |
pxHigherPriorityTaskWoken | 记退出此函数以后是否进行任务切换,这个变量的值函数会自动设置的,用户不用进行设置,用户只需要提供一个变量来保存这个值就行了。当此值为pdTRUE的时候在退出中断服务函数之前一定要进行一次任务切换。 |
- ⑤、函数xTaskNotifyAndQuery()
此函数和xTaskNotify()很类似,此函数比xTaskNotify()多一个参数,此参数用来保存更新前的通知值。此函数是个宏,真正执行的是函数xTaskGenericNotify0,此函数原型如下:
BaseType_txTaskNotifyAndQuery(TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction,
uint32_t*pulPreviousNotificationValue);
参数 | 描述 |
---|---|
xTaskToNotify | 任务句柄,指定任务通知是发送给哪个任务的。 |
ulValue | 任务通知值。 |
eAction | 任务通知更新的方法。 |
pulPreviousNotificationValue | 用来保存更新前的任务通知值。 |
返回值 | pdFAIL:当参数eAction设置为eSetValueWithoutOverwrite的时候,如果任务通知值没有更新成功就返回pdFAIL。pdPASS:eAction设置为其他选项的时候统一返回pdPASS。 |
- ⑥、函数xTaskNotifyAndQueryFromISR()
此函数为xTaskNorityAndQuery0的中断版本,用在中断服务函数中。此函数同样为宏,真正执行的是函数xTaskGenericNotifyFromISR(),此函数的原型如下:
BaseType_t xTaskNotifyAndQueryFromISR(TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction,
uint32_t* pulPreviousNotificationValue,
BaseType_t*pxHigherPriorityTaskWoken);
参数 | 描述 |
---|---|
xTaskToNotify | 任务句柄,指定任务通知是发送给哪个任务的。 |
ulValue | 任务通知值。 |
eAction | 任务通知更新的方法。 |
pulPreviousNotificationValue | 用来保存更新前的任务通知值。 |
pxHigherPriorityTaskWoken | 记退出此函数以后是否进行任务切换,这个变量的值函数会自动设置的,用户不用进行设置,用户只需要提供一个变量来保存这个值就行了。当此值为pdTRUE的时候在退出中断服务函数之前一定要进行一次任务切换。 |
返回值 | pdFAIL:当参数eAction设置为eSetValueWithoutOverwrite的时候,如果任务通知值没有更新成功就返回pdFAIL。pdPASS:eAction设置为其他选项的时候统一返回pdPASS。 |
2、任务通知通用发送函数详解
- ①、任务级任务通知通用发送函数
3个任务级任务通知发送函数:xTaskNotify0、xTaskNotifyGive()和xTaskNotifyAndQuery(),这三个函数最终调用的都是函数xTaskGenericNotify()此函数在文件tasks.c中有如下定义,函数代码如下:
BaseType_t xTaskGenericNotify( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotificationValue )
{
TCB_t * pxTCB;
BaseType_t xReturn = pdPASS;
uint8_t ucOriginalNotifyState;
configASSERT( xTaskToNotify );
pxTCB = ( TCB_t * ) xTaskToNotify;
taskENTER_CRITICAL();
{
if( pulPreviousNotificationValue != NULL )(1)
{
*pulPreviousNotificationValue = pxTCB->ulNotifiedValue;(2)
}
ucOriginalNotifyState = pxTCB->ucNotifyState;(3)
pxTCB->ucNotifyState = taskNOTIFICATION_RECEIVED;(4)
switch( eAction )
{
case eSetBits :(5)
pxTCB->ulNotifiedValue |= ulValue;
break;
case eIncrement :(6)
( pxTCB->ulNotifiedValue )++;
break;
case eSetValueWithOverwrite :(7)
pxTCB->ulNotifiedValue = ulValue;
break;
case eSetValueWithoutOverwrite :(8)
if( ucOriginalNotifyState != taskNOTIFICATION_RECEIVED )
{
pxTCB->ulNotifiedValue = 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;
}
traceTASK_NOTIFY();
//如果任务因为等待任务通知而进入阻塞态的话就解除阻塞态
if( ucOriginalNotifyState == taskWAITING_NOTIFICATION )(9)
{
( void ) uxListRemove( &( pxTCB->xStateListItem ) );(10)
prvAddTaskToReadyList( pxTCB );(11)
//省略条件编译
if( pxTCB->uxPriority > pxCurrentTCB->uxPriority )(12)
{
/* 解除阻塞的任务优先级比当前运行的优先级高,所以需要任务切换。 */
taskYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
taskEXIT_CRITICAL();
return xReturn;(13)
}
-
(1)、判断参数pulPreviousNotificationValue是否有效,因为此参数用来保存更新前的任务通知值。
-
2)、如果参数pulPreviousNotificationValue有效的话就用此参数保存更新前的任务通知值。
-
(3)、保存任务通知状态,因为下面会修改这个状态,后面要根据这个状态来确定是否将任务从阻塞态解除。
-
(4)、更新任务通知状态为taskNOTIFICATION_RECEIVED。
-
(5)、根据不同的更新方式做不同的处理,如果为eSetBits的话就将指定的bit置1。也就是更新接收任务通知值的一个或多个bit。
-
(6)、如果更新方式为elncrement的话就将任务通知值加一。
-
(7)、如果更新方式为eSetValueWithOverwrite的话就直接覆写原来的任务通知值。
-
(8)、如果更新方式为eSetValueWithoutOverwrite的话就需要判断原来的任务通知值是否被处理,如果已经被处理了就更新为任务通知值。如果此前的任务通知值话没有被处理的话就标记记xReturn为pdFAIL,后面会返回这个值。
-
(9)、根据(3)中保存的接收任务之前的状态值来判断是否有任务需要解除阻塞,如果在任务通知值被更新前任务处于taskWAITING_NOTIFICATION状态的话就说明有任务因为等待任务通知值而进入了阻塞态
-
(10)、将任务从状态列表中移除。
-
(11)、将任务重新添加到就绪列表中。
-
(12)、判断刚刚解除阻塞的任务优先级是否比当前正在运行的任务优先级高,如果是的话需要进行一次任务切换。
-
(13)、返回xReturn的值,pdFAIL或pdPASS。
-
②、中断级任务通知发送函数
中断级任务通知发送函数也有三个,分别为:xTaskNotifyFromISR()、xTaskNotifyAndQueryFromISR()和vTaskNotifyGiveFromISR()。其中函数xTaskNotifyFromlSR()和xTaskNotifyAndQueryFromISR()最终调用的都是函数xTaskGenericNotifyFromlSR(),函数xTaskGenericNotifyFromISR()在文件tasks.c中有定义,函数源码如下:
BaseType_t xTaskGenericNotifyFromISR( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotificationValue, BaseType_t *pxHigherPriorityTaskWoken )
{
TCB_t * pxTCB;
uint8_t ucOriginalNotifyState;
BaseType_t xReturn = pdPASS;
UBaseType_t uxSavedInterruptStatus;
configASSERT( xTaskToNotify );
portASSERT_IF_INTERRUPT_PRIORITY_INVALID();
pxTCB = ( TCB_t * ) xTaskToNotify;
uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
{
if( pulPreviousNotificationValue != NULL )(1)
{
*pulPreviousNotificationValue = pxTCB->ulNotifiedValue;
}
ucOriginalNotifyState = pxTCB->ucNotifyState;(2)
pxTCB->ucNotifyState = taskNOTIFICATION_RECEIVED;(3)
switch( eAction )(4)
{
case eSetBits :
pxTCB->ulNotifiedValue |= ulValue;
break;
case eIncrement :
( pxTCB->ulNotifiedValue )++;
break;
case eSetValueWithOverwrite :
pxTCB->ulNotifiedValue = ulValue;
break;
case eSetValueWithoutOverwrite :
if( ucOriginalNotifyState != taskNOTIFICATION_RECEIVED )
{
pxTCB->ulNotifiedValue = 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;
}
traceTASK_NOTIFY_FROM_ISR();
//如果任务因为等待任务通知而进入阻塞态的话就需要解除阻塞
if( ucOriginalNotifyState == taskWAITING_NOTIFICATION )(5)
{
/* The task should not have been on an event list. */
configASSERT( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) == NULL );
if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )(6)
{
( void ) uxListRemove( &( pxTCB->xStateListItem ) );
prvAddTaskToReadyList( pxTCB );
}
else(7)
{
vListInsertEnd( &( xPendingReadyList ), &( pxTCB->xEventListItem ) );
}
/*解除阻塞的任务优先级比当前运行任务的优先级高,所以需要标记在退出中断服务函数的时候需要任务切换*/
if( pxTCB->uxPriority > pxCurrentTCB->uxPriority )(8)
{
if( pxHigherPriorityTaskWoken != NULL )
{
*pxHigherPriorityTaskWoken = pdTRUE;
}
else
{
xYieldPending = pdTRUE;
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );
return xReturn;
}
- (1)、判断参数pulPreviousNotificationValue是否有效,因为此参数用来保存更新前的任务知值。如果参数pulPreviousNotificationValue有效的话就用此参数保存更新前的任务通知值。
- (2)、保存任务通知状态,因为后面会修改这个状态,后面要根据这个状态来确定是否将任务解除阻塞态。
- (3)、更新任务通知状态 taskNOTIFICATION_RECEIVED。
- (4)、根据不同的通知值更新方式来做不同的处理,与函数xTaskGenericNotify()的处理过程一样。
- (5)、根据(2)中保存的接收任务之前的状态值来判断是否有任务需要解除阻塞,如果在任务通知值被更新前任务处于taskWAITING_NOTIFICATION状态的话就说明有任务因为等待任务通知值而进入了阻塞态。
- (6)、判断任务调度器是否上锁,如果调度器没有上锁的话就将任务从状态列表中移除,然后重新将任务添加到就绪列表中。
- (7)、如果任务调度器上锁了的话就将任务添加到列表xPendingReadyList中。
- (8)、判断任务解除阻塞的任务优先级是否比当前任务优先级高,如果是的话就将pxHigherPriorityTaskWoken标记pdTRUE。如果参数pxHigherPriorityTaskWoken无效的话就将全局变量xYieldPending标记为pdTRUE。
三、任务获取通知相关API函数详解
1、获取任务通知
获取任务通知的函数有两个,如下表所示:
函数 | 描述 |
---|---|
ulTaskNotifyTake() | 获取任务通知,可以设置在退出此函数的时候将任务通知值清零或者减一。当任务通知用作二值信号量或者计数信号量的时候使用此函数来获取信号量。 |
xTaskNotifyWait() | 等待任务通知,比ulTaskNotifyTak()更为强大,全功能版任务通知获取函数。 |
- ①、函数ulTaskNotifyTake()
此函数为获取任务通知函数,当任务通知用作二值信号量或者计数型信号量的时候可以使用此函数来获取信号量,函数原型如下:
uint32_tulTaskNotifyTake(BaseType_t xClearCountOnExit,
TickType_t xTicksToWait);
参数 | 描述 |
---|---|
XClearCountOnExit | 参数为pdFALSE的话在退出函数ulTaskNotifyTake()的时候任务通知值减一,类似计数型信号量。当此参数为pdTRUE的话在退出函数的时候任务任务通知值清零,类似二值信号量。 |
xTickToWait | 阻塞时间。 |
返回值 | 任何值:任务通知值减少或者清零之前的值 |
此函数在文件task.c中定义,函数源码如下:
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait )
{
uint32_t ulReturn;
taskENTER_CRITICAL();
{
if( pxCurrentTCB->ulNotifiedValue == 0UL )(1)
{
pxCurrentTCB->ucNotifyState = taskWAITING_NOTIFICATION;(2)
if( xTicksToWait > ( TickType_t ) 0 )(3)
{
prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE );
traceTASK_NOTIFY_TAKE_BLOCK();
portYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
taskEXIT_CRITICAL();
taskENTER_CRITICAL();
{
traceTASK_NOTIFY_TAKE();
ulReturn = pxCurrentTCB->ulNotifiedValue;(4)
if( ulReturn != 0UL )(5)
{
if( xClearCountOnExit != pdFALSE )(6)
{
pxCurrentTCB->ulNotifiedValue = 0UL;
}
else
{
pxCurrentTCB->ulNotifiedValue = ulReturn - 1;(7)
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
pxCurrentTCB->ucNotifyState = taskNOT_WAITING_NOTIFICATION;(8)
}
taskEXIT_CRITICAL();
return ulReturn;
}
-
(1)、判断任务通知值是否为0,如果为0的话说明还没有接收到任务通知。
-
(2)、修改任务通知状态为taskWAITING_NOTIFICATION。
-
(3)、如果阻塞时间不为0的话就将任务添加到延时列表中,并且进行一次任务调度。
-
(4)、如果任务通知值不为0的话就先获取任务通知值。
-
(5)、任务通知值大于0。
-
(6)、参数xClearCountOnExit不为pdFALSE,那就将任务通知值清零。
-
(7)、如果参数xClearCountOnExit为pdFALSE的话那就将任务通知值减一。
-
(8)、更新任务通知状态为taskNOT_WAITING_NOTIFICATION。
-
②、函数xTaskNotifyWait()
此函数也是用来获取任务通知的,不过此函数比ulTaskNotifyTake()更为强大,不管任务通知用作二值信号量、计数型信号量、队列和事件标志组中的哪一种,都可以使用此函数来获取任务通知。但是当任务通知用作二值信号量和计数型信号量的时候推荐使用函数ulTaskNotifyTake()。此函数原型如下:
BaseType_txTaskNotifyWait(uint32_tulBitsToClearOnEntry,
uint32_t ulBitsToClearOnExit,
uint32_t*pulNotificationValue,
TickType_t xTicksToWait);
参数 | 描述 |
---|---|
ulBitsToClearOnEntry | 当没有接收到任务通知的时候将任务通知值与此参数的取反值进行按位与运算,当此参数为0xffffffff或者ULONG_MAX的时候就会将任务通知值清零。 |
ulBitsToClearOnExit | 如果接收到了任务通知,在做完相应的处理退出函数之前将任务通知值与此参数的取反值进行按位与运算,当此参数为0xffffffff或者ULONG_MAX的时候就会将任务通知值清零。 |
pulNotificationValue | 此参数用来保存任务通知值。 |
xTickToWait | 阻塞时间。 |
返回值 | pdTRUE:获取到了任务通知。pdFALSE:任务通知获取失败。 |
此函数在文件task.c有定义,源代码为:
BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue, TickType_t xTicksToWait )
{
BaseType_t xReturn;
taskENTER_CRITICAL();
{
if( pxCurrentTCB->ucNotifyState != taskNOTIFICATION_RECEIVED )(1)
{
pxCurrentTCB->ulNotifiedValue &= ~ulBitsToClearOnEntry;(2)
pxCurrentTCB->ucNotifyState = taskWAITING_NOTIFICATION;(3)
if( xTicksToWait > ( TickType_t ) 0 )(4)
{
prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE );
traceTASK_NOTIFY_WAIT_BLOCK();
portYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
taskEXIT_CRITICAL();
taskENTER_CRITICAL();
{
traceTASK_NOTIFY_WAIT();
if( pulNotificationValue != NULL )(5)
{
*pulNotificationValue = pxCurrentTCB->ulNotifiedValue;
}
if( pxCurrentTCB->ucNotifyState == taskWAITING_NOTIFICATION )(6)
{
xReturn = pdFALSE;
}
else
{
pxCurrentTCB->ulNotifiedValue &= ~ulBitsToClearOnExit;(7)
xReturn = pdTRUE;
}
pxCurrentTCB->ucNotifyState = taskNOT_WAITING_NOTIFICATION;(8)
}
taskEXIT_CRITICAL();
return xReturn;
}
- (1)、任务同通状态不为taskNOTIFICATION_RECEIVED。
- (2)、将任务通知值与参数ulBitsToClearOnEntry的取反值进行按位与运算。
- (3)、任务通知状态改为taskWAITING_NOTIFICATION。
- (4)、如果阻塞时间大于0的话就要将任务添加到延时列表中,并且进行一次任务切换。
- (5)、如果任务通知状态为taskNOTIFICATION_RECEIVED,并且参数pulNotificationValue有效的话就保存任务通知值。
- (6)、如果任务通知的状态又变为taskWAITING_NOTIFICATION的话就标记xReturn为pdFALSE。
- (7)、如果任务通知的状态一直为taskNOTIFICATION_RECEIVED的话就将任务通知的值与参数ulBitsToClearOnExit的取反值进行按位与运算,并且标记xReturn为pdTRUE,表示获取任务通知成功。
- (8)、标记任务通知的状态为taskNOT_WAITING_NOTIFICATION。