在沒有使用隊列之前,任務之間的通信是通過共享全局變量或者傳遞指針參數來進行消息傳遞,但是全局變量一旦使用多了就會佔用很大的資源,在操作系統中,這就會涉及到資源管理的問題。操作系統需要管理有限的資源,進而產生了隊列,解決了任務與任務、中斷與中斷、任務與中斷的通信問題,任務與任務、任務與中斷之間要交流的數據保存在隊列中,這就叫做隊列項目。而隊列中能存儲的數據是有限的,每個數據項目大小是固定的。
操作系統使用隊列有如下好處:
- 使用消息隊列可以讓RTOS內核有效地管理任務,而全局數組是無法做到的,任務的超時等機制需要用戶自己去實現。
- 使用了全局數組就要防止多任務的訪問衝突,而使用消息隊列則處理好了這個問題,用戶無需擔心。
- 使用消息隊列可以有效地解決中斷服務程序與任務之間消息傳遞的問題。
- FIFO機制更有利於數據的處理。
數據存儲
FreeRTOS有兩種數據緩存機制,將數據發送到隊列中,第一種是先進先出的的FIFO緩存,第二種爲先進後出的類似棧的緩存機制。
數據緩存勢必涉及到數據拷貝,將要發送的數據拷貝至隊列中,FreeRTOS中採用的是值傳遞方式,在拷貝的是將整個數據拷貝到隊列中,雖然這會導致時間上的浪費,但是數據一旦被拷貝至隊列後,原始數據就可以重複讀寫。如果採用引用傳遞(即指針方式)將地址發送至隊列,效率會比較高效,但是原始數據必須一致保持可見性,期間不能更改。很明顯,在處理大量數據時,採用引用傳遞方式比較靠譜。
隊列創建
隊列創建API爲xQueueCreate(x,y),x爲創建隊列長度,y爲每個隊列項的長度,通過宏定義可以知道,實際完成隊列創建工作的xQueueGenericCreate()函數。
隊列創建過程追蹤:
隊列創建完成
隊列發送相關API
入隊方式 |
API接口 |
實際執行函數 |
從隊列尾部入隊 |
xQueueSend() |
xQueueGenericSend() |
xQueueSendToBack() |
||
xQueueSendToFront() |
||
從隊列首部入隊 |
xQueueSendToFront() |
|
從隊列尾部入隊 (帶中斷保護) |
xQueueSendFromISR() |
xQueueGenericSendFromISR() |
xQueueSendToBackFromISR() |
||
xQueueOverWriteFromISR() |
||
從隊列首部入隊 (帶中斷保護) |
xQueueSendToFront() |
普通任務中發送數據到隊列中最終是經過xQueueGenericSend()函數進行處理,帶中斷保護的發送函數最終是調用xQueueGenericSendFromISR()函數,不同的函數調用相同的函數進行處理,所以需要借用標識符在函數內部進行不同的處理。
在xQueueGenericSend()函數中主要完成的事情分兩種情況:
第一種情況是隊列未滿的情況或者採用覆寫的方式入隊,這種情況處理比較簡單,將數據拷貝至隊列(FreeRTOS傳遞數據是通過傳值方式進行),檢查出對隊列中是否有阻塞的任務,如果有則解除阻塞狀態,如果該任務比當前運行的任務優先級高,則進行一次上下文切換,返回入隊成功標誌。
第二種情況是對列已滿的情況,需要將調度器掛起,並且將隊列上鎖,因爲希望任務在由於條件不滿足的時候被阻塞掛在阻塞隊列期間不希望被其他任務或者中斷操作隊列而導致原來阻塞的任務解除了,這是不符合的。接着再判斷阻塞時間是否到達,如果已到達恢復調度器解鎖隊列,返回隊列已滿的信息,如果阻塞時間還未到而隊列依舊是滿,則只能將發送隊列項掛起。
在入隊函數中使用到了數據拷貝到隊列的函數prvCopyDataToQueue(),在該函數中主要進行的工作是判斷入隊類型,如果是隊列後面入隊,就將數據拷貝至pcWriteTo指向的地方,如果是隊列前入隊,將數據拷貝至u.xQueue.pcReadFrom指向的地方,這裏需注意如果傳入的隊列項長度爲0,代表的是互斥列隊類型,此時如果不使用互斥隊列不能將pcWriteTo指針指向NULL,因爲FreeRTOS中規定如果指向NULL代表互斥隊列。
帶中斷保護的入隊函數xQueueGenericSendFromISR()由於在中斷中不能執行阻塞或者掛起操作,所以帶中斷保護的入隊函數處理入隊時比較簡單,隊列未滿則數據拷貝進隊列,隊列已滿則返回隊列滿的信息。
出隊函數:
出隊原理與入隊差不多。