【freertos】012-事件標誌概念和實現細節

李柱明發表於2022-06-08


前言

預設以32bit事件型別和任務專用API講解。

事件獨立於訊息佇列、訊號量和互斥量這些章節是因為內部實現機制不同。

參考:李柱明部落格:https://www.cnblogs.com/lizhuming/p/16353453.html

12.1 實現事件機制的預備知識

12.1.1 守護任務

和守護程式一樣理解即可。

守護任務(Daemon)又稱為精靈任務,是執行在後臺的一種特殊任務,週期性地執行某種任務或等待處理某些事情的發生,主要表現為以下兩個特點:

  • 長期執行。守護任務是一種生存期很長的任務,一般在系統啟動就開始執行,到系統退出或呼叫介面強制停止而結束。
  • 後臺執行。使用者一般不能直接接觸控制該任務。使用者的任務也不會影響守護任務的生存。

比如freertos的軟體定時器服務任務。

12.1.2 事件的不確定性

先明白freertos不允許在中斷或臨界中操作不確定的業務。

而事件有個不確定的業務是因為事件的一對多特性,如當發生事件置位時,會遍歷阻塞在這個事件組的連結串列,而阻塞在這個連結串列任務是不確定的。

所以任務專用的API事件置位時,不是在臨界,而是在排程鎖內完成的。

而中斷專用的API事件置位,整個上下文都不符合要求,所以中斷專用的API的事件置位實現是通過給FreeRTOS的守護任務傳送一個訊息,讓置位事件組的操作在守護任務(軟體定時器服務任務)裡面完成,守護任務是基於排程鎖而非臨界段的機制來實現的。

12.1.3 事件組的報文

事件組報文就一個系統位長的變數。

最高位bit表示該值表示事件組有效。這個bit影響到整個系統的事件阻塞、優先順序繼承等等任務事件節點相關的業務的實現。

最高位元組的[6:0]bit是該事件組的控制資訊。

剩餘bit表示各個事件。

12.2 事件概念

事件是一種實現任務間通訊的機制,主要用於實現多工間的同步,但事件通訊只能是事件型別的通訊,無資料傳輸。

和訊號量又是不同的,事件可以一對多,多對多同步。

事件(bit):

  • 0:事件沒有發生。需要該事件的任務阻塞或直接返回失敗。
  • 1:事件發生。需要該事件的任務解除阻塞返回成功或者直接返回成功。

事件組:多個事件組合在一起,使用者可以選擇等待某個事件或者等待所有事件實現同步。

12.3 事件用途參考

事件位用於指示事件是否發生。事件位通常被稱為事件標誌。例如,申請可以:

  • 定義一個位(或標誌),當它設定為1時,表示“訊息已接收並準備處理”,當它設定為0時,表示“沒有訊息等待處理”。
  • 定義一個位(或標誌),當它設定為1時,表示“應用程式已將準備傳送到網路的訊息排隊”,而當它設定為0時,表示“沒有準備傳送到網路的訊息排隊”。
  • 定義一個位(或標誌),當它設定為1時,表示“是時候向網路傳送一個心跳訊息了”,而當它設定為0時,表示“還沒有到傳送另一個心跳訊息的時候了”。

事件組是事件位的集合。事件組中的個別事件位由位號引用。展開上面提供的示例:

  • 表示“訊息已接收並準備處理”的事件位可能是事件組中的位0。
  • 在同一個事件組中,表示“應用程式已將準備傳送到網路的訊息排隊”的事件位可能是第1位。
  • 表示“是時候向網路傳送心跳訊息了”的事件位可能位於同一事件組中的第2位。

12.4 事件實現原理簡述

核心原理就是一個全域性變數+訪問機制+阻塞機制。

這些元件都用控制塊資料結構管理起來。

封裝一些API訪問即可。

事件組由EventGroupHandle_t型別的變數引用。

如果configUSE_16_BIT_TICKS設定為1,則事件組中儲存的位元數(或標誌數)為8,如果configUSE_16_BIT_TICKS設定為0,則為24。

configUSE_16_BIT_TICKS的依賴源於任務內部實現中用於執行緒本地儲存的資料型別。

事件組中的所有事件位都儲存在EventBits_t型別的單個無符號變數中。

