文章目錄
- 補充
- 任務通知
- 發送
- 處理
- ulTaskGenericNotifyTake
- xTaskGenericNotifyWait
- 清除
- xTaskGenericNotifyStateClear
- ulTaskGenericNotifyValueClear
- 結構體
- StreamBufferHandle_t
- StreamBufferCallbackFunction_t
- 創建
- xStreamBufferGenericCreate
- stream buffer的類型
- 刪除
- vStreamBufferDelete
- 發送
- prvWriteBytesToBuffer
- prvWriteMessageToBuffer
- prvBytesInBuffer
- 接收
- xStreamBufferReceive
補充
任務通知
任務通知(Task Notifications)是一種輕量級的、快速的機制,用于在任務之間或中斷與任務之間傳遞簡單的信號或少量數據。相比傳統的隊列、信號量等同步機制,任務通知具有更低的資源消耗和更高的效率。
還記得嗎,每個任務控制塊都有這么兩個字段
//狀態
#define taskNOT_WAITING_NOTIFICATION ( ( uint8_t ) 0 ) /* Must be zero as it is the initialised value. */
#define taskWAITING_NOTIFICATION ( ( uint8_t ) 1 )
#define taskNOTIFICATION_RECEIVED ( ( uint8_t ) 2 )//字段
#if ( configUSE_TASK_NOTIFICATIONS == 1 )volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ]; //通知值volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ]; //通知狀態
#endif
通過這些,可以實現多種同步模式:
- 簡單通知:僅設置通知狀態。
- 帶值的通知:除了設置狀態外,還可以傳遞一個32位的數值。
- 帶掩碼的通知:使用特定的位掩碼來更新通知值。
大體流程如下
發送
xTaskGenericNotify
是所有任務通知 API(如 xTaskNotify
, xTaskNotifyIndexed
, xTaskNotifyAndQuery
, xTaskNotifyAndQueryIndexed
等)的底層通用函數,用于向一個任務發送通知,并根據不同的操作類型更新該任務的通知值。
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 ];//記錄原始通知狀態并標記為“已收到”pxTCB->ucNotifyState[ uxIndexToNotify ] = taskNOTIFICATION_RECEIVED;switch( eAction )//根據通知操作類型處理通知值{case eSetBits://將通知值與 ulValue 做按位或操作(常用于標志位)pxTCB->ulNotifiedValue[ uxIndexToNotify ] |= ulValue;break;case eIncrement://通知值自增 1( pxTCB->ulNotifiedValue[ uxIndexToNotify ] )++;break;case eSetValueWithOverwrite://直接設置通知值,不管之前有沒有被讀取過pxTCB->ulNotifiedValue[ uxIndexToNotify ] = ulValue;break;case eSetValueWithoutOverwrite://如果上次通知還沒被讀取,則不更新值,返回 pdFAILif( ucOriginalNotifyState != taskNOTIFICATION_RECEIVED ){ //目標任務在此次通知發送之前 沒有處于“已收到通知”狀態(即上次的通知已經被讀取或清除)pxTCB->ulNotifiedValue[ uxIndexToNotify ] = ulValue;}else{xReturn = pdFAIL;}break;case eNoAction://不改變通知值break;default:configASSERT( xTickCount == ( TickType_t ) 0 );break;}if( ucOriginalNotifyState == taskWAITING_NOTIFICATION )//如果目標任務正在等待通知,將其喚醒{listREMOVE_ITEM( &( pxTCB->xStateListItem ) );prvAddTaskToReadyList( pxTCB );configASSERT( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) == NULL );#if ( configUSE_TICKLESS_IDLE != 0 ){prvResetNextTaskUnblockTime();}#endiftaskYIELD_ANY_CORE_IF_USING_PREEMPTION( pxTCB );}}taskEXIT_CRITICAL();return xReturn;
}
vTaskGenericNotifyGiveFromISR()
和 xTaskGenericNotifyFromISR()
都是用于從ISR 中向任務發送通知的函數,vTaskGenericNotifyGiveFromISR()
專門用于遞增通知值并可能喚醒任務,而 xTaskGenericNotifyFromISR()
則提供了更多種操作類型來更新通知值,適合需要傳遞更多控制信息或執行復雜同步邏輯的應用場景。
處理
ulTaskGenericNotifyTake
處理任務通知的通用函數之一,它允許一個任務等待直到其接收到的通知計數值不為零
uint32_t ulTaskGenericNotifyTake( UBaseType_t uxIndexToWaitOn,BaseType_t xClearCountOnExit,//如果設置為非零(pdTRUE),當函數返回時將清除通知計數;如果設置為零(pdFALSE),則將通知計數減一TickType_t xTicksToWait )
{uint32_t ulReturn;BaseType_t xAlreadyYielded, xShouldBlock = pdFALSE;configASSERT( uxIndexToWaitOn < configTASK_NOTIFICATION_ARRAY_ENTRIES );vTaskSuspendAll();//掛起調度器{taskENTER_CRITICAL();{if( pxCurrentTCB->ulNotifiedValue[ uxIndexToWaitOn ] == 0U ){ //當前任務的通知計數若為零,則將任務的狀態設為taskWAITING_NOTIFICATION并根據超時時間決定是否需要阻塞(xShouldBlock)。pxCurrentTCB->ucNotifyState[ uxIndexToWaitOn ] = taskWAITING_NOTIFICATION;if( xTicksToWait > ( TickType_t ) 0 ){xShouldBlock = pdTRUE;}}}taskEXIT_CRITICAL();if( xShouldBlock == pdTRUE ){ //如果需要阻塞,則將當前任務添加到延遲列表中等待超時或被通知喚醒。prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE );}}xAlreadyYielded = xTaskResumeAll();//嘗試恢復調度器,并判斷是否發生了上下文切換(xAlreadyYielded)if( ( xShouldBlock == pdTRUE ) && ( xAlreadyYielded == pdFALSE ) ){ //如果需要阻塞且沒有發生上下文切換,則觸發一次任務切換taskYIELD_WITHIN_API();}taskENTER_CRITICAL();{ //獲取當前通知值,并根據xClearCountOnExit決定如何更新通知計數ulReturn = pxCurrentTCB->ulNotifiedValue[ uxIndexToWaitOn ];if( ulReturn != 0U ){if( xClearCountOnExit != pdFALSE ){pxCurrentTCB->ulNotifiedValue[ uxIndexToWaitOn ] = ( uint32_t ) 0U;}else{pxCurrentTCB->ulNotifiedValue[ uxIndexToWaitOn ] = ulReturn - ( uint32_t ) 1;}}//將任務狀態重置為taskNOT_WAITING_NOTIFICATIONpxCurrentTCB->ucNotifyState[ uxIndexToWaitOn ] = taskNOT_WAITING_NOTIFICATION;}taskEXIT_CRITICAL();return ulReturn;
}
xTaskGenericNotifyWait
等待某個任務的通知被觸發(即收到通知),并且支持在進入和退出時對通知值進行位清除操作
BaseType_t xTaskGenericNotifyWait( UBaseType_t uxIndexToWaitOn,uint32_t ulBitsToClearOnEntry,uint32_t ulBitsToClearOnExit,uint32_t * pulNotificationValue,TickType_t xTicksToWait )
{BaseType_t xReturn, xAlreadyYielded, xShouldBlock = pdFALSE;configASSERT( uxIndexToWaitOn < configTASK_NOTIFICATION_ARRAY_ENTRIES );vTaskSuspendAll();{taskENTER_CRITICAL();{ //檢查是否已有通知到達if( pxCurrentTCB->ucNotifyState[ uxIndexToWaitOn ] != taskNOTIFICATION_RECEIVED ){ //如果目標任務 尚未收到通知(狀態不是 taskNOTIFICATION_RECEIVED)pxCurrentTCB->ulNotifiedValue[ uxIndexToWaitOn ] &= ~ulBitsToClearOnEntry;//清除一些標志位(按位與非)pxCurrentTCB->ucNotifyState[ uxIndexToWaitOn ] = taskWAITING_NOTIFICATION;//設置狀態為 taskWAITING_NOTIFICATION,表示開始等待if( xTicksToWait > ( TickType_t ) 0 ){ //如果設置了超時時間,則標記需要阻塞xShouldBlock = pdTRUE;}}}taskEXIT_CRITICAL();if( xShouldBlock == pdTRUE ){ //如果需要阻塞,將任務加入延遲隊列prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE );}}xAlreadyYielded = xTaskResumeAll();//恢復調度器并判斷是否需要調度if( ( xShouldBlock == pdTRUE ) && ( xAlreadyYielded == pdFALSE ) ){ //如果任務被阻塞且沒有發生調度,則手動觸發一次調度taskYIELD_WITHIN_API();}taskENTER_CRITICAL();{ //再次進入臨界區處理通知結果if( pulNotificationValue != NULL ){*pulNotificationValue = pxCurrentTCB->ulNotifiedValue[ uxIndexToWaitOn ];}if( pxCurrentTCB->ucNotifyState[ uxIndexToWaitOn ] != taskNOTIFICATION_RECEIVED ){ //如果仍然沒有收到通知, 返回 pdFALSExReturn = pdFALSE;}else{ //如果收到了通知, 根據 ulBitsToClearOnExit 清除指定的標志位pxCurrentTCB->ulNotifiedValue[ uxIndexToWaitOn ] &= ~ulBitsToClearOnExit;xReturn = pdTRUE;}//將通知狀態重置為 taskNOT_WAITING_NOTIFICATIONpxCurrentTCB->ucNotifyState[ uxIndexToWaitOn ] = taskNOT_WAITING_NOTIFICATION;}taskEXIT_CRITICAL();return xReturn;
}
清除
xTaskGenericNotifyStateClear
//xTask若是RECEIVED狀態, 改為 WAITING 狀態
BaseType_t xTaskGenericNotifyStateClear( TaskHandle_t xTask,UBaseType_t uxIndexToClear )
{TCB_t * pxTCB;BaseType_t xReturn;configASSERT( uxIndexToClear < configTASK_NOTIFICATION_ARRAY_ENTRIES );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;
}
ulTaskGenericNotifyValueClear
//清除xTask的uxIndexToClear的通知值的ulBitsToClear位(掩碼)
uint32_t ulTaskGenericNotifyValueClear( TaskHandle_t xTask,UBaseType_t uxIndexToClear,uint32_t ulBitsToClear );
結構體
StreamBufferHandle_t
typedef struct StreamBufferDef_t * StreamBufferHandle_t;typedef struct StreamBufferDef_t
{volatile size_t xTail;//指向緩沖區中下一個要讀取的位置volatile size_t xHead;//指向緩沖區中下一個可以寫入的位置size_t xLength;//緩沖區的總大小size_t xTriggerLevelBytes;//觸發接收任務喚醒的最小數據量volatile TaskHandle_t xTaskWaitingToReceive;//正在等待接收數據的任務句柄volatile TaskHandle_t xTaskWaitingToSend;//當前正在等待發送數據的任務句柄uint8_t * pucBuffer; //指向實際用于存儲數據的緩沖區內存uint8_t ucFlags;//用于標識 Stream Buffer 的狀態, 緩沖區類型,分配方式等#if ( configUSE_TRACE_FACILITY == 1 )UBaseType_t uxStreamBufferNumber;//調試用編號,用于跟蹤每個 Stream Buffer#endif#if ( configUSE_SB_COMPLETED_CALLBACK == 1 )//發送/接收完成后的回調函數指針StreamBufferCallbackFunction_t pxSendCompletedCallback;StreamBufferCallbackFunction_t pxReceiveCompletedCallback;#endifUBaseType_t uxNotificationIndex;//用于內部通知機制的索引
} StreamBuffer_t;
StreamBufferCallbackFunction_t
typedef void (* StreamBufferCallbackFunction_t)( StreamBufferHandle_t xStreamBuffer,BaseType_t xIsInsideISR,BaseType_t * const pxHigherPriorityTaskWoken );
創建
xStreamBufferGenericCreate
初始化一個StreamBufferHandle_t并返回,設置字段,分配結構體和緩沖區的內存,具體代碼就細說了。
StreamBufferHandle_t xStreamBufferGenericCreate( size_t xBufferSizeBytes,size_t xTriggerLevelBytes,BaseType_t xStreamBufferType,//緩沖區類型:普通流緩沖區、消息緩沖區等StreamBufferCallbackFunction_t pxSendCompletedCallback,StreamBufferCallbackFunction_t pxReceiveCompletedCallback );
stream buffer的類型
#define sbTYPE_STREAM_BUFFER ( ( BaseType_t ) 0 )
#define sbTYPE_MESSAGE_BUFFER ( ( BaseType_t ) 1 )
#define sbTYPE_STREAM_BATCHING_BUFFER ( ( BaseType_t ) 2 )
其他創建相關的宏或函數都是調用xStreamBufferGenericCreate
,區別在于stream buffer的類型以及帶不帶回調函數。3種類型×2種帶不帶回調函數,一共是六個宏(動態,如果加上靜態分配的就是12個),關于message_buffer
的單獨在message_buffer.h
里。
刪除
vStreamBufferDelete
釋放內存
動態創建的就調用vPortFree
,因為動態創建的時候結構體和數據區的內存是一起申請的,所以會一起釋放。
靜態創建的只會清除結構體,但數據區沒釋放。
void vStreamBufferDelete( StreamBufferHandle_t xStreamBuffer )
發送
size_t xStreamBufferSend( StreamBufferHandle_t xStreamBuffer,const void * pvTxData,size_t xDataLengthBytes,TickType_t xTicksToWait )
{StreamBuffer_t * const pxStreamBuffer = xStreamBuffer;size_t xReturn, xSpace = 0;size_t xRequiredSpace = xDataLengthBytes;TimeOut_t xTimeOut;size_t xMaxReportedSpace = 0;configASSERT( pvTxData );configASSERT( pxStreamBuffer );//計算所需空間,如果是MESSAGE_BUFFER還有加上消息長度占用的空間xMaxReportedSpace = pxStreamBuffer->xLength - ( size_t ) 1;if( ( pxStreamBuffer->ucFlags & sbFLAGS_IS_MESSAGE_BUFFER ) != ( uint8_t ) 0 ){xRequiredSpace += sbBYTES_TO_STORE_MESSAGE_LENGTH;configASSERT( xRequiredSpace > xDataLengthBytes );if( xRequiredSpace > xMaxReportedSpace ){ //直接設置不等待xTicksToWait = ( TickType_t ) 0;}}else{if( xRequiredSpace > xMaxReportedSpace ){ //限制請求的空間不超過最大值xRequiredSpace = xMaxReportedSpace;}}if( xTicksToWait != ( TickType_t ) 0 ){vTaskSetTimeOutState( &xTimeOut );//計時do{ //等待空間可用taskENTER_CRITICAL();{xSpace = xStreamBufferSpacesAvailable( pxStreamBuffer );//當前可用空間 xSpaceif( xSpace < xRequiredSpace ){ //清除舊通知( void ) xTaskNotifyStateClearIndexed( NULL, pxStreamBuffer->uxNotificationIndex );configASSERT( pxStreamBuffer->xTaskWaitingToSend == NULL );pxStreamBuffer->xTaskWaitingToSend = xTaskGetCurrentTaskHandle();}else{taskEXIT_CRITICAL();break;}}taskEXIT_CRITICAL();//等待通知( void ) xTaskNotifyWaitIndexed( pxStreamBuffer->uxNotificationIndex, ( uint32_t ) 0, ( uint32_t ) 0, NULL, xTicksToWait );pxStreamBuffer->xTaskWaitingToSend = NULL;} while( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE );}if( xSpace == ( size_t ) 0 ){ //重新檢查空間(以防在等待期間有其他任務釋放了空間)xSpace = xStreamBufferSpacesAvailable( pxStreamBuffer );}xReturn = prvWriteMessageToBuffer( pxStreamBuffer, pvTxData, xDataLengthBytes, xSpace, xRequiredSpace );if( xReturn > ( size_t ) 0 ){ //如果成功寫入數據,并且緩沖區中的數據量達到了觸發級別,則調用 prvSEND_COMPLETED() 通知等待接收的任務。if( prvBytesInBuffer( pxStreamBuffer ) >= pxStreamBuffer->xTriggerLevelBytes ){prvSEND_COMPLETED( pxStreamBuffer );}}return xReturn;
}
prvWriteBytesToBuffer
向StreamBuffer_t 的緩沖區(環形)寫入數據,返回新的 head 位置。
static size_t prvWriteBytesToBuffer( StreamBuffer_t * const pxStreamBuffer,const uint8_t * pucData,size_t xCount,size_t xHead )
{size_t xFirstLength;configASSERT( xCount > ( size_t ) 0 );xFirstLength = configMIN( pxStreamBuffer->xLength - xHead, xCount );//確定從當前位置 xHead 到緩沖區末尾還有多少空間configASSERT( ( xHead + xFirstLength ) <= pxStreamBuffer->xLength );//把數據從 pucData 拷貝xFirstLength長度到 pxStreamBuffer->pucBuffer[xHead] 開始的位置( void ) memcpy( ( void * ) ( &( pxStreamBuffer->pucBuffer[ xHead ] ) ), ( const void * ) pucData, xFirstLength );if( xCount > xFirstLength )//判斷是否還有剩余數據需要寫入{configASSERT( ( xCount - xFirstLength ) <= pxStreamBuffer->xLength );//如果一次沒寫完(比如緩沖區末尾空間不足),則從緩沖區起始位置開始寫剩下的部分。可以看出在維護一個環形緩沖區( void ) memcpy( ( void * ) pxStreamBuffer->pucBuffer, ( const void * ) &( pucData[ xFirstLength ] ), xCount - xFirstLength );}xHead += xCount; //更新并返回新的 head 位置if( xHead >= pxStreamBuffer->xLength ){ //如果超出緩沖區總長度,則用減法回繞xHead -= pxStreamBuffer->xLength;}return xHead;
}
prvWriteMessageToBuffer
將一條完整消息寫入 Stream Buffer
static size_t prvWriteMessageToBuffer( StreamBuffer_t * const pxStreamBuffer,const void * pvTxData,//數據源地址size_t xDataLengthBytes,//數據長度size_t xSpace,//當前緩沖區可用空間大小size_t xRequiredSpace )//寫入該消息所需的最小空間(包括消息頭)
{size_t xNextHead = pxStreamBuffer->xHead;configMESSAGE_BUFFER_LENGTH_TYPE xMessageLength;if( ( pxStreamBuffer->ucFlags & sbFLAGS_IS_MESSAGE_BUFFER ) != ( uint8_t ) 0 )//判斷是否為 Message Buffer{ //如果是,則每條消息都要先寫一個長度字段xMessageLength = ( configMESSAGE_BUFFER_LENGTH_TYPE ) xDataLengthBytes;configASSERT( ( size_t ) xMessageLength == xDataLengthBytes );if( xSpace >= xRequiredSpace ){ //如果有足夠空間,把消息長度寫入緩沖區頭部,更新 xNextHead 指針位置xNextHead = prvWriteBytesToBuffer( pxStreamBuffer, ( const uint8_t * ) &( xMessageLength ), sbBYTES_TO_STORE_MESSAGE_LENGTH, xNextHead );}else{ //直接返回 0,表示本次發送失敗。xDataLengthBytes = 0;}}else //如果不是 Message Buffer{ //不需要寫入長度字段xDataLengthBytes = configMIN( xDataLengthBytes, xSpace );}if( xDataLengthBytes != ( size_t ) 0 ){ //實際寫入數據,更新 xHead 指針pxStreamBuffer->xHead = prvWriteBytesToBuffer( pxStreamBuffer, ( const uint8_t * ) pvTxData, xDataLengthBytes, xNextHead );}return xDataLengthBytes;
}
prvBytesInBuffer
返回當前緩沖區中的有效數據量
static size_t prvBytesInBuffer( const StreamBuffer_t * const pxStreamBuffer )
{size_t xCount;xCount = pxStreamBuffer->xLength + pxStreamBuffer->xHead;xCount -= pxStreamBuffer->xTail;if( xCount >= pxStreamBuffer->xLength ){xCount -= pxStreamBuffer->xLength;}return xCount;
}
接收
xStreamBufferReceive
size_t xStreamBufferReceive( StreamBufferHandle_t xStreamBuffer,void * pvRxData,size_t xBufferLengthBytes,TickType_t xTicksToWait )
{StreamBuffer_t * const pxStreamBuffer = xStreamBuffer;size_t xReceivedLength = 0, xBytesAvailable, xBytesToStoreMessageLength;configASSERT( pvRxData );configASSERT( pxStreamBuffer );//判斷緩沖區類型并設置消息頭長度if( ( pxStreamBuffer->ucFlags & sbFLAGS_IS_MESSAGE_BUFFER ) != ( uint8_t ) 0 ){xBytesToStoreMessageLength = sbBYTES_TO_STORE_MESSAGE_LENGTH;}else if( ( pxStreamBuffer->ucFlags & sbFLAGS_IS_BATCHING_BUFFER ) != ( uint8_t ) 0 ){xBytesToStoreMessageLength = pxStreamBuffer->xTriggerLevelBytes;}else{xBytesToStoreMessageLength = 0;}if( xTicksToWait != ( TickType_t ) 0 ){ //進入臨界區檢查是否有數據可讀taskENTER_CRITICAL();{xBytesAvailable = prvBytesInBuffer( pxStreamBuffer );if( xBytesAvailable <= xBytesToStoreMessageLength ){( void ) xTaskNotifyStateClearIndexed( NULL, pxStreamBuffer->uxNotificationIndex );configASSERT( pxStreamBuffer->xTaskWaitingToReceive == NULL );pxStreamBuffer->xTaskWaitingToReceive = xTaskGetCurrentTaskHandle();}}taskEXIT_CRITICAL();if( xBytesAvailable <= xBytesToStoreMessageLength ){( void ) xTaskNotifyWaitIndexed( pxStreamBuffer->uxNotificationIndex, ( uint32_t ) 0, ( uint32_t ) 0, NULL, xTicksToWait );pxStreamBuffer->xTaskWaitingToReceive = NULL;xBytesAvailable = prvBytesInBuffer( pxStreamBuffer );}}else{ //非阻塞模式直接檢查數據量xBytesAvailable = prvBytesInBuffer( pxStreamBuffer );}if( xBytesAvailable > xBytesToStoreMessageLength ){ //實際讀取數據xReceivedLength = prvReadMessageFromBuffer( pxStreamBuffer, pvRxData, xBufferLengthBytes, xBytesAvailable );if( xReceivedLength != ( size_t ) 0 ){ //如果成功讀取到數據,調用 prvRECEIVE_COMPLETED() 通知等待發送的任務可以繼續發送了prvRECEIVE_COMPLETED( xStreamBuffer );}}return xReceivedLength;
}