【freertos】013-任务通知及其实现细节

前言

参考:

13.1 任务通知实现原理个人构想

任务通知的实现机制和消息队列和事件标志机制不一样。

消息队列和事件标志机制实现需要的资源,数据结构都是需要申请,创建的,独立于内核,分离于任务的组件。

这些组件的实现构想都是拿个内存块组成数据结构,一部分内存做信息传递,一部分内存做阻塞链表等等,然后通过封装各种API实现各种对这个数据结构的骚操作而形成的任务间通信机制。

而任务通知,首先内存源自任务控制块,这个内存的数据结构并未确定,所以我们能把这个内存做信息传递。

阻塞部分,我们可以利用系统的延时链表,做个等待信息阻塞。

不过没有发送阻塞,因为向某个任务发送信息只是向某个任务控制块写入数据的骚操作而已,没有因为没有空间而阻塞的这个说法,因为任务控制块根本就没有给被写入失败而阻塞的链表的内存。

明白了任务通知的实现原理,就知道这玩意是啥了。

13.2 任务通知概念

每个RTOS任务都有一个任务通知数组。

每个任务通知都有一个通知状态,可以是pending或者not pending,并且有一个32位的通知值。

常量configTASK_NOTIFICATION_ARRAY_ENTRIES设置任务通知数组中的索引数量。

注意:在FreeRTOS V10.4.0之前,任务只有一个任务通知,而不是一组通知。

任务的通知是直接发送给任务的事件,而不是通过队列、事件组或信号量等中间对象间接发送给任务。

向任务发送直接到任务的通知会将目标任务通知的状态设置为pending

就像一个任务可以阻塞一个中间对象,比如一个信号量,以等待该信号量可用一样,一个任务也可以阻塞一个任务通知,以等待该通知的状态变为pending

13.3 任务通知实现各种IPC

任务通知方式可以实现计数信号量,二值信号量,事件标志组和消息邮箱(消息邮箱就是消息队列长度为1的情况)。

使用方法与前面章节讲解的事件标志组和信号量基本相同,只是换了不同的函数来实现。

任务通知方式实现的计数信号量,二值信号量,事件标志组和消息邮箱是通过修改任务对应任务控制块的ulNotifiedValue数组成员实现:

  • 覆盖该值,无论接收任务是否读取了被覆盖的值。(消息邮箱)
  • 仅当接收任务已读取才能写入。(消息队列)
  • 在值中设置一个或多个位。(事件组)
  • 增加(加1)值。(信号量)

调用xTaskNotifyWait()xTaskNotifyWaitIndexed()来读取一个通知值,将该通知的状态清除为not pending

通知状态也可以通过调用xTaskNotifyStateClear()xTaskNotifyStateClearIndexed()显式地设置为not pending

13.4 任务通知性能优势

使用任务通知比通过信号量等ICP通信方式解除阻塞的任务要快45%,并且更加省RAM内存空间。

13.5 任务通知使用注意

  1. freertos的任务通知功能默认是启用的,可以通过在FreeRTOSConfig.h中将configUSE_TASK_NOTIFICATIONS设置为0来关闭。
  2. RTOS 任务通知只能在只有一个任务可以作为事件的接收者时使用。
  3. 数组中的每个通知都是独立操作的。一个任务一次只能阻塞数组中的一个通知,并且不会被发送到任何其他数组索引的通知解除阻塞。
  4. 在使用 RTOS 任务通知代替队列的情况下:虽然接收任务可以在阻塞状态等待通知(因此不消耗任何 CPU 时间),但如果发送不能立即完成,则发送任务不能在阻塞状态下等待发送完成。
  5. FreeRTOS StreamMessage Buffers 在数组索引 0 处使用任务通知。如果想在调用 StreamMessage Buffer API 函数时保持任务通知的状态,则在数组索引大于 0 处使用任务通知。

13.6 任务通知数据结构

任务通知是任务控制块的资源。

