FreeRTOS 任務與調度器(2)


 在上一篇我們介紹了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 = 0,    
    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 */
View Code

 

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 */
View Code

 

 接下來我們回到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 ) 0 )                                  
                {
                    /* The task is going to block.  First it must be removed
                    from the ready list. */
                    /* 任務將會阻塞。 但首先必須從准備列表移除 */
                    if( uxListRemove( &( pxCurrentTCB->xGenericListItem ) ) == ( UBaseType_t ) 0 )
                    {
                        /* 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 */
View Code

 

  *在我們查看源碼的時候,我們可以留意到,當我們設置輸入參數為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 );  /*把計算好的時間添加到延時列表。交給調度器處理*/
                        }
                    }
View Code

 

官方例子:

 

 

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 ) 0 )
                {
                    /* The task is going to block.  First it must be removed
                    from the    ready list. */
                    /* 任務馬上就要阻塞了,首先要把它移出准備列表 */
                    if( uxListRemove( &( pxCurrentTCB->xGenericListItem ) ) == ( UBaseType_t ) 0 )
                    {
                        /* 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 */
View Code

 

官方使用例子:

  這個例子是使用通知代替事件組,也就是置位通知

  1.   等待通知信號,任務會進入阻塞/掛起狀態。
  2.   判斷通知信號的第幾位被置位了

 

3、最后筆者寫了四個例子,提供大家參考

使用不帶數據的二值任務通知(可以代替二進制信號量)

//Notify is used for binary semaphore
void vDemoTaskA(void *Parammenters)
{
    for(;;)
    {
        //xTaskNotifyGive( HandleOfTaskB );
        xTaskNotify( HandleOfTaskB, 0, eNoAction );
        
        vTaskDelay( pdMS_TO_TICKS(5) );
    }
}

void vDemoTaskB(void *Parammenters)
{
    for(;;)
    {
        ulTaskNotifyTake( pdTRUE , portMAX_DELAY );
        //add your codes here        
    }   
}


void vTaskCreate(void )
{    
    xTaskCreate( vDemoTaskA, "vDemoTaskA", 100, NULL, 1, NULL );                                            
    xTaskCreate( vDemoTaskB, "vDemoTaskB", 100, NULL, 0, &HandleOfTaskB ); 
}

/*******************************************************************************
* Function Name  : main
* Description    : 
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
int main( void )
{

    vTaskCreate();
    
    vTaskStartScheduler( ); 
         
    while(1)
    {
        //should not be here
    }    
    
}
View Code

 

使用自增的任務通知(可以代替計數信號量)

//Notify is used for counting semaphore
void vDemoTaskA(void *Parammenters)
{
    for(;;)
    {
        xTaskNotifyGive( HandleOfTaskB );
        xTaskNotifyGive( HandleOfTaskB );
        xTaskNotifyGive( HandleOfTaskB );
        
        vTaskDelay( pdMS_TO_TICKS(5) );
    }
}
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", 100, NULL, 1, NULL );                                            
    xTaskCreate( vDemoTaskB, "vDemoTaskB", 100, NULL, 0, &HandleOfTaskB ); 
}

/*******************************************************************************
* Function Name  : main
* Description    : 
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
int main( void )
{

    vTaskCreate();
    
    vTaskStartScheduler( ); 
         
    while(1)
    {
        //should not be here
    }    
    
}
View Code

 

使用置位任務通知(可以代替事件標志組)

//Notify is used for event group
void vDemoTaskA(void *Parammenters)
{
    for(;;)
    {
        xTaskNotify( HandleOfTaskB, (uint32_t)1 , eSetBits );        
        xTaskNotify( HandleOfTaskB, (uint32_t)1<<1 , eSetBits );
        xTaskNotify( HandleOfTaskB, (uint32_t)1<<3 , eSetBits );
        
        vTaskDelay( pdMS_TO_TICKS(5) );
    }
}
void vDemoTaskB(void *Parammenters)
{
    #define ULONG_MAX  0xffffffff 
    uint32_t ulNotifiedValue=0;
    for(;;)
    {
        xTaskNotifyWait( 0x00 , ULONG_MAX , &ulNotifiedValue ,portMAX_DELAY );
        
        if( ulNotifiedValue & (1) )
        {
            //add your codes here            
        }
        if( ulNotifiedValue & (1<<1) )
        {
            //add your codes here            
        }
        if( ulNotifiedValue & (1<<2) )
        {
            //add your codes here            
        }
        if( ulNotifiedValue & (1<<3) )
        {
            //add your codes here            
        }
        //you can add more events below if you need them 
    }   
}



void vTaskCreate(void )
{    
    xTaskCreate( vDemoTaskA, "vDemoTaskA", 100, NULL, 1, NULL );                                            
    xTaskCreate( vDemoTaskB, "vDemoTaskB", 100, NULL, 0, &HandleOfTaskB ); 
}

/*******************************************************************************
* Function Name  : main
* Description    : 
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
int main( void )
{

    vTaskCreate();
    
    vTaskStartScheduler( ); 
         
    while(1)
    {
        //should not be here
    }    
    
}
View Code

 

使用傳遞消息任務通知(可以代替深度為一的消息隊列)

//Notify is used for sending message
void vDemoTaskA(void *Parammenters)
{
    uint32_t pTestBuff[5] = { 0,1,2,3,4 };
    int i;
    
    for(;;)
    {    
        for(i=0;i<5;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(5) );
        }
    }
}
void vDemoTaskB(void *Parammenters)
{
    #define ULONG_MAX  0xffffffff 
    uint32_t ulNotifiedValue=0;
    uint32_t pRecBuff[5]={0};
    int i;
    
    for(;;)
    {
        for(i=0;i<5;i++)
        {
            xTaskNotifyWait( 0x00 , ULONG_MAX , &ulNotifiedValue ,portMAX_DELAY );
            pRecBuff[i] = ulNotifiedValue;
            //add your codes here            
        }
    }   
}

void vTaskCreate(void )
{    
    xTaskCreate( vDemoTaskA, "vDemoTaskA", 100, NULL, 1, NULL );                                            
    xTaskCreate( vDemoTaskB, "vDemoTaskB", 100, NULL, 0, &HandleOfTaskB ); 
}

/*******************************************************************************
* Function Name  : main
* Description    : 
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
int main( void )
{

    vTaskCreate();
    
    vTaskStartScheduler( ); 
         
    while(1)
    {
        //should not be here
    }    
    
}
View Code

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM