預設以32bit事件型別和任務專用API講解。
事件獨立於訊息佇列、號誌和互斥量這些章節是因為內部實現機制不同。
參考:李柱明部落格:https://www.cnblogs.com/lizhuming/p/16353453.html
和守護行程一樣理解即可。
守護任務(Daemon)又稱為精靈任務,是執行在後臺的一種特殊任務,週期性地執行某種任務或等待處理某些事情的發生,主要表現為以下兩個特點:
比如freertos的軟體定時器服務任務。
先明白freertos不允許在中斷或臨界中操作不確定的業務。
而事件有個不確定的業務是因為事件的一對多特性,如當發生事件置位時,會遍歷阻塞在這個事件組的連結串列,而阻塞在這個連結串列任務是不確定的。
所以任務專用的API事件置位時,不是在臨界,而是在排程鎖內完成的。
而中斷專用的API事件置位,整個上下文都不符合要求,所以中斷專用的API的事件置位實現是通過給FreeRTOS的守護任務傳送一個訊息,讓置位事件組的操作在守護任務(軟體定時器服務任務)裡面完成,守護任務是基於排程鎖而非臨界段的機制來實現的。
事件組報文就一個系統位長的變數。
最高位bit表示該值表示事件組有效。這個bit影響到整個系統的事件阻塞、優先順序繼承等等任務事件節點相關的業務的實現。
最高位元組的[6:0]bit是該事件組的控制資訊。
剩餘bit表示各個事件。
事件是一種實現任務間通訊的機制,主要用於實現多工間的同步,但事件通訊只能是事件型別的通訊,無資料傳輸。
和號誌又是不同的,事件可以一對多,多對多同步。
事件(bit):
事件組:多個事件組合在一起,使用者可以選擇等待某個事件或者等待所有事件實現同步。
事件位用於指示事件是否發生。事件位通常被稱為事件標誌。例如,申請可以:
事件組是事件位的集合。事件組中的個別事件位由位號參照。展開上面提供的範例:
核心原理就是一個全域性變數+存取機制+阻塞機制。
這些元件都用控制塊資料結構管理起來。
封裝一些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。
在實現事件組時,RTOS必須克服的兩個主要挑戰是:應用程式競態混合執行不確定性。
如果出現以下情況,事件組實現將在應用程式中產生競爭條件:
這樣對全域性資源的這個事件組來說,應用層的呼叫很模糊,所以為了解決這些問題,避免應用程式的競態產生,實現事件機制時可以用一下方法解決:
FreeRTOS 事件組實現通過包含內建智慧來確保位的設定、測試和清除看起來是原子的,在處理了所有對該事件感興趣的任務後再統一對這個事件bit做更新,從而消除了競爭條件的可能性。
執行緒本地儲存(任務事件節點值儲存當前任務對該事件組的資訊)和 API 函數返回值的謹慎使用。
事件組概念意味著不確定性行為,因為它不知道在一個事件組上有多少任務被阻塞,因此當事件位被設定時,不知道有多少條件需要被測試或多少任務需要被解除阻塞。
FreeRTOS 質量標準不允許在中斷被禁用時或在中斷服務程式中執行不確定的動作。為了確保在設定事件位時不違反這些嚴格的質量標準:
排程鎖用於確保在 RTOS 任務設定事件位時中斷保持啟用狀態。
集中延遲中斷機制用於在嘗試從中斷服務程式設定事件位時,將設定位的動作推遲到任務。
從事件控制塊看就知道事件使用了非常少的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
:
事件控制資訊:
/* 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
:用於區分事件控制欄位和事件欄位。建立事件使用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 */
注意:事件置位是沒有阻塞這個說法的,事件發生了就置位即可。
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;
}
}
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 );
}
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;
}
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;
}
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();
}