寫在前面:傑傑這個月很忙~因此並無時間更新,如今健身房閉館裝修,晚上有空就更新一下!其實在公衆號沒更新的這段日子,天天都有兄弟在來關注個人公衆號,這讓我受寵若驚,在這裏謝謝你們的支持啦!!謝謝^安全
在這裏咱們就跟着火哥的書來學習一下FreeRTOS的消息隊列,這本書我以爲寫得很好,基本都講解到了,關於什麼是消息隊列,就請你們去看書,基礎知識我暫時不說了。數據結構
聲明:本書絕大部份內容來自《FreeRTOS 內核實現與應用開發實戰指南—基於野火 STM32 全系列(M3/4/7)開發板》,如涉及侵權請聯繫傑傑刪除異步
通常來講,魚與熊掌不可兼得,若是數據太多,那數據傳輸的速度必然是會慢下來,而若是採用引用傳遞的方式,當原始數據被修改的時候,數據有變得不安全,可是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;複製代碼
先過一遍消息隊列的數據結構,其實沒啥東西的,記不住也沒啥大問題,下面會用到就好了。ui
FreeRTOS建立隊列API函數是xQueueCreate(),但其實這是一個宏。真正被執行的函數是xQueueGenericCreate(),咱們稱這個函數爲通用隊列建立函數。spa
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;
}複製代碼
真正的初始化在下面這個函數中:.net
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則代表在中斷中使用的。code
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 函數。但掛起調度器並不會禁止中斷,中斷服務函數仍然能夠操做隊列事件列表,可能會解除任務阻塞、可能會進行上下文切換,這也是不容許的。因而,解決辦法是不但掛起調度器,還要給隊列上鎖,禁止任何中斷來操做隊列。再借用朱工精心製做的流程圖加以理解:圖片出自:blog.csdn.net/zhzht198610…
消息隊列出隊的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 /*-----------------------------------------------------------*/複製代碼
歡迎關注「物聯網IoT開發」公衆號