/* 任务控制块 */
typedef struct tskTaskControlBlock
{
    volatile StackType_t * pxTopOfStack; /*< 指向放在任务堆栈上的最后一项的位置。这必须是TCB结构体的第一个成员。 */

    #if ( portUSING_MPU_WRAPPERS == 1 )
        xMPU_SETTINGS xMPUSettings; /*< The MPU settings are defined as part of the port layer.  THIS MUST BE THE SECOND MEMBER OF THE TCB STRUCT. */
    #endif

    ListItem_t xStateListItem;                  /*< The list that the state list item of a task is reference from denotes the state of that task (Ready, Blocked, Suspended ). */
    ListItem_t xEventListItem;                  /*< Used to reference a task from an event list. */
    UBaseType_t uxPriority;                     /*< The priority of the task.  0 is the lowest priority. */
    StackType_t * pxStack;                      /*< Points to the start of the stack. */
    char pcTaskName[ configMAX_TASK_NAME_LEN ]; /*< Descriptive name given to the task when created.  Facilitates debugging only. */ /*lint !e971 Unqualified char types are allowed for strings and single characters only. */

    #if ( ( portSTACK_GROWTH > 0 ) || ( configRECORD_STACK_HIGH_ADDRESS == 1 ) )
        StackType_t * pxEndOfStack; /*< Points to the highest valid address for the stack. */
    #endif

    #if ( portCRITICAL_NESTING_IN_TCB == 1 )
        UBaseType_t uxCriticalNesting; /*< Holds the critical section nesting depth for ports that do not maintain their own count in the port layer. */
    #endif

    #if ( configUSE_TRACE_FACILITY == 1 )
        UBaseType_t uxTCBNumber;  /*< Stores a number that increments each time a TCB is created.  It allows debuggers to determine when a task has been deleted and then recreated. */
        UBaseType_t uxTaskNumber; /*< Stores a number specifically for use by third party trace code. */
    #endif

    #if ( configUSE_MUTEXES == 1 )
        UBaseType_t uxBasePriority; /*< The priority last assigned to the task - used by the priority inheritance mechanism. */
        UBaseType_t uxMutexesHeld;
    #endif

    #if ( configUSE_APPLICATION_TASK_TAG == 1 )
        TaskHookFunction_t pxTaskTag;
    #endif

    #if ( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 )
        void * pvThreadLocalStoragePointers[ configNUM_THREAD_LOCAL_STORAGE_POINTERS ];
    #endif

    #if ( configGENERATE_RUN_TIME_STATS == 1 )
        configRUN_TIME_COUNTER_TYPE ulRunTimeCounter; /*< Stores the amount of time the task has spent in the Running state. */
    #endif

    #if ( configUSE_NEWLIB_REENTRANT == 1 )

        /* Allocate a Newlib reent structure that is specific to this task.
         * Note Newlib support has been included by popular demand, but is not
         * used by the FreeRTOS maintainers themselves.  FreeRTOS is not
         * responsible for resulting newlib operation.  User must be familiar with
         * newlib and must provide system-wide implementations of the necessary
         * stubs. Be warned that (at the time of writing) the current newlib design
         * implements a system-wide malloc() that must be provided with locks.
         *
         * See the third party link //www.nadler.com/embedded/newlibAndFreeRTOS.html
         * for additional information. */
        struct  _reent xNewLib_reent;
    #endif

    #if ( configUSE_TASK_NOTIFICATIONS == 1 )
        volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
        volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
    #endif

    /* See the comments in FreeRTOS.h with the definition of
     * tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE. */
    #if ( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) /*lint !e731 !e9029 Macro has been consolidated for readability reasons. */
        uint8_t ucStaticallyAllocated;                     /*< Set to pdTRUE if the task is a statically allocated to ensure no attempt is made to free the memory. */
    #endif

    #if ( INCLUDE_xTaskAbortDelay == 1 )
        uint8_t ucDelayAborted;
    #endif

    #if ( configUSE_POSIX_ERRNO == 1 )
        int iTaskErrno;
    #endif
} tskTCB;

ulNotifiedValue

  • 任务通知值数组。