事件位0儲存在位0中,事件位1儲存在位1中,依此類推。

如圖是一個24位事件組,它使用3位來儲存前面描述的3個示例事件。在影像中,只設定了事件位2。

12.5 事件實現需要克服的問題

在實現事件組時,RTOS必須克服的兩個主要挑戰是:應用程式競態混合執行不確定性。

12.5.1 避免在使用者的應用程式中建立競爭條件

如果出現以下情況,事件組實現將在應用程式中產生競爭條件:

  • 不清楚誰負責清除單個事件。
  • 不清楚何時要清除位。
  • 不清楚在任務退出測試位值的 API 函式時是否設定或清除了事件(可能是另一個任務或中斷已更改該位的狀態)。

這樣對全域性資源的這個事件組來說,應用層的呼叫很模糊,所以為了解決這些問題,避免應用程式的競態產生,實現事件機制時可以用一下方法解決:

  • FreeRTOS 事件組實現通過包含內建智慧來確保位的設定、測試和清除看起來是原子的,在處理了所有對該事件感興趣的任務後再統一對這個事件bit做更新,從而消除了競爭條件的可能性。

    • 但是這樣就會出現不確定性。第二個問題就是解決不確定性。
  • 執行緒本地儲存(任務事件節點值儲存當前任務對該事件組的資訊)和 API 函式返回值的謹慎使用。

12.5.2 避免不確定性

事件組概念意味著不確定性行為,因為它不知道在一個事件組上有多少任務被阻塞,因此當事件位被設定時,不知道有多少條件需要被測試或多少任務需要被解除阻塞。

FreeRTOS 質量標準不允許在中斷被禁用時或在中斷服務程式中執行不確定的動作。為了確保在設定事件位時不違反這些嚴格的質量標準:

  • 排程鎖用於確保在 RTOS 任務設定事件位時中斷保持啟用狀態。

    • 即是原子性不使用臨界,而是排程鎖級別。
  • 集中延遲中斷機制用於在嘗試從中斷服務程式設定事件位時,將設定位的動作推遲到任務。

    • 即是把中斷上下文對事件的操作轉包給守護任務實現,這樣就維護了排程鎖級別的原子性。

12.6 事件控制塊

從事件控制塊看就知道事件使用了非常少的RAM實現。

typedef struct EventGroupDef_t
{
    EventBits_t uxEventBits; /* 事件組 */
    List_t xTasksWaitingForBits; /* 等待事件阻塞任務連結串列 */

    #if ( configUSE_TRACE_FACILITY == 1 )
        UBaseType_t uxEventGroupNumber; /* 事件number */
    #endif

    #if ( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
        uint8_t ucStaticallyAllocated; /* 標記是否為靜態分配 */
    #endif
} EventGroup_t;

EventBits_t uxEventBits

  • 最高bit:表示當前值為事件元件使用。主要用於在任務事件節點值中區分任務優先順序。
  • 最高位元組的[6:0]bit:表示事件組控制資訊。
  • 剩下的bit:表示各個事件。

事件控制資訊:

/* The following bit fields convey control information in a task's event list item value.  
    It is important they don't clash with the taskEVENT_LIST_ITEM_VALUE_IN_USE definition. */
#if configUSE_16_BIT_TICKS == 1
    #define eventCLEAR_EVENTS_ON_EXIT_BIT    0x0100U /* 事後清空事件 */
    #define eventUNBLOCKED_DUE_TO_BIT_SET    0x0200U /* 事後喚醒 */
    #define eventWAIT_FOR_ALL_BITS           0x0400U /* 等待所有事件 */
    #define eventEVENT_BITS_CONTROL_BYTES    0xff00U /* 事件控制欄位所有bit */
#else
    #define eventCLEAR_EVENTS_ON_EXIT_BIT    0x01000000UL /* 事後清空事件 */
    #define eventUNBLOCKED_DUE_TO_BIT_SET    0x02000000UL /* 發生事件而解除阻塞的標記 */
    #define eventWAIT_FOR_ALL_BITS           0x04000000UL /* 等待所有事件 */
    #define eventEVENT_BITS_CONTROL_BYTES    0xff000000UL /* 事件控制欄位所有bit */
