【FreeRTOS】队列管理
前言
通過閱讀本文可以了解到:
- 如何創建隊列
- 一個隊列如何管理它包含的數據
- 如何發送數據至隊列
- 如何從隊列接收數據
- 阻塞隊列意味著什么
- 如何阻塞多個隊列
- 如何覆蓋隊列中的數據
- 如何清除一個隊列
- 讀取和寫入一個隊列對任務優先級的影響
隊列的特征
一個隊列能保存有限數量的固定大小的數據單元,每個隊列數據單元的長度與大小是在創建隊列時設置的。
隊列通常是一個先入先出(FIFO)的緩沖區,即數據在隊列末尾被寫入,在隊列前部移除,也可以寫入隊列的前端,并覆蓋已位于隊列前端的數據。下圖演示了隊列的創建及使用:
有兩種方法可以實現隊列的數據通信:
- 通過復制實現隊列:復制隊列是指將發送至隊列的數據一個字節一個字節地復制到隊列中。
- 通過引用實現隊列:引用隊列意味著隊列只持有指向發送到隊列的數據的指針,而不是數據本身。
FreeRTOS是通過使用復制方法實現隊列。這是考慮到復制隊列比引用隊列更強大更容易使用: - 堆棧變量可以直接發送至隊列,即使該變量將在聲明它的函數退出后。
- 可以將數據發送到隊列,而無需先分配緩沖區來保存數據,然后將數據復制到分配的緩沖區。
- 發送任務可以立即重用發送至隊列的變量或緩沖區。
- 發送任務和接受任務是完全解耦的,開發人員不需要關心哪個任務擁有數據或者負責發布數據。
- 復制隊列并不會阻止隊列也被用于引用隊列。例如,當正在排隊的數據的大小使得數據復制到隊列不切實際時,可以將指向數據的指針復制到隊列中。
- RTOS完全負責分配用于存儲數據的內存。
- 在受內存保護的系統中,任務可以訪問的RAM將受到限制。這種情況下,只有當發送和接受任務都可以訪問存儲數據的RAM時,才可以使用引用隊列。
多任務訪問
隊列本身就是對象,任何知道它們存在的任務或 ISR 都可以訪問它們。任意數量的任務可以寫入同一個隊列,任意數量的任務也可以從同一個隊列讀取。在實踐中,隊列有多個寫入者是非常常見的,但是隊列有多個讀取者就不那么常見了。
阻塞隊列讀取
當任務嘗試從隊列中讀取時,可以選擇指定阻塞時間。如果隊列已經為空,則這是任務將保持在阻塞狀態以等待隊列中的數據的時間。當一個任務或中斷將數據寫入隊列時,因為等待隊列而阻塞的任務移至就緒態,如果指定的阻塞時間在數據可用之前到期,相應的任務也會移至就緒態。
隊列可以有多個讀取者,因此單個隊列可能會阻塞多個任務,在這種情況下,只有一個任務在數據可用時將被解除阻塞。解除阻塞的任務始終是等待數據的最高優先級任務。如果被阻塞的任務具有相同優先級,那么等待數據最久的任務將被阻塞。
阻塞隊列寫入
與讀取隊列一樣,任務也可以在向隊列寫入數據時指定阻塞時間。在這種情況下,如果隊列已滿,則阻塞時間是任務應該保持在阻塞狀態的最長時間,在這期間中如果隊列寫入成功則退出阻塞狀態。
阻塞多個隊列
隊列可被分組到集合中,允許任務進入阻塞狀態來等待數據在集合的全部隊列中變為可用。
使用隊列
xQueueCreate()
創建隊列的函數。創建隊列后,可以使用xQueueReset()函數將隊列返回其原始的空狀態。
uxQueueLength
創建的隊列一次可以容納的最大項數。
uxItemSize
隊列的每個項的字節大小。
返回值
如果返回NULL,則無法創建隊列,因為FreeRTOS沒喲足夠的堆內存來分配隊列數據結構和存儲區域。返回的非空值代表創建成功,該返回值是這個隊列的句柄,后續操作這個隊列時需要用到。
xQueueSendToBack()與xQueueSendToFront()
xQueueSendToBack()用于將數據發送到隊列的尾部,xQueueSendToFront()用于將數據發送到隊列的頭部。xQueueSend()與xQueueSendToBack()等價。
xQueueSendToFront() 或 xQueueSendToBack()不能在中斷函數中調用。應該使用中斷安全轉換 xQueueSendToFrontFromISR() 和 xQueueSendToBackFromISR()。
xQueue
發送數據的隊列的句柄。
pvItemToQueue
指向要復制到隊列中的數據的指針。
xTicksToWait
如果隊列已滿,任務應該保持阻塞狀態以等待隊列上可用空間的最大時間量。如果為0,則立即返回;如果為portMAX_DELAY,則一直阻塞。
返回值
在指定的時間內沒有成功寫入會返回失敗。
xQueueReceive()
從隊列中接收一個元素,收到的元素將從隊列中刪除。
xQueue
需要接收數據的隊列句柄。
pvBuffer
指向要將接收到的數據復制到內存的指針。pvBuffer指向的內存必須大于在創建隊列設置了每個數據項的大小。
xTicksToWait
如果隊列已空,則任務應保持阻塞狀態等待數據的最長時間。如果為0,則立即返回;如果為portMAX_DELAY,則一直阻塞。
返回值
在指定的時間內沒有成功寫入會返回失敗。
uxQueueMessagesWaiting()
用于查詢當前隊列的項數。
傳輸時引用指針
如果存儲在隊列中的數據量很大,最好使用隊列將指針傳輸到數據,而不是將數據本身逐個字節得復制到隊列中。引用指針在處理時間和創建隊列所需的RAM量方面都更有效。在隊列中引用指針時,以下幾點需要確保:
- 指向的RAM必須是明確定義的。通過指針在任務間共享內存時,必須確保兩個任務不會同時修改它。理想情況下,只允許發送訪問存儲器,直到指向存儲器的指針已經排隊,并且隊列接收到指針后,只允許接收任務訪問存儲器。
- 如果指向的指針時已在任務堆棧上分配的數據,堆棧幀更改后,數據無效。比如隊列引用了一個函數內的變量地址,這個函數結束后,這個變量的就是未知的了,所以這個一個非常規的操作。
使用隊列發送不同類型和長度的數據
目前我們已經知道隊列可以發送一個結構或者引用指針,將這兩個組合起來就可以允許一個任務使用一個隊列接收來自任何數據源的任何數據類型。
首先我們可以先定義一個結構,這個結構是用來描述需要發送的數據的特性的(例如類型、長度、存放數據的內存地址),與此同時要自定義一塊內存來放需要發送的數據,里面的數據如何解析可以從定義的結構得知。
從多個隊列接收
隊列集
應用程序設計通常需要單個任務來接收不同大小、不同含義、不同來源的數據,而且同時需要這些隊列都非空,這種情況可以使用“隊列集”。
隊列集允許任務從多個隊列接收數據,而無需依次輪詢每個隊列是否有數據。與使用接收結構的單個隊列實現相同功能的設計相比,使用隊列集從多個源接收數據的設計更復雜,效率也更低,所以在設計是非必要不使用。
如何使用隊列集:
- 創建隊列集
- 向集合中添加隊列。信號量也可以添加到隊列集中。
- 從隊列集讀取數據,確定隊列集中哪些隊列包含數據。當;作為隊列集合成員的隊列接收數據時,接收隊列的句柄被發送到隊列集合中,當任務調用隊列集讀取的函數時返回。因此,如果從隊列集中返回隊列句柄,那么句柄引用的隊列就包含數據,然后任務可以直接從隊列中讀取數據。
如果隊列是隊列集的成員,那么不要直接從隊列中讀取數據,除非隊列的句柄已經從隊列集中獲取。
xQueueCreateSet()
創建隊列集。
uxEventQueueLength
創建隊列集時指定可以容納的隊列數量。
返回值
如果返回NULL,則無法創建隊列集,因為沒有足夠的空間。如果返回非空值,則該返回值為隊列集的句柄。
xQueueAddToSet()
將隊列或信號量添加到隊列集中。
xQueueOrSemaphore
正在添加到隊列集中的隊列或信號量的句柄。兩種句柄可以互相轉化。
xQueueSet
要添加隊列或信號量的隊列集的句柄。
返回值
成功或否。隊列和二進制信號量只有在為空時才能添加到集合中。計數信號量只能在其計數為零時才能添加到集合中。隊列和信號量一次只能是一個隊列集的成員。
xQueueSelectFromSet()
從隊列集中讀取隊列的句柄。當隊列集的成員有接收到數據時,該接口可以返回隊列成員的句柄,然后必須直接從隊列成員中讀取數據。用該接口讀取成員句柄時,每次只能讀取一個。從使用的感覺來看,有點像任務間的switch語句。
xQueueSet
隊列集的句柄。
xTicksToWait
阻塞的超時時間。
返回值
如果成功的話該返回值是隊列集成員的句柄。
使用隊列創建郵箱
嵌入式社區內部對“郵箱”沒有共識,在不同的操作系統中有不同的含義。在本文中,指的是一個長度為1的隊列。隊列之所以被描述為郵箱,是因為它再應用程序中的使用方式,而不是因為它與隊列的功能不同:
- 隊列用于將數據從一個任務發送到另一個任務,或者從中斷服務函數發送到一個任務。發送方在隊列中放置一個條目,接收方從隊列中移除該條目。數據從發送發傳遞到接收方。
- 郵箱由于保存任何任務或中斷服務函數都可以讀取的數據。數據不會通過郵箱,而是保留在郵箱中,直到被覆蓋。發件人會覆蓋郵箱中的值,接收人會從郵箱中讀取值,不會去刪除。
xQueueOverwrite()
像隊列發送數據,如果隊列已滿,則覆蓋。
xQueuePeek()
從隊列中接收數據,而不從隊列中移除項目。
總結
以上是生活随笔為你收集整理的【FreeRTOS】队列管理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [paper]Feature Squee
- 下一篇: 人要时刻保持危机感