在上一篇我们介绍了FreeRTOS任务的一些基本操作和功能,今天我们会介绍一个很好很强大的功能——任务通知

任务通知可以在不同任务之间传递信息,它可以取代二值信号量、计数信号量、事件标志组、深度为1的消息队列等功能,因为它更快,占用RAM更少,是FreeRTOS自8.2以来推出的重大改进功能。

一、任务通知相关变量

1.1、TCB中的通知相关成员

FreeRTOS每个任务都有一个通知指示值,和一个32位通知值:

任务数据结构(TCB)中与队列通知相关的成员

#if ( configUSE_TASK_NOTIFICATIONS == 1 )
volatile uint32_t ulNotifiedValue;
volatile eNotifyValue eNotifyState;
#endif
  • ulNotifiedValue就是任务中的通知值,任务通知实际上就是围绕这个变量作文章,下面会以“通知值”来代替这个成员变量,
  • eNotifyState用来标志任务是否在等待通知,它有以下3种情况
eNotified 任务已经被通知 带发送通知功能的函数都会首先把eNotifyState设置为eNotified,表示任务已经被通知
eWaitingNotification 任务正在等待通知 接收通知功能的函数会首先把eNotifyState设置为eWaitingNotification,表示任务已经阻塞了正在等待通知
eNotWaitingNotification 空状态 表示任务即没有收到新的通知,也没有正在等待通知,接收通知功能函数在接收到通知处理后,会把eNotifyState设置为eWaitingNotification

根据上一节中的TCB我们的精简,我们现在为TCB补上接下来会用到新的成员:

typedef struct tskTaskControlBlock
{
volatile StackType_t *pxTopOfStack; /*任务堆栈栈顶*/ ListItem_t xGenericListItem; /*任务状态列表项项引用的列表,指示任务状态(准备态、阻塞态、挂起态)*/
ListItem_t xEventListItem; /*状态列表项*/
UBaseType_t uxPriority; /*任务优先级*/
StackType_t *pxStack; /*任务堆栈起始地址*/
char pcTaskName[ configMAX_TASK_NAME_LEN ];/*任务名字*/ volatile uint32_t ulNotifiedValue; /*任务通知值*/
volatile eNotifyValue eNotifyState; /*通知状态标志*/ } tskTCB;

二、任务通知API函数

2.1、发送通知

xTaskNotifyGive() 发送通知,通知值设定为自增方式(每次调用通知值加1)
vTaskNotifyGiveFromISR() 上面函数的中断版本
xTaskNotify()  发送通知,可以自定义通知方式
 xTaskNotifyFromISR()  上面函数的中断版本
 xTaskNotifyAndQuery()  发送通知,自定义通知方式,并且读出上一次的通知值
 xTaskNotifyAndQueryFromISR()  上面函数的中断版本

  其中有5个API是宏,它们关系如下:

  非中断版本:

#define xTaskNotify( xTaskToNotify, ulValue, eAction )  xTaskGenericNotify( ( xTaskToNotify ), ( ulValue ), ( eAction ), NULL )
#define xTaskNotifyAndQuery( xTaskToNotify, ulValue, eAction, pulPreviousNotifyValue )  xTaskGenericNotify( ( xTaskToNotify ), ( ulValue ), ( eAction ), ( pulPreviousNotifyValue ) )
#define xTaskNotifyGive( xTaskToNotify ) xTaskGenericNotify( ( xTaskToNotify ), ( 0 ), eIncrement, NULL )

  中断版本:

#define xTaskNotifyFromISR( xTaskToNotify, ulValue, eAction, pxHigherPriorityTaskWoken )  xTaskGenericNotifyFromISR( ( xTaskToNotify ), ( ulValue ), ( eAction ), NULL, ( pxHigherPriorityTaskWoken ) ) 
 #define xTaskNotifyAndQueryFromISR( xTaskToNotify, ulValue, eAction, pulPreviousNotificationValue, pxHigherPriorityTaskWoken )  xTaskGenericNotifyFromISR( ( xTaskToNotify ), ( ulValue ), ( eAction ), ( pulPreviousNotificationValue ), ( pxHigherPriorityTaskWoken ) )

  

  可以发现,实际上他们底层只有两个函数,

    •   xTaskGenericNotify()
    •   xTaskGenericNotifyFromISR()

  再加上一个直接指向顶层的函数

    •   vTaskNotifyGiveFromISR()

  接下来我们直接看这3个函数,会更便于理解队列通知以及使用

2.1.1生成通知信号函数xTaskGenericNotify():

函数原型:

  BaseType_t xTaskGenericNotify( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction,   uint32_t *pulPreviousNotificationValue )

输入参数:

xTaskToNotify 被通知的任务的句柄
ulValue 输入值
eAction 进行通知方式
*pulPreviousNotificationValue 这个任务上一次的通知值

  

  其中参数eAction最为重要,它决定了我们以什么方式发送通知,我们查看它是一个eNotifyAction类型的结构体,查看这个结构体,可以留意到发送通知的方式分为以下5种:

typedef enum
{
eNoAction = ,
eSetBits,
eIncrement,
eSetValueWithOverwrite,
eSetValueWithoutOverwrite
} eNotifyAction;

现在我们来截取xTaskGenericNotifyFromISR()一段操作通知值的代码,看看这5种信号是怎么改变通知值的:

            switch( eAction )
{
case eSetBits :
pxTCB->ulNotifiedValue |= ulValue;
break; case eIncrement :
( pxTCB->ulNotifiedValue )++;
break; case eSetValueWithOverwrite :
pxTCB->ulNotifiedValue = ulValue;
break; case eSetValueWithoutOverwrite :
if( eOriginalNotifyState != eNotified )
{
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;
}

源码分析(展开折叠查看)

这个函数主要做了3件事:

  1. 将TCB中的通知状态标志eNotifyState设置为已经收到通知的状态
  2. 根据需求更新TCB中的通知值ulNotifiedValue
  3. 解除目标任务的阻塞状态
#if( configUSE_TASK_NOTIFICATIONS == 1 )

    BaseType_t xTaskGenericNotify( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotificationValue )
{
/*
xTaskToNotify:被通知的任务句柄
ulValue: 更新的通知值
eAction: 枚举类型,指明更新通知值的方法,
*pulPreviousNotificationValue:用于获取上次的通知值
*/
TCB_t * pxTCB;
eNotifyValue eOriginalNotifyState;
BaseType_t xReturn = pdPASS; configASSERT( xTaskToNotify );
pxTCB = ( TCB_t * ) xTaskToNotify; taskENTER_CRITICAL();
{
if( pulPreviousNotificationValue != NULL )
{
*pulPreviousNotificationValue = pxTCB->ulNotifiedValue;
}
/*获取上一次的通知状态,保存在局部变量eOriginalNotifyState中,稍后覆盖方法会用到*/
eOriginalNotifyState = pxTCB->eNotifyState; /*更新TCB的通知状态为eNotified(已通知状态)*/ /*【1】*/
pxTCB->eNotifyState = eNotified; /*更新TCB的通知值(pxTCB->ulNotifiedValue),根据eAction的不同,达到传输不同种类通知的目的*/ /*【2】*/
switch( eAction )
{
case eSetBits :
pxTCB->ulNotifiedValue |= ulValue;
break; case eIncrement :
( pxTCB->ulNotifiedValue )++;
break; case eSetValueWithOverwrite :
pxTCB->ulNotifiedValue = ulValue;
break; case eSetValueWithoutOverwrite :
if( eOriginalNotifyState != eNotified )
{
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 the task is in the blocked state specifically to wait for a
notification then unblock it now. */
/* 如果目标任务是阻塞状态,特别是如果在等待通知,那么解除阻塞 */
if( eOriginalNotifyState == eWaitingNotification ) /*【3】*/
{
( void ) uxListRemove( &( pxTCB->xGenericListItem ) );
prvAddTaskToReadyList( pxTCB ); /* The task should not have been on an event list. */
/* 任务不应该已经添加进事件列表 */
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应该设置为阻塞任务时间外的时间。
如果任务因为一些原因(除了一个超时)被解除阻塞,xNextTaskUnblockTime通常保持不变。
因为当滴答计数器等于xNextTaskUnblockTime的时候,它会被重置为一个新的值。
无论如何,如果tickless idling 被使用,它可能是首先进入睡眠模式,
所以在这里重置xNextTaskUnblockTime来确保它被更新*/
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();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
taskEXIT_CRITICAL(); return xReturn;
} #endif /* configUSE_TASK_NOTIFICATIONS */

2.1.2、生成通知信号函数中断版本 xTaskGenericNotifyFromISR()

函数原型:

  BaseType_t xTaskGenericNotifyFromISR( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotificationValue, BaseType_t *pxHigherPriorityTaskWoken )

作为xTaskGenericNotify()的中断版本,xTaskGenericNotifyFromISR()中加了一些中段相关处理,我们可以看到,他们输入参数都相同(增加一个pxHigherPriorityTaskWoken参数用于指示执行期间是否有任务切换发生)。
所以这个函数实际上和xTaskGenericNotify()的操作相同。

  1. 将TCB中的通知状态标志eNotifyState设置为已经收到通知的状态
  2. 根据需求更新TCB中的通知值ulNotifiedValue
  3. 解除目标任务的阻塞状态

2.1.3、一个自增通知的中断定制版 vTaskNotifyGiveFromISR():

函数原型:

  void vTaskNotifyGiveFromISR( TaskHandle_t xTaskToNotify, BaseType_t *pxHigherPriorityTaskWoken )

这个函数是xTaskGenericNotifyFromISR()的压缩定制版,去掉了3个输入参数ulValue、eAction、pulPreviousNotificationValue和对应这3个参数相关的处理程序,因为它是特别为自增型通知定制的,与xTaskGenericNotifyFromISR()这个函数提高了效率,大概FreeRTOS作者认为自增通知是常用的通知类型,所以特意写了这个优化版本的函数。

所以这个函数实际上也和xTaskGenericNotify()的操作相同

源码分析:

  1. 将TCB中的通知状态标志eNotifyState设置为已经收到通知的状态
  2. 根据需求更新TCB中的通知值ulNotifiedValue(自增)
  3. 解除目标任务的阻塞状态
#if( configUSE_TASK_NOTIFICATIONS == 1 )

    void vTaskNotifyGiveFromISR( TaskHandle_t xTaskToNotify, BaseType_t *pxHigherPriorityTaskWoken )
{
TCB_t * pxTCB;
eNotifyValue eOriginalNotifyState;
UBaseType_t uxSavedInterruptStatus; configASSERT( xTaskToNotify ); /* RTOS ports that support interrupt nesting have the concept of a
maximum system call (or maximum API call) interrupt priority.
Interrupts that are above the maximum system call priority are keep
permanently enabled, even when the RTOS kernel is in a critical section,
but cannot make any calls to FreeRTOS API functions. If configASSERT()
is defined in FreeRTOSConfig.h then
portASSERT_IF_INTERRUPT_PRIORITY_INVALID() will result in an assertion
failure if a FreeRTOS API function is called from an interrupt that has
been assigned a priority above the configured maximum system call
priority. Only FreeRTOS functions that end in FromISR can be called
from interrupts that have been assigned a priority at or (logically)
below the maximum system call interrupt priority. FreeRTOS maintains a
separate interrupt safe API to ensure interrupt entry is as fast and as
simple as possible. More information (albeit Cortex-M specific) is
provided on the following link:
http://www.freertos.org/RTOS-Cortex-M3-M4.html */ portASSERT_IF_INTERRUPT_PRIORITY_INVALID(); pxTCB = ( TCB_t * ) xTaskToNotify; uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
{
eOriginalNotifyState = pxTCB->eNotifyState;
pxTCB->eNotifyState = eNotified; /*【1】*/ /* 'Giving' is equivalent to incrementing a count in a counting
semaphore. */
/* 给予是等价于在计数信号量中增加一个计数 */
( pxTCB->ulNotifiedValue )++; /*【2】*/ traceTASK_NOTIFY_GIVE_FROM_ISR(); /* If the task is in the blocked state specifically to wait for a
notification then unblock it now. */
/* 如果任务在阻塞状态,明确地等待一个通知,然后马上解除阻塞*/
if( eOriginalNotifyState == eWaitingNotification ) /*【3】*/
{
/* The task should not have been on an event list. */
/* 任务不应该已经加入事件列表了 */
configASSERT( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) == NULL ); if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
{
( void ) uxListRemove( &( pxTCB->xGenericListItem ) );
prvAddTaskToReadyList( pxTCB );
}
else
{
/* The delayed and ready lists cannot be accessed, so hold
this task pending until the scheduler is resumed. */
/* 延时和准备列表无法被访问,所以保持这个任务挂起,知道调度器被恢复 */
vListInsertEnd( &( xPendingReadyList ), &( pxTCB->xEventListItem ) );
} if( pxTCB->uxPriority > pxCurrentTCB->uxPriority )
{
/* The notified task has a priority above the currently
executing task so a yield is required. */
/* 通知任务已经比当前执行任务更高,所以需要进行切换 */
if( pxHigherPriorityTaskWoken != NULL )
{
*pxHigherPriorityTaskWoken = pdTRUE;
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );
} #endif /* configUSE_TASK_NOTIFICATIONS */

接下来我们回到API函数,看看这6个API的功能,以及它们调用的是哪个底层函数:

2.2接收通知

ulTaskNotifyTake()  提取通知

适用于二值通知(eNoAction)和自增通知
(eIncrement)

 xTaskNotifyWait()  等待通知  适用所有通知,但不附带自增通知的自减功能

2.2.1、ulTaskNotifyTake()

函数原型:

  uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait )

输入参数:

BaseType_t xClearCountOnExit 退出时是否清除
TickType_t xTicksToWait 最大等待时间
  • xClearCountOnExit:这个参数可以传入pdTRUE或者pdFALSE

  

  • xTicksToWait:在等待通知的时候,任务会进入阻塞状态,任务进入阻塞状态的最大时间(以Tick为单位,可以用pdMS_TO_TICKS()将毫秒换为tick)

源码分析:(展开折叠查看)

  这个函数主要做了4件事:

  1. 改变TCB中的eNotifyState为正在等待通知状态
  2. 让任务进入阻塞或挂起状态等待通知
  3. 收到通知后,对通知值ulNotifiedValue进行操作(删除或自减)
  4. 改变TCB中的eNotifyState为空状态,因为读取通知的操作已经完成了
#if( configUSE_TASK_NOTIFICATIONS == 1 )

    uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait )
{
TickType_t xTimeToWake;
uint32_t ulReturn; taskENTER_CRITICAL();
{
/* Only block if the notification count is not already non-zero. */
/* 仅仅当通知值为0的时候, 才进行阻塞操作 */
if( pxCurrentTCB->ulNotifiedValue == 0UL )
{
/* Mark this task as waiting for a notification. */
/* 屏蔽这个任务来等待通知 */
pxCurrentTCB->eNotifyState = eWaitingNotification; /*改变TCB中的eNotifyState 为 eWaitingNotification*/ /*【1】*/ if( xTicksToWait > ( TickType_t ) )
{
/* The task is going to block. First it must be removed
from the ready list. */
/* 任务将会阻塞。 但首先必须从准备列表移除 */
if( uxListRemove( &( pxCurrentTCB->xGenericListItem ) ) == ( UBaseType_t ) )
{
/* The current task must be in a ready list, so there is
no need to check, and the port reset macro can be called
directly. */
/* 当前任务必须在准备列表,所以没有必要再检查,接口重置宏可以被直接调用 */
portRESET_READY_PRIORITY( pxCurrentTCB->uxPriority, uxTopReadyPriority );
}
else
{
mtCOVERAGE_TEST_MARKER();
} #if ( INCLUDE_vTaskSuspend == 1 ) /*【2】*/
{
/* 如果设置了 xTicksToWait 为 portMAX_DELAY。任务会直接挂起*/
if( xTicksToWait == portMAX_DELAY )
{
/* Add the task to the suspended task list instead
of a delayed task list to ensure the task is not
woken by a timing event. It will block
indefinitely. */
/* 把任务添加到挂起任务列表,而不是延时任务列表(阻塞状态会倒计时)。这是为了确认任务没有被时间事件唤醒。任务会被无限期的阻塞(直接挂起) */
vListInsertEnd( &xSuspendedTaskList, &( pxCurrentTCB->xGenericListItem ) );
}
else
{
/* Calculate the time at which the task should be
woken if no notification events occur. This may
overflow but this doesn't matter, the scheduler will
handle it. */
/* 如果没有通知事件发生,计算任务应该被唤醒的时间
这个可能溢出,但是没有关系,调度器会处理它的*/ xTimeToWake = xTickCount + xTicksToWait; /*计算唤醒时间*/
prvAddCurrentTaskToDelayedList( xTimeToWake ); /*把计算好的时间添加到延时列表。交给调度器处理*/
}
}
#else /* INCLUDE_vTaskSuspend */
{
/* Calculate the time at which the task should be
woken if the event does not occur. This may
overflow but this doesn't matter, the scheduler will
handle it. */
/* 如果没有通知事件发生,计算任务应该被唤醒的时间
这个可能溢出,但是没有关系,调度器会处理它的*/
xTimeToWake = xTickCount + xTicksToWait;
prvAddCurrentTaskToDelayedList( xTimeToWake );
}
#endif /* INCLUDE_vTaskSuspend */ traceTASK_NOTIFY_TAKE_BLOCK(); /* 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
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
taskEXIT_CRITICAL(); taskENTER_CRITICAL();
{
traceTASK_NOTIFY_TAKE();
ulReturn = pxCurrentTCB->ulNotifiedValue;/*设置返回值为收到的通知值*/ if( ulReturn != 0UL ) /*【3】*/
{
if( xClearCountOnExit != pdFALSE )
{
pxCurrentTCB->ulNotifiedValue = 0UL;/*清零通知值*/
}
else
{
( pxCurrentTCB->ulNotifiedValue )--;/*减少通知值(对自增通知的特殊处理方法)*/
}
}
else
{
mtCOVERAGE_TEST_MARKER();
} pxCurrentTCB->eNotifyState = eNotWaitingNotification;/* 清除等待/接收通知状态 */ /*【4】*/
}
taskEXIT_CRITICAL(); return ulReturn;
} #endif /* configUSE_TASK_NOTIFICATIONS */

  *在我们查看源码的时候,我们可以留意到,当我们设置输入参数为xTicksToWait为portMAX_DELAY的时候,而且INCLUDE_vTaskSuspend(激活挂起状态)宏定义为1的时候任务不是阻塞,而是直接挂起

                    #if ( INCLUDE_vTaskSuspend == 1 )                    /*【2】*/
{
/* 如果设置了 xTicksToWait 为 portMAX_DELAY。任务会直接挂起*/
if( xTicksToWait == portMAX_DELAY )
{
/* Add the task to the suspended task list instead
of a delayed task list to ensure the task is not
woken by a timing event. It will block
indefinitely. */
/* 把任务添加到挂起任务列表,而不是延时任务列表(阻塞状态会倒计时)。这是为了确认任务没有被时间事件唤醒。任务会被无限期的阻塞(直接挂起) */
vListInsertEnd( &xSuspendedTaskList, &( pxCurrentTCB->xGenericListItem ) );
}
else
{
/* Calculate the time at which the task should be
woken if no notification events occur. This may
overflow but this doesn't matter, the scheduler will
handle it. */
/* 如果没有通知事件发生,计算任务应该被唤醒的时间
这个可能溢出,但是没有关系,调度器会处理它的*/ xTimeToWake = xTickCount + xTicksToWait; /*计算唤醒时间*/
prvAddCurrentTaskToDelayedList( xTimeToWake ); /*把计算好的时间添加到延时列表。交给调度器处理*/
}
}

官方例子:

2.2.2、xTaskNotifyWait()

  BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue, TickType_t xTicksToWait )

输入参数:

  

  • ulBitsToClearOnEntry:在进入阻塞之前把指定的比特清除
  • ulBitsToClearOnExit:在接收到通知并处理后把指定的比特位清除
  • *pulNotificationValue:用于储存新到的通知值
  • xTicksToWait:最大阻塞时间

返回值:

  两种情况

  

源码分析:(展开折叠查看)

这个函数和ulTaskNotifyTake很像,主要做了5件事:

  1. 根据ulBitsToClearOnEntry先清除一下通知值相应的位
  2. 改变TCB中的eNotifyState为正在等待通知状态
  3. 让任务进入阻塞或挂起状态等待通知
  4. 收到通知后,对通知值ulNotifiedValue进行操作(根据ulBitsToClearOnExit清除相应位)
  5. 改变TCB中的eNotifyState为空状态,因为读取通知的操作已经完成了
#if( configUSE_TASK_NOTIFICATIONS == 1 )

    BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue, TickType_t xTicksToWait )
{
TickType_t xTimeToWake;
BaseType_t xReturn; /* */
taskENTER_CRITICAL();
{
/* Only block if a notification is not already pending. */
/* 只有没收到通知,才会阻塞任务(换句话说,如果现在已经收到通知了就不需要阻塞任务了,直接处理就好了) */
if( pxCurrentTCB->eNotifyState != eNotified )
{
/* 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. */
/* 清除任务通知值的比特位,因为这些比特肯能被通知任务或者中断置位了。
这个可以用于把值清0*/
pxCurrentTCB->ulNotifiedValue &= ~ulBitsToClearOnEntry; /*【1】*/ /* Mark this task as waiting for a notification. */
/* 记录这个任务为等待通知的状态*/
pxCurrentTCB->eNotifyState = eWaitingNotification; /*【2】*/ if( xTicksToWait > ( TickType_t ) )
{
/* The task is going to block. First it must be removed
from the ready list. */
/* 任务马上就要阻塞了,首先要把它移出准备列表 */
if( uxListRemove( &( pxCurrentTCB->xGenericListItem ) ) == ( UBaseType_t ) )
{
/* The current task must be in a ready list, so there is
no need to check, and the port reset macro can be called
directly. */
/* 当前任务肯定在准备列表中 ,所以没有必要检查了,接口重置宏可以被直接调用 */
portRESET_READY_PRIORITY( pxCurrentTCB->uxPriority, uxTopReadyPriority );
}
else
{
mtCOVERAGE_TEST_MARKER();
} #if ( INCLUDE_vTaskSuspend == 1 ) /*【3】*/
{
if( xTicksToWait == portMAX_DELAY )
{
/* Add the task to the suspended task list instead
of a delayed task list to ensure the task is not
woken by a timing event. It will block
indefinitely. */
/* 把任务加入挂起任务列表,而不是延时任务列表。这是为了确保任务没有被时间事件唤醒
这个任务会无限阻塞 */
vListInsertEnd( &xSuspendedTaskList, &( pxCurrentTCB->xGenericListItem ) );
}
else
{
/* Calculate the time at which the task should be
woken if no notification events occur. This may
overflow but this doesn't matter, the scheduler will
handle it. */
/* 计算在如果没有通知事件,发生任务应该被唤醒的时间 。
这个可能会导致溢出,但是没有关系。调度器会处理它的*/
xTimeToWake = xTickCount + xTicksToWait;
prvAddCurrentTaskToDelayedList( xTimeToWake );
}
}
#else /* INCLUDE_vTaskSuspend */
{
/* Calculate the time at which the task should be
woken if the event does not occur. This may
overflow but this doesn't matter, the scheduler will
handle it. */
/* 计算在如果没有通知事件,发生任务应该被唤醒的时间 。
这个可能会导致溢出,但是没有关系。调度器会处理它的*/
xTimeToWake = xTickCount + xTicksToWait;
prvAddCurrentTaskToDelayedList( xTimeToWake );
}
#endif /* INCLUDE_vTaskSuspend */ traceTASK_NOTIFY_WAIT_BLOCK(); /* 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
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
taskEXIT_CRITICAL(); taskENTER_CRITICAL();
{
traceTASK_NOTIFY_WAIT(); if( pulNotificationValue != NULL )
{
/* Output the current notification value, which may or may not
have changed. */
/* 设置pulNotificationValue参数为当前通知值,不管它有没有改变*/
*pulNotificationValue = pxCurrentTCB->ulNotifiedValue;
} /* If eNotifyValue 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->eNotifyState == eWaitingNotification )
{
/* 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 &= ~ulBitsToClearOnExit; /*【4】*/
xReturn = pdTRUE;
} /*接收通知完成,修改TCB的eNotifyState为空状态*/
pxCurrentTCB->eNotifyState = eNotWaitingNotification; /*【5】*/
}
taskEXIT_CRITICAL(); return xReturn;
} #endif /* configUSE_TASK_NOTIFICATIONS */

官方使用例子:

  这个例子是使用通知代替事件组,也就是置位通知

  1.   等待通知信号,任务会进入阻塞/挂起状态。
  2.   判断通知信号的第几位被置位了

3、最后笔者写了四个例子,提供大家参考

使用不带数据的二值任务通知(可以代替二进制信号量)

//Notify is used for binary semaphore
void vDemoTaskA(void *Parammenters)
{
for(;;)
{
//xTaskNotifyGive( HandleOfTaskB );
xTaskNotify( HandleOfTaskB, , eNoAction ); vTaskDelay( pdMS_TO_TICKS() );
}
} void vDemoTaskB(void *Parammenters)
{
for(;;)
{
ulTaskNotifyTake( pdTRUE , portMAX_DELAY );
//add your codes here
}
} void vTaskCreate(void )
{
xTaskCreate( vDemoTaskA, "vDemoTaskA", , NULL, , NULL );
xTaskCreate( vDemoTaskB, "vDemoTaskB", , NULL, , &HandleOfTaskB );
} /*******************************************************************************
* Function Name : main
* Description :
* Input : None
* Output : None
* Return : None
*******************************************************************************/
int main( void )
{ vTaskCreate(); vTaskStartScheduler( ); while()
{
//should not be here
} }

使用自增的任务通知(可以代替计数信号量)

//Notify is used for counting semaphore
void vDemoTaskA(void *Parammenters)
{
for(;;)
{
xTaskNotifyGive( HandleOfTaskB );
xTaskNotifyGive( HandleOfTaskB );
xTaskNotifyGive( HandleOfTaskB ); vTaskDelay( pdMS_TO_TICKS() );
}
}
void vDemoTaskB(void *Parammenters)
{
uint32_t notifyCNT; for(;;)
{ if( notifyCNT = ulTaskNotifyTake( pdFALSE , portMAX_DELAY ) )
{
//should come in here for three times each turn
//add your codes here
} }
} void vTaskCreate(void )
{
xTaskCreate( vDemoTaskA, "vDemoTaskA", , NULL, , NULL );
xTaskCreate( vDemoTaskB, "vDemoTaskB", , NULL, , &HandleOfTaskB );
} /*******************************************************************************
* Function Name : main
* Description :
* Input : None
* Output : None
* Return : None
*******************************************************************************/
int main( void )
{ vTaskCreate(); vTaskStartScheduler( ); while()
{
//should not be here
} }

使用置位任务通知(可以代替事件标志组)

//Notify is used for event group
void vDemoTaskA(void *Parammenters)
{
for(;;)
{
xTaskNotify( HandleOfTaskB, (uint32_t) , eSetBits );
xTaskNotify( HandleOfTaskB, (uint32_t)<< , eSetBits );
xTaskNotify( HandleOfTaskB, (uint32_t)<< , eSetBits ); vTaskDelay( pdMS_TO_TICKS() );
}
}
void vDemoTaskB(void *Parammenters)
{
#define ULONG_MAX 0xffffffff
uint32_t ulNotifiedValue=;
for(;;)
{
xTaskNotifyWait( 0x00 , ULONG_MAX , &ulNotifiedValue ,portMAX_DELAY ); if( ulNotifiedValue & () )
{
//add your codes here
}
if( ulNotifiedValue & (<<) )
{
//add your codes here
}
if( ulNotifiedValue & (<<) )
{
//add your codes here
}
if( ulNotifiedValue & (<<) )
{
//add your codes here
}
//you can add more events below if you need them
}
} void vTaskCreate(void )
{
xTaskCreate( vDemoTaskA, "vDemoTaskA", , NULL, , NULL );
xTaskCreate( vDemoTaskB, "vDemoTaskB", , NULL, , &HandleOfTaskB );
} /*******************************************************************************
* Function Name : main
* Description :
* Input : None
* Output : None
* Return : None
*******************************************************************************/
int main( void )
{ vTaskCreate(); vTaskStartScheduler( ); while()
{
//should not be here
} }

使用传递消息任务通知(可以代替深度为一的消息队列)

//Notify is used for sending message
void vDemoTaskA(void *Parammenters)
{
uint32_t pTestBuff[] = { ,,,, };
int i; for(;;)
{
for(i=;i<;i++)
{
//xTaskNotify( HandleOfTaskB, pTestBuff[i] , eSetValueWithOverwrite ); //The notify can not be overwrite
xTaskNotify( HandleOfTaskB, pTestBuff[i] , eSetValueWithoutOverwrite );//The notify can be overwrite vTaskDelay( pdMS_TO_TICKS() );
}
}
}
void vDemoTaskB(void *Parammenters)
{
#define ULONG_MAX 0xffffffff
uint32_t ulNotifiedValue=;
uint32_t pRecBuff[]={};
int i; for(;;)
{
for(i=;i<;i++)
{
xTaskNotifyWait( 0x00 , ULONG_MAX , &ulNotifiedValue ,portMAX_DELAY );
pRecBuff[i] = ulNotifiedValue;
//add your codes here
}
}
} void vTaskCreate(void )
{
xTaskCreate( vDemoTaskA, "vDemoTaskA", , NULL, , NULL );
xTaskCreate( vDemoTaskB, "vDemoTaskB", , NULL, , &HandleOfTaskB );
} /*******************************************************************************
* Function Name : main
* Description :
* Input : None
* Output : None
* Return : None
*******************************************************************************/
int main( void )
{ vTaskCreate(); vTaskStartScheduler( ); while()
{
//should not be here
} }
 

FreeRTOS 任务与调度器(2)的更多相关文章

  1. FreeRTOS 任务与调度器(1)

    前言: Task.c和Task.h文件内是FreeRTOS的核心内容,所有任务和调度器相关的API函数都在这个文件中,它包括下图这些内容FreeRTOS文件如下: Task.c和Task.h文件内是F ...

  2. FreeRTOS调度器

    FreeRTOS----调度器 调度器的启动流程分析 当创建完任务之后,会调用vTaskStartScheduler()函数,启动任务调度器: void vTaskStartScheduler( vo ...

  3. 【freertos】005-启动调度器分析

    前言 本节主要讲解启动调度器. 这些都是与硬件相关,所以会分两条线走:posix和cortex m3. 原文:李柱明博客:https://www.cnblogs.com/lizhuming/p/160 ...

  4. FreeRTOS --(9)任务管理之启动调度器

    转载自 https://blog.csdn.net/zhoutaopower/article/details/107057528 在使用 FreeRTOS 的时候,一般的,先创建若干任务,但此刻任务并 ...

  5. FreeRTOS - 调度器

    原文地址:http://www.cnblogs.com/god-of-death/p/6942641.html 绝大多数情况下,调度器的配置如下: 下面的说明基于上面的调度器配置: 如果有更高优先级的 ...

  6. 大数据之Yarn——Capacity调度器概念以及配置

    试想一下,你现在所在的公司有一个hadoop的集群.但是A项目组经常做一些定时的BI报表,B项目组则经常使用一些软件做一些临时需求.那么他们肯定会遇到同时提交任务的场景,这个时候到底如何分配资源满足这 ...

  7. [Spring]支持注解的Spring调度器

    概述 如果想在Spring中使用任务调度功能,除了集成调度框架Quartz这种方式,也可以使用Spring自己的调度任务框架. 使用Spring的调度框架,优点是:支持注解(@Scheduler),可 ...

  8. 编写简单的ramdisk(选择IO调度器)

    前言 目前linux中包含anticipatory.cfq.deadline和noop这4个I/O调度器.2.6.18之前的linux默认使用anticipatory,而之后的默认使用cfq.我们在前 ...

  9. Erlang/OTP 17.0-rc1 新引入的"脏调度器"浅析

    最近在做一些和 NIF 有关的事情,看到 OTP 团队发布的 17 rc1 引入了一个新的特性“脏调度器”,为的是解决 NIF 运行时间过长耗死调度器的问题.本文首先简单介绍脏调度器机制的用法,然后简 ...

随机推荐

  1. 洗礼灵魂,修炼python(30)--装饰器(2)—>装饰器总结+进阶使用

    在上一篇博文的经典案例中,我想你应该对装饰器有很好的了解了,不过光有那些还不够真的,还需要总结和进阶一下,所以本篇博文解析装饰器进阶. 装饰器 1.什么是装饰器? 个人理解:装饰器又叫语法糖,指的是对 ...

  2. pyenv离线安装python各版本

    1.问题描述: 可能是国内的网络原因,在线用pyenv安装python老是定住没反应 [root@zabbix ~]# pyenv install Downloading Python-.tar.xz ...

  3. Linux自制编译内核

    今天我们来自己学习编译内核并使用它.自制内核是个人定制版,定制自己专属的内核环境. 我们先看看编译步骤有哪些: 步骤: 1.# tar xf linux-3.10.37.tar.xz -C /usr/ ...

  4. February 8th, 2018 Week 6th Thursday

    When you fall in love, friends, let yourself fall. 当你坠入爱河,我的朋友,你就放手去爱吧. To love someone is like movi ...

  5. javascript闭包—围观大神如何解释闭包

    闭包的概念已经出来很长时间了,网上资源一大把,本着拿来主意的方法来看看. 这一篇文章 学习Javascript闭包(Closure) 是大神阮一峰的博文,作者循序渐进,讲的很透彻.下面一一剖析. 1. ...

  6. Java设计模式之十三 ---- 观察者模式和空对象模式

    前言 在上一篇中我们学习了行为型模式的备忘录模式(Memento Pattern)和状态模式(Memento Pattern).本篇则来学习下行为型模式的最后两个模式,观察者模式(Observer P ...

  7. golang变量的注意

    方法内部的变量使用方法: /* 定义三个变量,它们分别初始化为相应的值 vname1为v1,vname2为v2,vname3为v3 编译器会根据初始化的值自动推导出相应的类型 */ vname1, v ...

  8. lamp/lnmp下添加PHP扩展

    在linux下安装好基本的PHP运行环境后有时候添加了新的功能,就得增加新的扩展,比如之前没有安装redis扩展,可以手动编译安装相关的扩展可以找下下载的php源码包中,ext目录下是否有相关的扩展源 ...

  9. Spring AOP的实现研究

    1. 背景 在前文Spring IOC容器创建bean过程浅析已经介绍了Spring IOC创建初始化bean的大致过程.现在对Spring的AOP实现机制进行研究分析. 2. 名词与概念 名词 概念 ...

  10. 30个你 “ 不可能全部会做 ” 的javascript题目

    1,以下表达式的运行结果是: ["1","2","3"].map(parseInt) A.["1","2&qu ...