#endif
  • eventCLEAR_EVENTS_ON_EXIT_BIT:該標記用於等待事件時配置,表示獲得事件觸發任務解鎖後,要清除這些事件。
  • eventUNBLOCKED_DUE_TO_BIT_SET:用於區分等待事件而阻塞的任務被喚醒的原因(事件或超時),該標記表示因為事件而觸發喚醒。
  • eventWAIT_FOR_ALL_BITS:用於等待事件時配置,表示該任務等待標記的所有事件都發生時才有效。否則就是任意事件有效。
  • eventEVENT_BITS_CONTROL_BYTES:用於區分事件控制欄位和事件欄位。

12.7 建立事件

建立事件使用API xEventGroupCreate()

#if ( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
    EventGroupHandle_t xEventGroupCreate( void )
    {
        EventGroup_t * pxEventBits;
        /* 申請事件組資源 */
        pxEventBits = ( EventGroup_t * ) pvPortMalloc( sizeof( EventGroup_t ) );

        if( pxEventBits != NULL ) /* 資源申請成功 */
        {
            pxEventBits->uxEventBits = 0; /* 初始化事件組 */
            vListInitialise( &( pxEventBits->xTasksWaitingForBits ) ); /* 初始化連結串列 */

            #if ( configSUPPORT_STATIC_ALLOCATION == 1 )
                {
                    /* 如果開啟了靜態記憶體功能,需要標記當前事件組資源是核心提供的,以免在回收資源時誤判導致記憶體洩漏 */
                    pxEventBits->ucStaticallyAllocated = pdFALSE;
                }
            #endif /* configSUPPORT_STATIC_ALLOCATION */

            traceEVENT_GROUP_CREATE( pxEventBits );
        }
        else /* 資源申請失敗 */
        {
            traceEVENT_GROUP_CREATE_FAILED();
        }
        return pxEventBits;
    }
#endif /* configSUPPORT_DYNAMIC_ALLOCATION */

12.8 事件置位

注意:事件置位是沒有阻塞這個說法的,事件發生了就置位即可。

xEventGroupSetBits()用於置位事件組中指定的位,當位被置位之後,阻塞在該位上的任務將會被解鎖。

事件置位,只需要設定事件欄位即可,事件標記欄位和事件控制欄位只是在等待事件時使用的。

EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup,
                                const EventBits_t uxBitsToSet )
{
    ListItem_t * pxListItem, * pxNext;
    ListItem_t const * pxListEnd;
    List_t const * pxList;
    EventBits_t uxBitsToClear = 0, uxBitsWaitedFor, uxControlBits;
    EventGroup_t * pxEventBits = xEventGroup;
    BaseType_t xMatchFound = pdFALSE;

    /* 引數校驗 */
    configASSERT( xEventGroup );
    /* 新置的事件標誌位是否有效 */
    configASSERT( ( uxBitsToSet & eventEVENT_BITS_CONTROL_BYTES ) == 0 );

    /* 獲取連結串列 */
    pxList = &( pxEventBits->xTasksWaitingForBits );
    /* 獲取連結串列尾,待會用於結束遍歷連結串列使用 */
    pxListEnd = listGET_END_MARKER( pxList );

    vTaskSuspendAll(); /* 掛起排程器 */
    {
        traceEVENT_GROUP_SET_BITS( xEventGroup, uxBitsToSet );
        /* 獲取首個節點,即是阻塞在當前事件中的最早那個任務的事件節點 */
        pxListItem = listGET_HEAD_ENTRY( pxList );

        /* 置位事件 */
        pxEventBits->uxEventBits |= uxBitsToSet;

        /* 遍歷阻塞在等待事件連結串列中的所有任務 */
        while( pxListItem != pxListEnd )
        {
            pxNext = listGET_NEXT( pxListItem ); /* 獲取下一個節點 */
            uxBitsWaitedFor = listGET_LIST_ITEM_VALUE( pxListItem ); /* 獲取阻塞任務的事件包 */
            xMatchFound = pdFALSE;

            /* 欄位分離 */
            /* 獲取事件控制資訊 */
            uxControlBits = uxBitsWaitedFor & eventEVENT_BITS_CONTROL_BYTES;
            /* 獲取事件資訊 */
            uxBitsWaitedFor &= ~eventEVENT_BITS_CONTROL_BYTES;

            if( ( uxControlBits & eventWAIT_FOR_ALL_BITS ) == ( EventBits_t ) 0 ) /* 該任務對標記的任意事件感興趣 */
            {
                /* 標記的任意事件發生即可解鎖 */
                if( ( uxBitsWaitedFor & pxEventBits->uxEventBits ) != ( EventBits_t ) 0 )
                {
                    xMatchFound = pdTRUE; /* 事件組中符合該任務等待的事件,允許解鎖 */
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
            else if( ( uxBitsWaitedFor & pxEventBits->uxEventBits ) == uxBitsWaitedFor ) /* 該任務需要標記的所有事件都滿足才能解鎖 */
            {
                /* 允許解鎖 */
                xMatchFound = pdTRUE;
            }
            else
            {
                /* 條件不滿足解鎖當前任務。事件組狀態沒有符合當前任務的要求 */
            }

            if( xMatchFound != pdFALSE ) /* 當前事件組狀態滿足該任務的解鎖條件 */
            {
                /* 事後處理 */
                if( ( uxControlBits & eventCLEAR_EVENTS_ON_EXIT_BIT ) != ( EventBits_t ) 0 )
                {
                    /* 解鎖後需要清除該事件 */
                    uxBitsToClear |= uxBitsWaitedFor;
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }

                /* 解除任務阻塞,配置該任務的事件值為當前事件組狀態 */
                vTaskRemoveFromUnorderedEventList( pxListItem, pxEventBits->uxEventBits | eventUNBLOCKED_DUE_TO_BIT_SET );
            }

            /* 遍歷下一個任務 */
            pxListItem = pxNext;
        }

        /* 所有阻塞在等待該事件組的任務都處理完畢後按結果清除事件 */
        pxEventBits->uxEventBits &= ~uxBitsToClear;
    }
    ( void ) xTaskResumeAll(); /* 恢復排程器 */

    return pxEventBits->uxEventBits; /* 返回該事件組的當前狀態 */
}

事件組的解除任務阻塞處理:vTaskRemoveFromUnorderedEventList()

void vTaskRemoveFromUnorderedEventList( ListItem_t * pxEventListItem,
                                        const TickType_t xItemValue )
{
    TCB_t * pxUnblockedTCB;

    /* 該函式在掛起排程器內呼叫 */
    configASSERT( uxSchedulerSuspended != pdFALSE );

    /* 把新的任務事件節點值寫入該任務 */
    listSET_LIST_ITEM_VALUE( pxEventListItem, xItemValue | taskEVENT_LIST_ITEM_VALUE_IN_USE );

    /* 找到節點持有者,即是對應的任務 */
    pxUnblockedTCB = listGET_LIST_ITEM_OWNER( pxEventListItem );
    /* 引數校驗 */
    configASSERT( pxUnblockedTCB );
    /* 把該節點從事件等待阻塞連結串列中移除 */
    listREMOVE_ITEM( pxEventListItem );

    #if ( configUSE_TICKLESS_IDLE != 0 )
        {
            /* 更新一下下次檢索延時連結串列的系統節拍值。
                在沒有開啟低功耗模式下,這裡喚醒一個任務後不更新也無妨,最多也就係統心跳服務中多檢索一次延時連結串列,
                但是如果開啟了低功耗模式後,喚醒一個任務後需要重新整理一下,否則提前喚醒退出低功耗模式的多餘操作不值得。 */
            prvResetNextTaskUnblockTime();
        }
    #endif

    /* 解除需要解鎖的任務的任務狀態 */
    listREMOVE_ITEM( &( pxUnblockedTCB->xStateListItem ) );
    /* 把解鎖的任務重新插入到對應的就緒連結串列中 */
    prvAddTaskToReadyList( pxUnblockedTCB );

    if( pxUnblockedTCB->uxPriority > pxCurrentTCB->uxPriority )
    {
        /* 如果新解鎖的任務優先順序比當前任務優先順序要高,在下次檢索切換任務時,需要繼續任務切換 */
        xYieldPending = pdTRUE;
    }
}

12.9 事件置位中斷版

xEventGroupSetBitsFromISR()

#if ( ( configUSE_TRACE_FACILITY == 1 ) && ( INCLUDE_xTimerPendFunctionCall == 1 ) && ( configUSE_TIMERS == 1 ) )

    BaseType_t xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup,
                                          const EventBits_t uxBitsToSet,
                                          BaseType_t * pxHigherPriorityTaskWoken )
    {
        BaseType_t xReturn;

        traceEVENT_GROUP_SET_BITS_FROM_ISR( xEventGroup, uxBitsToSet );
        xReturn = xTimerPendFunctionCallFromISR( vEventGroupSetBitsCallback, ( void * ) xEventGroup, ( uint32_t ) uxBitsToSet, pxHigherPriorityTaskWoken ); /*lint !e9087 Can't avoid cast to void* as a generic callback function not specific to this use case. Callback casts back to original type so safe. */

        return xReturn;
    }

#endif

事件置位業務傳送到守護任務中執行,傳送的API實現:

#if ( INCLUDE_xTimerPendFunctionCall == 1 )
        BaseType_t xTimerPendFunctionCallFromISR( PendedFunction_t xFunctionToPend,
                                                  void * pvParameter1,
                                                  uint32_t ulParameter2,
                                                  BaseType_t * pxHigherPriorityTaskWoken )
        {
            DaemonTaskMessage_t xMessage;
            BaseType_t xReturn;
            /* 把相關資料傳送給守護任務執行 */
            xMessage.xMessageID = tmrCOMMAND_EXECUTE_CALLBACK_FROM_ISR;
            xMessage.u.xCallbackParameters.pxCallbackFunction = xFunctionToPend;
            xMessage.u.xCallbackParameters.pvParameter1 = pvParameter1;
            xMessage.u.xCallbackParameters.ulParameter2 = ulParameter2;
            xReturn = xQueueSendFromISR( xTimerQueue, &xMessage, pxHigherPriorityTaskWoken );
            tracePEND_FUNC_CALL_FROM_ISR( xFunctionToPend, pvParameter1, ulParameter2, xReturn );
            return xReturn;
        }
    #endif

事件置位函式在守護任務的回撥:

void vEventGroupSetBitsCallback( void * pvEventGroup,
                                 const uint32_t ulBitsToSet )
{
    ( void ) xEventGroupSetBits( pvEventGroup, ( EventBits_t ) ulBitsToSet );
}

12.10 等待事件

xEventGroupWaitBits()

  • 因為有阻塞機制,所以實現的框架和訊息佇列接收訊息的函式類似,上段檢查、獲取資料,下段處理阻塞。
EventBits_t xEventGroupWaitBits( EventGroupHandle_t xEventGroup,
                                 const EventBits_t uxBitsToWaitFor,
                                 const BaseType_t xClearOnExit,
                                 const BaseType_t xWaitForAllBits,
                                 TickType_t xTicksToWait )
{
    EventGroup_t * pxEventBits = xEventGroup;
    EventBits_t uxReturn, uxControlBits = 0;
    BaseType_t xWaitConditionMet, xAlreadyYielded;
    BaseType_t xTimeoutOccurred = pdFALSE;

    /* 引數校驗 */
    configASSERT( xEventGroup );
    /* 事件組有效性校驗 */
    configASSERT( ( uxBitsToWaitFor & eventEVENT_BITS_CONTROL_BYTES ) == 0 );
    /* 必須得有事件 */
    configASSERT( uxBitsToWaitFor != 0 );
    #if ( ( INCLUDE_xTaskGetSchedulerState == 1 ) || ( configUSE_TIMERS == 1 ) )
        {
            /* 掛起排程器後,就不能以阻塞式進入 */
            configASSERT( !( ( xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED ) && ( xTicksToWait != 0 ) ) );
        }
    #endif

    vTaskSuspendAll(); /* 掛起排程器。使用排程所的方式處理事件業務 */
    {
        /* 獲取當前事件組報文 */
        const EventBits_t uxCurrentEventBits = pxEventBits->uxEventBits;

        /* 檢查當前事件組狀態是否已經符合當前任務的要求 */
        xWaitConditionMet = prvTestWaitCondition( uxCurrentEventBits, uxBitsToWaitFor, xWaitForAllBits );

        if( xWaitConditionMet != pdFALSE ) /* 當前事件組狀態就已經滿足的 */
        {
            /* 返回當前事件組的狀態 */
            uxReturn = uxCurrentEventBits;
            xTicksToWait = ( TickType_t ) 0;

            if( xClearOnExit != pdFALSE ) /* 需要清除事件組中當前任務已標記事件 */
            {
                pxEventBits->uxEventBits &= ~uxBitsToWaitFor;
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        else if( xTicksToWait == ( TickType_t ) 0 )
        {
            /* 當前事件組狀態還沒滿足當前任務要求,也不阻塞,所以直接返回 */
            uxReturn = uxCurrentEventBits;
            xTimeoutOccurred = pdTRUE;
        }
        else /* 當前條件還不滿足,且需要阻塞處理 */
        {
            /* 組建事件組報文控制欄位 */
            if( xClearOnExit != pdFALSE )
            {
                /* 標記事後刪除 */
                uxControlBits |= eventCLEAR_EVENTS_ON_EXIT_BIT;
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }

            if( xWaitForAllBits != pdFALSE )
            {
                /* 標記等待所有標記的事件才生效 */
                uxControlBits |= eventWAIT_FOR_ALL_BITS;
            }
            else /* 否則就是等待任意事件 */
            {
                mtCOVERAGE_TEST_MARKER();
            }

            /* 重置當前任務事件節點值為當前任務事件組報文;
                把當前任務從就緒連結串列遷移到延時連結串列;(xTicksToWait大於0的情況下)
                把當前任務插入到事件組阻塞等待事件連結串列中。 */
            vTaskPlaceOnUnorderedEventList( &( pxEventBits->xTasksWaitingForBits ), ( uxBitsToWaitFor | uxControlBits ), xTicksToWait );

            uxReturn = 0;
            traceEVENT_GROUP_WAIT_BITS_BLOCK( xEventGroup, uxBitsToWaitFor );
        }
    }
    xAlreadyYielded = xTaskResumeAll(); /* 恢復排程器 */

    if( xTicksToWait != ( TickType_t ) 0 ) /* 需要阻塞(但是已經阻塞過了的) */
    {
        if( xAlreadyYielded == pdFALSE )
        {
            /* 如果當前任務解除了執行態和就緒態後沒有排程過,就需要手動觸發排程 */
            portYIELD_WITHIN_API();
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }

        /* 已經排程過了,又回到當前任務 */

        /* 獲取當前任務收到的事件組報文,用於判斷當前喚醒的原因。
            並重置任務事件節點值為當前任務優先順序。 */
        uxReturn = uxTaskResetEventItemValue();

        if( ( uxReturn & eventUNBLOCKED_DUE_TO_BIT_SET ) == ( EventBits_t ) 0 ) /* 阻塞超時而被喚醒的 */
        {
            taskENTER_CRITICAL(); /* 進入臨界 */
            {
                /* 獲取下當前事件組的報文 */
                uxReturn = pxEventBits->uxEventBits;

                /* 退出前檢查下是否滿足了 */
                if( prvTestWaitCondition( uxReturn, uxBitsToWaitFor, xWaitForAllBits ) != pdFALSE )
                {
                    /* 事件組條件滿足 */
                    if( xClearOnExit != pdFALSE )
                    {
                        /* 條件滿足,且需要清空對應事件,便清空 */
                        pxEventBits->uxEventBits &= ~uxBitsToWaitFor;
                    }
                    else
                    {
                        mtCOVERAGE_TEST_MARKER();
                    }
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
                /* 在未使用跟蹤巨集時防止編譯器警告 */
                xTimeoutOccurred = pdTRUE;
            }
            taskEXIT_CRITICAL(); /* 退出臨界 */
        }
        else /* 因為事件組狀態滿足而解除阻塞的 */
        {;}

        /* 獲取事件欄位 */
        uxReturn &= ~eventEVENT_BITS_CONTROL_BYTES;
    }

    traceEVENT_GROUP_WAIT_BITS_END( xEventGroup, uxBitsToWaitFor, xTimeoutOccurred );
    /* 防止編譯警告 */
    ( void ) xTimeoutOccurred;
    /* 返回事件欄位 */
    return uxReturn;
}

事件匹配prvTestWaitCondition()

static BaseType_t prvTestWaitCondition( const EventBits_t uxCurrentEventBits,
                                        const EventBits_t uxBitsToWaitFor,
                                        const BaseType_t xWaitForAllBits )
{
    BaseType_t xWaitConditionMet = pdFALSE;

    if( xWaitForAllBits == pdFALSE ) /* 匹配任意事件 */
    {
        if( ( uxCurrentEventBits & uxBitsToWaitFor ) != ( EventBits_t ) 0 )
        {
            /* 某個事件滿足即可 */
            xWaitConditionMet = pdTRUE;
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }
    else /* 標記事件全匹配 */
    {
        if( ( uxCurrentEventBits & uxBitsToWaitFor ) == uxBitsToWaitFor )
        {
            /* 全匹配才生效 */
            xWaitConditionMet = pdTRUE;
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }
    /* 返回匹配結果 */
    return xWaitConditionMet;
}

12.11 清除事件

xEventGroupClearBits()xEventGroupClearBitsFromISR()都是用於清除事件組指定的位,如果在獲取事件的時候沒有將對應的標誌位清除,那麼就需要用這個函式來進行顯式清除。

xEventGroupClearBitsFromISR()這個中斷專用API也是和事件置位一樣,都是發通知給守護任務執行置位業務。

這裡主要分析xEventGroupClearBits()

EventBits_t xEventGroupClearBits( EventGroupHandle_t xEventGroup,
                                  const EventBits_t uxBitsToClear )
{
    EventGroup_t * pxEventBits = xEventGroup;
    EventBits_t uxReturn;

    /* 引數校驗 */
    configASSERT( xEventGroup );
    /* 事件欄位檢查 */
    configASSERT( ( uxBitsToClear & eventEVENT_BITS_CONTROL_BYTES ) == 0 );

    taskENTER_CRITICAL(); /* 進入臨界 */
    {
        traceEVENT_GROUP_CLEAR_BITS( xEventGroup, uxBitsToClear );

        /* 返回的是清除事件前的事件組狀態 */
        uxReturn = pxEventBits->uxEventBits;

        /* 清除目標事件 */
        pxEventBits->uxEventBits &= ~uxBitsToClear;
    }
    taskEXIT_CRITICAL(); /* 退出臨界 */

    return uxReturn;
}

12.12 刪除事件

vEventGroupDelete()

void vEventGroupDelete( EventGroupHandle_t xEventGroup )
{
    EventGroup_t * pxEventBits = xEventGroup;
    const List_t * pxTasksWaitingForBits;
    /* 引數校驗 */
    configASSERT( pxEventBits );
    /* 獲取等待事件阻塞連結串列 */
    pxTasksWaitingForBits = &( pxEventBits->xTasksWaitingForBits );

    vTaskSuspendAll(); /* 掛起排程器 */
    {
        traceEVENT_GROUP_DELETE( xEventGroup );

        while( listCURRENT_LIST_LENGTH( pxTasksWaitingForBits ) > ( UBaseType_t ) 0 ) /* 遍歷等待事件阻塞連結串列 */
        {
            /* 相當於引數校驗 */
            configASSERT( pxTasksWaitingForBits->xListEnd.pxNext != ( const ListItem_t * ) &( pxTasksWaitingForBits->xListEnd ) );
            /* 解除這些阻塞的任務,解鎖理由是事件原因,並把事件無效(事件bit全0)返回給任務 */
            vTaskRemoveFromUnorderedEventList( pxTasksWaitingForBits->xListEnd.pxNext, eventUNBLOCKED_DUE_TO_BIT_SET );
        }

        #if ( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && ( configSUPPORT_STATIC_ALLOCATION == 0 ) )
            {
                /* 動態申請就動態釋放資源 */
                vPortFree( pxEventBits );
            }
        #elif ( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && ( configSUPPORT_STATIC_ALLOCATION == 1 ) )
            {
                if( pxEventBits->ucStaticallyAllocated == ( uint8_t ) pdFALSE ) /* 動態分配,動態釋放 */
                {
                    vPortFree( pxEventBits );
                }
                else /* 靜態分配,使用者釋放 */
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
        #endif /* configSUPPORT_DYNAMIC_ALLOCATION */
    }
    ( void ) xTaskResumeAll();
}

相關文章