ucNotifyState:

  • 任务通知状态数组。
  • 与任务通知值一一对应。

13.7 任务通知为什么没有写阻塞?

通过任务通知的数据结构就知道,其数据结构嵌入在任务控制块中,一个通知对应一个通知状态。

13.7.1 读阻塞实现

读阻塞很容易实现,读取当前任务通知不满足条件时,可以将其阻塞到延时链表或者挂起链表,当有任务往这个任务通知发送数据时,满足当前任务通知要求,可以将当前任务通知从延时链表或者挂起链表解除阻塞。

13.7.2 写阻塞实现(修改内核组件实现)

先按照当前官方任务通知数据结构分析,并不能实现。

试想下,如果不能写,就进入阻塞,可以参考读阻塞一样先插入延时链表或挂起链表,但是怎么唤醒呢?当任务通知可以写时,通过什么方式找到这个写阻塞的任务呢?

任务通知这个对象嵌入到任务控制块中,归属于某个任务,其它任务往这里写,发生阻塞,当可写时,需要解除阻塞时,是找不到这个写任务并将其唤醒的。

以上就是按照freertos任务通知数据结构基础来演进分析。

其实解决了写阻塞的唤醒,就可以解决写阻塞这个问题了。
也很简单,在任务通知这个数据结构中再添加一个写阻塞链表即可。有兴趣的同学可以自己改写下内核源码实现。

13.8 任务通知类型

typedef enum
{
    eNoAction = 0,            /* 通知任务而不更新其通知值 */
    eSetBits,                 /* 事件组。按位或设置。 */
    eIncrement,               /* 信号量。自增。 */
    eSetValueWithOverwrite,   /* 邮箱。覆盖希写入 */
    eSetValueWithoutOverwrite /* 队列。对应通知为空时才写入。 */
} eNotifyAction;

13.9 任务通知状态

/* 任务通知状态 */
#define taskNOT_WAITING_NOTIFICATION              ( ( uint8_t ) 0 ) /* 初始值为0,所以默认态也应该为0 */
#define taskWAITING_NOTIFICATION                  ( ( uint8_t ) 1 )
#define taskNOTIFICATION_RECEIVED                 ( ( uint8_t ) 2 )

taskNOT_WAITING_NOTIFICATION:任务没有在等待通知。

taskWAITING_NOTIFICATION:任务在等待通知。

taskNOTIFICATION_RECEIVED:任务接收到通知。

13.10 发送任务通知基函数

在分析任务通知做信号量前先分析一个任务通知基函数xTaskGenericNotify(),任务消息、任务邮箱、任务事件这些发送端都是通过封装该API实现的。

xTaskGenericNotify()

  • xTaskToNotify:任务句柄。即是任务通知的对象。

  • uxIndexToNotify

    • 通知将发送到的目标任务通知值数组中的索引。
    • uxIndexToNotify必须小于configTASK_NOTIFICATION_ARRAY_ENTRIES
  • ulValue:用于更新目标任务的通知值。

  • pulPreviousNotificationValue:任务原本的通知值返回。(回传)

