您好,登錄后才能下訂單哦!
這篇文章主要介紹“FreeRTOS隊列的特點和相關操作”,在日常操作中,相信很多人在FreeRTOS隊列的特點和相關操作問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”FreeRTOS隊列的特點和相關操作”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
FreeRTOS 中使用隊列數據結構實現任務異步通信工作,具有如下特性:
消息支持先進先出方式排隊,支持異步讀寫工作方式。
讀寫隊列均支持超時機制。
消息支持后進先出方式排隊, 往隊首發送消息(LIFO) 。
可以允許不同長度(不超過隊列節點最大值)的任意類型消息。
一個任務能夠從任意一個消息隊列接收和發送消息。
多個任務能夠從同一個消息隊列接收和發送消息。
當隊列使用結束后,可以通過刪除隊列函數進行刪除。
一般來說,魚與熊掌不可兼得,如果數據太多,那數據傳輸的速度必然是會慢下來,而如果采用引用傳遞的方式,當原始數據被修改的時候,數據有變得不安全,但是FreeRTOS支持拷貝與引用的方式進行數據的傳輸,變得更加靈活。 隊列是通過拷貝傳遞數據的,但這并不妨礙隊列通過引用來傳遞數據。當信息的大小到達一個臨界點后,逐字節拷貝整個信息是不實際的,可以定義一個指向數據區域的指針,將指針傳遞即可。這種方法在物聯網中是非常常用的。
其實消息隊列不僅僅是用于當做消息隊列,FreeRTOS還把他當做信號量的數據結構來使用
typedef struct QueueDefinition { int8_t *pcHead; /* 指向隊列存儲區起始位置,即第一個隊列項 */ int8_t *pcTail; /* 指向隊列存儲區結束后的下一個字節 */ int8_t *pcWriteTo; /* 指向下隊列存儲區的下一個空閑位置 */ union /* 使用聯合體用來確保兩個互斥的結構體成員不會同時出現 */ { int8_t *pcReadFrom; /* 當結構體用于隊列時,這個字段指向出隊項目中的最后一個. */ UBaseType_t uxRecursiveCallCount;/* 當結構體用于互斥量時,用作計數器,保存遞歸互斥量被"獲取"的次數. */ } u; List_t xTasksWaitingToSend; /* 因為等待入隊而阻塞的任務列表,按照優先級順序存儲 */ List_t xTasksWaitingToReceive; /* 因為等待隊列項而阻塞的任務列表,按照優先級順序存儲 */ volatile UBaseType_t uxMessagesWaiting;/*< 當前隊列的隊列項數目 */ UBaseType_t uxLength; /* 隊列項的數目 */ UBaseType_t uxItemSize; /* 每個隊列項的大小 */ volatile BaseType_t xRxLock; /* 隊列上鎖后,存儲從隊列收到的列表項數目,如果隊列沒有上鎖,設置為queueUNLOCKED */ volatile BaseType_t xTxLock; /* 隊列上鎖后,存儲發送到隊列的列表項數目,如果隊列沒有上鎖,設置為queueUNLOCKED */ /* 刪除部分源碼 */ } xQUEUE; typedef xQUEUE Queue_t;
先過一遍消息隊列的數據結構,其實沒啥東西的,記不住也沒啥大問題,下面會用到就行了。
FreeRTOS創建隊列API函數是xQueueCreate(),但其實這是一個宏。真正被執行的函數是xQueueGenericCreate(),我們稱這個函數為通用隊列創建函數。
QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, const uint8_t ucQueueType ) { Queue_t *pxNewQueue; size_t xQueueSizeInBytes; uint8_t *pucQueueStorage; configASSERT( uxQueueLength > ( UBaseType_t ) 0 ); if( uxItemSize == ( UBaseType_t ) 0 ) { /* 如果 uxItemSize 為 0,也就是單個消息空間大小為 0,這樣子就不 需要申請內存了,那么 xQueueSizeInBytes 也設置為 0 即可,設置為 0 是可以的,用作信號 量的時候這個就可以設置為 0。*/ xQueueSizeInBytes = ( size_t ) 0; } else { /* 分配足夠消息存儲空間,空間的大小為隊列長度*單個消息大小 */ xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */ } /* FreeRTOS 調用 pvPortMalloc()函數向系統申請內存空間,內存大 小為消息隊列控制塊大小加上消息存儲空間大小,因為這段內存空間是需要保證連續的 */ pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes ); if( pxNewQueue != NULL ) { /* 計算出消息存儲空間的起始地址 */ pucQueueStorage = ( ( uint8_t * ) pxNewQueue ) + sizeof( Queue_t ); #if( configSUPPORT_STATIC_ALLOCATION == 1 ) { pxNewQueue->ucStaticallyAllocated = pdFALSE; } #endif /* configSUPPORT_STATIC_ALLOCATION */ prvInitialiseNewQueue( uxQueueLength, uxItemSize, pucQueueStorage, ucQueueType, pxNewQueue ); } return pxNewQueue; }
真正的初始化在下面這個函數中:
BaseType_t xQueueGenericReset( QueueHandle_t xQueue, BaseType_t xNewQueue ) { Queue_t * const pxQueue = ( Queue_t * ) xQueue; configASSERT( pxQueue ); taskENTER_CRITICAL(); { /* 消息隊列數據結構的相關初始化 */ pxQueue->pcTail = pxQueue->pcHead + ( pxQueue->uxLength * pxQueue->uxItemSize ); pxQueue->uxMessagesWaiting = ( UBaseType_t ) 0U; pxQueue->pcWriteTo = pxQueue->pcHead; pxQueue->u.pcReadFrom = pxQueue->pcHead + ( ( pxQueue->uxLength - ( UBaseType_t ) 1U ) * pxQueue->uxItemSize ); pxQueue->cRxLock = queueUNLOCKED; pxQueue->cTxLock = queueUNLOCKED; if( xNewQueue == pdFALSE ) { if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE ) { if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE ) { queueYIELD_IF_USING_PREEMPTION(); } else { mtCOVERAGE_TEST_MARKER(); } } else { mtCOVERAGE_TEST_MARKER(); } } else { /* Ensure the event queues start in the correct state. */ vListInitialise( &( pxQueue->xTasksWaitingToSend ) ); vListInitialise( &( pxQueue->xTasksWaitingToReceive ) ); } } taskEXIT_CRITICAL(); return pdPASS; }
初始化完成之后,為了讓大家理解,消息隊列是怎么樣的,就給出一個示意圖,黃色部分是消息隊列的控制塊,而綠色部分則是消息隊列的存放消息的地方,在創建的時候,我們知道的消息隊列長度與單個消息空間大小。
任務或者中斷服務程序都可以給消息隊列發送消息,當發送消息時,如果隊列未滿或者允許覆蓋入隊, FreeRTOS 會將消息拷貝到消息隊列隊尾,否則,會根據用戶指定的阻塞超時時間進行阻塞,在這段時間中,如果隊列一直不允許入隊,該任務將保持阻塞狀態以等待隊列允許入隊。當其它任務從其等待的隊列中讀取入了數據(隊列未滿),該任務將自動由阻塞態轉為就緒態。當任務等待的時間超過了指定的阻塞時間,即使隊列中還不允許入隊,任務也會自動從阻塞態轉移為就緒態,此時發送消息的任務或者中斷程序會收到一個錯誤碼 errQUEUE_FULL。 發送緊急消息的過程與發送消息幾乎一樣,唯一的不同是,當發送緊急消息時,發送的位置是消息隊列隊頭而非隊尾,這樣,接收者就能夠優先接收到緊急消息,從而及時進行消息處理。 下面是消息隊列的發送API接口,函數中有FromISR則表明在中斷中使用的。
1 /*-----------------------------------------------------------*/ 2 BaseType_t xQueueGenericSend( QueueHandle_t xQueue, (1) 3 const void * const pvItemToQueue, (2) 4 TickType_t xTicksToWait, (3) 5 const BaseType_t xCopyPosition ) (4) 6 { 7 BaseType_t xEntryTimeSet = pdFALSE, xYieldRequired; 8 TimeOut_t xTimeOut; 9 Queue_t * const pxQueue = ( Queue_t * ) xQueue; 10 11 /* 已刪除一些斷言操作 */ 12 13 for ( ;; ) { 14 taskENTER_CRITICAL(); (5) 15 { 16 /* 隊列未滿 */ 17 if ( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) 18 || ( xCopyPosition == queueOVERWRITE ) ) { (6) 19 traceQUEUE_SEND( pxQueue ); 20 xYieldRequired = 21 prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition ); (7) 22 23 /* 已刪除使用隊列集部分代碼 */ 24 /* 如果有任務在等待獲取此消息隊列 */ 25 if ( listLIST_IS_EMPTY(&(pxQueue->xTasksWaitingToReceive))==pdFALSE){ (8) 26 /* 將任務從阻塞中恢復 */ 27 if ( xTaskRemoveFromEventList( 28 &( pxQueue->xTasksWaitingToReceive ) )!=pdFALSE) { (9) 29 /* 如果恢復的任務優先級比當前運行任務優先級還高, 30 那么需要進行一次任務切換 */ 31 queueYIELD_IF_USING_PREEMPTION(); (10) 32 } else { 33 mtCOVERAGE_TEST_MARKER(); 34 } 35 } else if ( xYieldRequired != pdFALSE ) { 36 /* 如果沒有等待的任務,拷貝成功也需要任務切換 */ 37 queueYIELD_IF_USING_PREEMPTION(); (11) 38 } else { 39 mtCOVERAGE_TEST_MARKER(); 40 } 41 42 taskEXIT_CRITICAL(); (12) 43 return pdPASS; 44 } 45 /* 隊列已滿 */ 46 else { (13) 47 if ( xTicksToWait == ( TickType_t ) 0 ) { 48 /* 如果用戶不指定阻塞超時時間,退出 */ 49 taskEXIT_CRITICAL(); (14) 50 traceQUEUE_SEND_FAILED( pxQueue ); 51 return errQUEUE_FULL; 52 } else if ( xEntryTimeSet == pdFALSE ) { 53 /* 初始化阻塞超時結構體變量,初始化進入 54 阻塞的時間xTickCount和溢出次數xNumOfOverflows */ 55 vTaskSetTimeOutState( &xTimeOut ); (15) 56 xEntryTimeSet = pdTRUE; 57 } else { 58 mtCOVERAGE_TEST_MARKER(); 59 } 60 } 61 } 62 taskEXIT_CRITICAL(); (16) 63 /* 掛起調度器 */ 64 vTaskSuspendAll(); 65 /* 隊列上鎖 */ 66 prvLockQueue( pxQueue ); 67 68 /* 檢查超時時間是否已經過去了 */ 69 if (xTaskCheckForTimeOut(&xTimeOut, &xTicksToWait)==pdFALSE){ (17) 70 /* 如果隊列還是滿的 */ 71 if ( prvIsQueueFull( pxQueue ) != pdFALSE ) { (18) 72 traceBLOCKING_ON_QUEUE_SEND( pxQueue ); 73 /* 將當前任務添加到隊列的等待發送列表中 74 以及阻塞延時列表,延時時間為用戶指定的超時時間xTicksToWait */ 75 vTaskPlaceOnEventList( 76 &( pxQueue->xTasksWaitingToSend ), xTicksToWait );(19) 77 /* 隊列解鎖 */ 78 prvUnlockQueue( pxQueue ); (20) 79 80 /* 恢復調度器 */ 81 if ( xTaskResumeAll() == pdFALSE ) { 82 portYIELD_WITHIN_API(); 83 } 84 } else { 85 /* 隊列有空閑消息空間,允許入隊 */ 86 prvUnlockQueue( pxQueue ); (21) 87 ( void ) xTaskResumeAll(); 88 } 89 } else { 90 /* 超時時間已過,退出 */ 91 prvUnlockQueue( pxQueue ); (22) 92 ( void ) xTaskResumeAll(); 93 94 traceQUEUE_SEND_FAILED( pxQueue ); 95 return errQUEUE_FULL; 96 } 97 } 98 } 99 /*-----------------------------------------------------------*/
如果阻塞時間不為 0,任務會因為等待入隊而進入阻塞, 在將任務設置為阻塞的過程中, 系統不希望有其它任務和中斷操作這個隊列的 xTasksWaitingToReceive 列表和 xTasksWaitingToSend 列表,因為可能引起其它任務解除阻塞,這可能會發生優先級翻轉。比如任務 A 的優先級低于當前任務,但是在當前任務進入阻塞的過程中,任務 A 卻因為其它原因解除阻塞了,這顯然是要絕對禁止的。因此FreeRTOS 使用掛起調度器禁止其它任務操作隊列,因為掛起調度器意味著任務不能切換并且不準調用可能引起任務切換的 API 函數。但掛起調度器并不會禁止中斷,中斷服務函數仍然可以操作隊列事件列表,可能會解除任務阻塞、可能會進行上下文切換,這也是不允許的。于是,解決辦法是不但掛起調度器,還要給隊列上鎖,禁止任何中斷來操作隊列。 再借用朱工精心制作的流程圖加以理解:圖片出自:https://blog.csdn.net/zhzht19861011/article/details/51510384
消息隊列出隊的API函數接口: 消息隊列出隊過程分析,其實跟入隊差不多,請看注釋:
1 /*-----------------------------------------------------------*/ 2 BaseType_t xQueueGenericReceive( QueueHandle_t xQueue, (1) 3 void * const pvBuffer, (2) 4 TickType_t xTicksToWait, (3) 5 const BaseType_t xJustPeeking ) (4) 6 { 7 BaseType_t xEntryTimeSet = pdFALSE; 8 TimeOut_t xTimeOut; 9 int8_t *pcOriginalReadPosition; 10 Queue_t * const pxQueue = ( Queue_t * ) xQueue; 11 12 /* 已刪除一些斷言 */ 13 for ( ;; ) { 14 taskENTER_CRITICAL(); (5) 15 { 16 const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting; 17 18 /* 看看隊列中有沒有消息 */ 19 if ( uxMessagesWaiting > ( UBaseType_t ) 0 ) { (6) 20 /*防止僅僅是讀取消息,而不進行消息出隊操作*/ 21 pcOriginalReadPosition = pxQueue->u.pcReadFrom; (7) 22 /* 拷貝消息到用戶指定存放區域pvBuffer */ 23 prvCopyDataFromQueue( pxQueue, pvBuffer ); (8) 24 25 if ( xJustPeeking == pdFALSE ) { (9) 26 /* 讀取消息并且消息出隊 */ 27 traceQUEUE_RECEIVE( pxQueue ); 28 29 /* 獲取了消息,當前消息隊列的消息個數需要減一 */ 30 pxQueue->uxMessagesWaiting = uxMessagesWaiting - 1; (10) 31 /* 判斷一下消息隊列中是否有等待發送消息的任務 */ 32 if ( listLIST_IS_EMPTY( (11) 33 &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE ) { 34 /* 將任務從阻塞中恢復 */ 35 if ( xTaskRemoveFromEventList( (12) 36 &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE ) { 37 /* 如果被恢復的任務優先級比當前任務高,會進行一次任務切換 */ 38 queueYIELD_IF_USING_PREEMPTION(); (13) 39 } else { 40 mtCOVERAGE_TEST_MARKER(); 41 } 42 } else { 43 mtCOVERAGE_TEST_MARKER(); 44 } 45 } else { (14) 46 /* 任務只是看一下消息(peek),并不出隊 */ 47 traceQUEUE_PEEK( pxQueue ); 48 49 /* 因為是只讀消息 所以還要還原讀消息位置指針 */ 50 pxQueue->u.pcReadFrom = pcOriginalReadPosition; (15) 51 52 /* 判斷一下消息隊列中是否還有等待獲取消息的任務 */ 53 if ( listLIST_IS_EMPTY( (16) 54 &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE ) { 55 /* 將任務從阻塞中恢復 */ 56 if ( xTaskRemoveFromEventList( 57 &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE ) { 58 /* 如果被恢復的任務優先級比當前任務高,會進行一次任務切換 */ 59 queueYIELD_IF_USING_PREEMPTION(); 60 } else { 61 mtCOVERAGE_TEST_MARKER(); 62 } 63 } else { 64 mtCOVERAGE_TEST_MARKER(); 65 } 66 } 67 68 taskEXIT_CRITICAL(); (17) 69 return pdPASS; 70 } else { (18) 71 /* 消息隊列中沒有消息可讀 */ 72 if ( xTicksToWait == ( TickType_t ) 0 ) { (19) 73 /* 不等待,直接返回 */ 74 taskEXIT_CRITICAL(); 75 traceQUEUE_RECEIVE_FAILED( pxQueue ); 76 return errQUEUE_EMPTY; 77 } else if ( xEntryTimeSet == pdFALSE ) { 78 /* 初始化阻塞超時結構體變量,初始化進入 79 阻塞的時間xTickCount和溢出次數xNumOfOverflows */ 80 vTaskSetTimeOutState( &xTimeOut ); (20) 81 xEntryTimeSet = pdTRUE; 82 } else { 83 mtCOVERAGE_TEST_MARKER(); 84 } 85 } 86 } 87 taskEXIT_CRITICAL(); 88 89 vTaskSuspendAll(); 90 prvLockQueue( pxQueue ); (21) 91 92 /* 檢查超時時間是否已經過去了*/ 93 if ( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE ) {(22) 94 /* 如果隊列還是空的 */ 95 if ( prvIsQueueEmpty( pxQueue ) != pdFALSE ) { 96 traceBLOCKING_ON_QUEUE_RECEIVE( pxQueue ); (23) 97 /* 將當前任務添加到隊列的等待接收列表中 98 以及阻塞延時列表,阻塞時間為用戶指定的超時時間xTicksToWait */ 99 vTaskPlaceOnEventList( 100 &( pxQueue->xTasksWaitingToReceive ), xTicksToWait ); 101 prvUnlockQueue( pxQueue ); 102 if ( xTaskResumeAll() == pdFALSE ) { 103 /* 如果有任務優先級比當前任務高,會進行一次任務切換 */ 104 portYIELD_WITHIN_API(); 105 } else { 106 mtCOVERAGE_TEST_MARKER(); 107 } 108 } else { 109 /* 如果隊列有消息了,就再試一次獲取消息 */ 110 prvUnlockQueue( pxQueue ); (24) 111 ( void ) xTaskResumeAll(); 112 } 113 } else { 114 /* 超時時間已過,退出 */ 115 prvUnlockQueue( pxQueue ); (25) 116 ( void ) xTaskResumeAll(); 117 118 if ( prvIsQueueEmpty( pxQueue ) != pdFALSE ) { 119 /* 如果隊列還是空的,返回錯誤代碼errQUEUE_EMPTY */ 120 traceQUEUE_RECEIVE_FAILED( pxQueue ); 121 return errQUEUE_EMPTY; (26) 122 } else { 123 mtCOVERAGE_TEST_MARKER(); 124 } 125 } 126 } 127 } 128 /*-----------------------------------------------------------*/
到此,關于“FreeRTOS隊列的特點和相關操作”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。