#if ( configUSE_TASK_NOTIFICATIONS == 1 )
    BaseType_t xTaskGenericNotify( TaskHandle_t xTaskToNotify,
                                   UBaseType_t uxIndexToNotify,
                                   uint32_t ulValue,
                                   eNotifyAction eAction,
                                   uint32_t * pulPreviousNotificationValue )
    {
        TCB_t * pxTCB;
        BaseType_t xReturn = pdPASS;
        uint8_t ucOriginalNotifyState;

        /* 任务通知索引不能溢出 */
        configASSERT( uxIndexToNotify < configTASK_NOTIFICATION_ARRAY_ENTRIES );
        /* 参数校验 */
        configASSERT( xTaskToNotify );
        pxTCB = xTaskToNotify;

        taskENTER_CRITICAL(); /* 进入临界 */
        {
            if( pulPreviousNotificationValue != NULL ) /* 需要回传 */
            {
                /* 回传当前未处理的任务通知值 */
                *pulPreviousNotificationValue = pxTCB->ulNotifiedValue[ uxIndexToNotify ];
            }
            /* 获取该任务通知的状态 */
            ucOriginalNotifyState = pxTCB->ucNotifyState[ uxIndexToNotify ];
            /* 更新为pendig状态,表示该任务通知接收到通知了 */
            pxTCB->ucNotifyState[ uxIndexToNotify ] = taskNOTIFICATION_RECEIVED;
            /* 按照不同通知类型进行操作 */
            switch( eAction )
            {
                case eSetBits: /* 事件组。bit处理。与原通知值按位或。 */
                    pxTCB->ulNotifiedValue[ uxIndexToNotify ] |= ulValue;
                    break;

                case eIncrement: /* 信号量。释放信号量。原通知值+1 */
                    ( pxTCB->ulNotifiedValue[ uxIndexToNotify ] )++;
                    break;

                case eSetValueWithOverwrite: /* 邮箱。覆盖写入。 */
                    pxTCB->ulNotifiedValue[ uxIndexToNotify ] = ulValue;
                    break;

                case eSetValueWithoutOverwrite: /* 队列。没有通知时才写入。 */
                    if( ucOriginalNotifyState != taskNOTIFICATION_RECEIVED )
                    {
                        pxTCB->ulNotifiedValue[ uxIndexToNotify ] = ulValue;
                    }
                    else
                    {
                        /* 如果已有通知,则无法写入。 */
                        xReturn = pdFAIL;
                    }
                    break;

                case 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 )
                    {
                        /* 解锁任务后,更新下下次解锁延时链表的节拍值,否则可能会误导低功耗模式提前唤醒 */
                        prvResetNextTaskUnblockTime();
                    }
                #endif

                if( pxTCB->uxPriority > pxCurrentTCB->uxPriority )
                {
                    /* 解锁的任务优先级更高就需要触发任务切换。(退出临界后) */
                    taskYIELD_IF_USING_PREEMPTION();
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        taskEXIT_CRITICAL(); /* 退出临界 */

        return xReturn;
    }
#endif 

13.11 任务事件等待通知基函数

需要明白任务通知并不是独立的IPC通信组件,而是基于任务控制块的通信组件,所以只能运行该任务时调用等待发给该任务的通知,所以上下文只能是线程形,没有中断形。这样,实现的API只需要实现任务版即可。

接收任务通知的基函数是xTaskNotifyWaitIndexed():

  • uxIndexToWait:等待的通知索引。

  • ulBitsToClearOnEntry:没有接到通知时才生效的参数。标记接收通知前清空通知。

    • 通知值=通知值& ~(ulBitsToClearOnEntry)
  • ulBitsToClearOnExit:收到通知后需要清除的通知的值。

    • 通知值=通知值& ~(ulBitsToClearOnExit)
  • pulNotificationValue:回传退出清除通知前的通知的容器。

  • xTicksToWait:等待阻塞超时时间。

#if ( configUSE_TASK_NOTIFICATIONS == 1 )
    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();
        {
            /* 当前没有通知 */
            if( pxCurrentTCB->ucNotifyState[ uxIndexToWait ] != taskNOTIFICATION_RECEIVED )
            {
                /* 接收通知前需要清空通知对应的bit */
                pxCurrentTCB->ulNotifiedValue[ uxIndexToWait ] &= ~ulBitsToClearOnEntry;

                /* 标记下当前任务等待当前通知 */
                pxCurrentTCB->ucNotifyState[ uxIndexToWait ] = taskWAITING_NOTIFICATION;

                if( xTicksToWait > ( TickType_t ) 0 ) /* 需要阻塞 */
                {
                    /* 从就绪链表迁移到延时链表或者挂起链表。即是进入阻塞态 */
                    prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE );
                    traceTASK_NOTIFY_WAIT_BLOCK( uxIndexToWait );

                    /* 触发任务调度。(退出临界后才执行实际的调度) */
                    portYIELD_WITHIN_API();
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
            else /* 当前已经有通知了 */
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        taskEXIT_CRITICAL(); /* 退出临界 */
  
        /* 如果前面进入了阻塞,再跑回这里是唤醒后了 */

        taskENTER_CRITICAL(); /* 进入临界 */
        {
            traceTASK_NOTIFY_WAIT( uxIndexToWait );

            if( pulNotificationValue != NULL ) /* 需要回传 */
            {
                /* 回传退出前的通知 */
                *pulNotificationValue = pxCurrentTCB->ulNotifiedValue[ uxIndexToWait ];
            }

            if( pxCurrentTCB->ucNotifyState[ uxIndexToWait ] != taskNOTIFICATION_RECEIVED ) /* 没有通知 */
            {
                /* 没有通知,要么没有进入阻塞,要么进入阻塞后因非通知原因而解锁,如阻塞超时或被强制解锁。 */
                xReturn = pdFALSE; /* 返回接收失败 */
            }
            else /* 收到通知 */
            {
                /* 收到通知的可能:1. 接收前已经收到通知;2. 阻塞时收到通知。 */
                pxCurrentTCB->ulNotifiedValue[ uxIndexToWait ] &= ~ulBitsToClearOnExit;
                xReturn = pdTRUE; /* 返回接收成功 */
            }
            /* 更新通知状态为初始态 */
            pxCurrentTCB->ucNotifyState[ uxIndexToWait ] = taskNOT_WAITING_NOTIFICATION;
        }
        taskEXIT_CRITICAL(); /* 退出临界 */

        return xReturn;
    }
#endif /* configUSE_TASK_NOTIFICATIONS */

13.12 任务全功能通知函数

xTaskNotify()xTaskNotifyIndexed(),对比内部通用函数,只是没有回传功能。

#define xTaskNotify( xTaskToNotify, ulValue, eAction ) \
    xTaskGenericNotify( ( xTaskToNotify ), ( tskDEFAULT_INDEX_TO_NOTIFY ), ( ulValue ), ( eAction ), NULL )
#define xTaskNotifyIndexed( xTaskToNotify, uxIndexToNotify, ulValue, eAction ) \
    xTaskGenericNotify( ( xTaskToNotify ), ( uxIndexToNotify ), ( ulValue ), ( eAction ), NULL )

中断版:xTaskNotifyFromISR()xTaskNotifyIndexedFromISR(),对比内部通用函数中断版,只是没有回传功能。

#define xTaskNotifyFromISR( xTaskToNotify, ulValue, eAction, pxHigherPriorityTaskWoken ) \
    xTaskGenericNotifyFromISR( ( xTaskToNotify ), ( tskDEFAULT_INDEX_TO_NOTIFY ), ( ulValue ), ( eAction ), NULL, ( pxHigherPriorityTaskWoken ) )
#define xTaskNotifyIndexedFromISR( xTaskToNotify, uxIndexToNotify, ulValue, eAction, pxHigherPriorityTaskWoken ) \
    xTaskGenericNotifyFromISR( ( xTaskToNotify ), ( uxIndexToNotify ), ( ulValue ), ( eAction ), NULL, ( pxHigherPriorityTaskWoken ) )

13.13 任务通知全功能等待通知函数

xTaskNotifyWait()xTaskNotifyWaitIndexed()

#define xTaskNotifyWait( ulBitsToClearOnEntry, ulBitsToClearOnExit, pulNotificationValue, xTicksToWait ) \
    xTaskGenericNotifyWait( tskDEFAULT_INDEX_TO_NOTIFY, ( ulBitsToClearOnEntry ), ( ulBitsToClearOnExit ), ( pulNotificationValue ), ( xTicksToWait ) )
#define xTaskNotifyWaitIndexed( uxIndexToWaitOn, ulBitsToClearOnEntry, ulBitsToClearOnExit, pulNotificationValue, xTicksToWait ) \
    xTaskGenericNotifyWait( ( uxIndexToWaitOn ), ( ulBitsToClearOnEntry ), ( ulBitsToClearOnExit ), ( pulNotificationValue ), ( xTicksToWait ) )

13.14 任务通知之信号量

通过学习了上述两个内部通用任务通知API后,我们可以通过封装这两个API来实现对应IPC通信概念的专用API。

13.14.1 释放信号量

任务通知之释放信号量:xTaskNotifyGive()xTaskGenericNotify()

#define xTaskNotifyGive( xTaskToNotify ) \
    xTaskGenericNotify( ( xTaskToNotify ), ( tskDEFAULT_INDEX_TO_NOTIFY ), ( 0 ), eIncrement, NULL )
#define xTaskNotifyGiveIndexed( xTaskToNotify, uxIndexToNotify ) \
    xTaskGenericNotify( ( xTaskToNotify ), ( uxIndexToNotify ), ( 0 ), eIncrement, NULL )

中断版:(内部源码可以自己分析,参考上述通用API中的信号量逻辑,只是没有阻塞机制)

#define vTaskNotifyGiveFromISR( xTaskToNotify, pxHigherPriorityTaskWoken ) \
    vTaskGenericNotifyGiveFromISR( ( xTaskToNotify ), ( tskDEFAULT_INDEX_TO_NOTIFY ), ( pxHigherPriorityTaskWoken ) );
#define vTaskNotifyGiveIndexedFromISR( xTaskToNotify, uxIndexToNotify, pxHigherPriorityTaskWoken ) \
    vTaskGenericNotifyGiveFromISR( ( xTaskToNotify ), ( uxIndexToNotify ), ( pxHigherPriorityTaskWoken ) );

13.14.2 获取信号量

ulTaskNotifyTake()ulTaskNotifyTakeIndexed()

如果参数xClearCountOnExit设置为pdTRUE则用于任务通知的二值信号量。

#define ulTaskNotifyTake( xClearCountOnExit, xTicksToWait ) \
    ulTaskGenericNotifyTake( ( tskDEFAULT_INDEX_TO_NOTIFY ), ( xClearCountOnExit ), ( xTicksToWait ) )
#define ulTaskNotifyTakeIndexed( uxIndexToWaitOn, xClearCountOnExit, xTicksToWait ) \
    ulTaskGenericNotifyTake( ( uxIndexToWaitOn ), ( xClearCountOnExit ), ( xTicksToWait ) )

13.15 任务通知之消息邮箱

xTaskNotifyAndQuery()xTaskNotifyAndQueryIndexed()

#define xTaskNotifyAndQuery( xTaskToNotify, ulValue, eAction, pulPreviousNotifyValue ) \
    xTaskGenericNotify( ( xTaskToNotify ), ( tskDEFAULT_INDEX_TO_NOTIFY ), ( ulValue ), ( eAction ), ( pulPreviousNotifyValue ) )
#define xTaskNotifyAndQueryIndexed( xTaskToNotify, uxIndexToNotify, ulValue, eAction, pulPreviousNotifyValue ) \
    xTaskGenericNotify( ( xTaskToNotify ), ( uxIndexToNotify ), ( ulValue ), ( eAction ), ( pulPreviousNotifyValue ) )

xTaskNotifyAndQueryIndexed()执行与xTaskNotifyIndexed()相同的操作,另外它还在附加的pulPreviousNotifyValue参数中返回目标任务的之前的通知值(函数被调用时的通知值,而不是函数返回时的通知值)。(对应的xTaskNotifyAndQuery()xTaskNotify()差别也一样)。

中断版:

#define xTaskNotifyAndQueryIndexedFromISR( xTaskToNotify, uxIndexToNotify, ulValue, eAction, pulPreviousNotificationValue, pxHigherPriorityTaskWoken ) \
    xTaskGenericNotifyFromISR( ( xTaskToNotify ), ( uxIndexToNotify ), ( ulValue ), ( eAction ), ( pulPreviousNotificationValue ), ( pxHigherPriorityTaskWoken ) )
#define xTaskNotifyAndQueryFromISR( xTaskToNotify, ulValue, eAction, pulPreviousNotificationValue, pxHigherPriorityTaskWoken ) \
    xTaskGenericNotifyFromISR( ( xTaskToNotify ), ( tskDEFAULT_INDEX_TO_NOTIFY ), ( ulValue ), ( eAction ), ( pulPreviousNotificationValue ), ( pxHigherPriorityTaskWoken ) )

13.16 任务通知之事件组标志

并没有封装出专门的事件标志API,但是可以使用全能版的API实现。用户也可以自己封装。

13.17 清除任务通知状态

如果一个通知被发送到通知数组中的一个索引,那么该索引的通知被称为pending,直到任务读取它的通知值或通过调用xTaskNotifyStateClear()显式地清除通知状态为not pending

xTaskNotifyStateClear()xTaskNotifyStateClearIndexed()

#define xTaskNotifyStateClear( xTask ) \
    xTaskGenericNotifyStateClear( ( xTask ), ( tskDEFAULT_INDEX_TO_NOTIFY ) )
#define xTaskNotifyStateClearIndexed( xTask, uxIndexToClear ) \
    xTaskGenericNotifyStateClear( ( xTask ), ( uxIndexToClear ) )

内部实现:xTaskGenericNotifyStateClear()

#if ( configUSE_TASK_NOTIFICATIONS == 1 )
    BaseType_t xTaskGenericNotifyStateClear( TaskHandle_t xTask,
                                             UBaseType_t uxIndexToClear )
    {
        TCB_t * pxTCB;
        BaseType_t xReturn;

        configASSERT( uxIndexToClear < configTASK_NOTIFICATION_ARRAY_ENTRIES );

        /* 如果在这里传递null,那么当前任务的通知状态被清除 */
        pxTCB = prvGetTCBFromHandle( xTask );

        taskENTER_CRITICAL(); /* 进入临界 */
        {
            if( pxTCB->ucNotifyState[ uxIndexToClear ] == taskNOTIFICATION_RECEIVED )
            {
                pxTCB->ucNotifyState[ uxIndexToClear ] = taskNOT_WAITING_NOTIFICATION; /* 如果处于收到通知的状态,则可清除,回到没有等待通知的状态 */

                xReturn = pdPASS;
            }
            else
            {
                xReturn = pdFAIL;
            }
        }
        taskEXIT_CRITICAL(); /* 退出临界 */

        return xReturn;
    }
#endif

13.18 清除任务通知值

ulTaskNotifyValueClear()ulTaskNotifyValueClearIndexed():

清除任务通知值,返回的是清除前的任务通知值。

注意:是按bit清除。

#define ulTaskNotifyValueClear( xTask, ulBitsToClear ) \
    ulTaskGenericNotifyValueClear( ( xTask ), ( tskDEFAULT_INDEX_TO_NOTIFY ), ( ulBitsToClear ) )
#define ulTaskNotifyValueClearIndexed( xTask, uxIndexToClear, ulBitsToClear ) \
    ulTaskGenericNotifyValueClear( ( xTask ), ( uxIndexToClear ), ( ulBitsToClear ) )

内部通用函数:ulTaskGenericNotifyValueClear()

#if ( configUSE_TASK_NOTIFICATIONS == 1 )
    uint32_t ulTaskGenericNotifyValueClear( TaskHandle_t xTask,
                                            UBaseType_t uxIndexToClear,
                                            uint32_t ulBitsToClear )
    {
        TCB_t * pxTCB;
        uint32_t ulReturn;

        /* 如果在这里传递null,那么当前任务的通知值被清除 */
        pxTCB = prvGetTCBFromHandle( xTask );

        taskENTER_CRITICAL(); /* 进入临界 */
        {
            /* 返回清除位之前的通知,然后清除位掩码 */
            ulReturn = pxTCB->ulNotifiedValue[ uxIndexToClear ];
            pxTCB->ulNotifiedValue[ uxIndexToClear ] &= ~ulBitsToClear;
        }
        taskEXIT_CRITICAL(); /* 退出临界 */

        return ulReturn;
    }
#endif /* configUSE_TASK_NOTIFICATIONS */