小編正在學習嵌入式軟件,目前建立了一個交流群,可以留下你的評論,我拉你進群
一、簡介
隊列是為了任務與任務、任務與中斷之間的通信而準備的,可以在任務與任務、任務與中斷之間消息傳遞,隊列中可以存儲有限的、大小固定的數據項目。
通常隊列采用先進先出的存儲緩沖機制(FIFO)
往隊列中發送數據稱為入隊,從隊列中讀取數據稱為出隊
FreeRTOS中隊列是通過值傳遞(默認)進行存儲的,如果大數據的時候,也可以通過數據引用(只傳遞數據的指針)進行存儲
隊列不屬于某個任務,任何任務與中斷都可以向隊列讀取/發送數據
注意:在出隊,入隊時候代碼是進入臨界區的,也就是說,在向隊列讀寫數據時不可被比FreeRTOS可管理的最高中斷優先級低的中斷所打斷
出隊阻塞:當一個任務要從一個隊列中讀數據時,但這個隊列是空的,這時任務該怎么辦呢?一是立刻返回任務繼續執行接下來的代碼,二是等一段時間后再返回任務,三是一直等待,直到隊列中有數據,讀到數據再返回任務;這三種方式取決于用戶設置的阻塞時間,如果阻塞時間為0就是第一種情況,若是阻塞時間為portMAX_DELAY就是第三種情況,若阻塞時間在0~portMAX_DELAY就是第二種情況
當阻塞時,將該任務的狀態列表項掛載到pxDelayTaskList;將該任務的事件列表項掛載到List_t xTasksWaitingToReceive;
入隊阻塞:當任務向隊列中發數據,而此時隊列是滿的,沒有多余的空間給了,這時任務該怎么辦?這與出對遇到的情況類似,程序可以根據有用設定的阻塞時間進行等待
當阻塞時,將該任務的狀態列表項掛載到pxDelayTaskList;將該任務的事件列表項掛載到List_t xTasksWaitingToSend;?
當多個任務往一個已經滿了的隊列中寫入時,任務均會進入阻塞狀態,當隊列有空間時,哪個任務先往隊列中寫入呢?
這里遵循兩個原則:1、優先級最高的任務先寫入 ;2、若優先級相同,等待時間最長的任務寫入
二、隊列結構體 /API
2.1 結構體
typedef struct QueueDefinition
{
int8_t * pcHead;? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? /*存儲區域的起始地址,也就是隊列項的起始地址 */
int8_t * pcWriteTo;? ? ? ? ? ? ? ? ? ? ? ? ? ? ? /*下一個隊列項寫入的位置 */? ? union
{
QueuePointers_t xQueue;? ? ? ? ? ? ?/* 該結構體用于隊列的選項*/
SemaphoreData_t xSemaphore;? ?/*該結構體用于互斥信號量/遞歸信號量的選項*/
} u;? ? List_t xTasksWaitingToSend;? ? ? ? ? ? ? ? ? ? ? ? ? ? /* 等待發送列表 */
List_t xTasksWaitingToReceive;? ? ? ? ? ? ? ? ? ? ? /*等待接收列表 */? ? volatile UBaseType_t uxMessagesWaiting;? ? /*非空閑隊列項目的數量,隊列中已使用的*/
UBaseType_t uxLength;? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? /*隊列長度 */
UBaseType_t uxItemSize;? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?/*對列項目的大小*/? ? volatile int8_t cRxLock;? ? ? ? ? ? ? ? ? ? ?/*讀取上鎖計數器,當使用時操作不了等待接收列表*/
volatile int8_t cTxLock;? ? ? ? ? ? ? ? ? ? /*寫入上鎖計數器,當使用時操作不了等待發送列表 */? ? ?/*其他的一些條件編譯*/
} xQUEUE;
2.2 API?
創建隊列API:
使用隊列的主要流程:創建隊列->寫入隊列->讀取隊列
函數 | 描述 |
xQueueCreate() | 動態方式創建隊列 (隊列所需內存空間由FreeRTOS從FreeRTOS管理的堆中分配) |
xQueueCreateStatic() | 靜態方式創建隊列 (需要用戶自行分配內存) |
#define xQueueCreate( uxQueueLength, uxItemSize ) xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), ( queueQUEUE_TYPE_BASE ) )
創建隊列API函數xQueueCreate()相當于API函數xQueueGenericCreate(),但后者多了一個參數queueQUEUE_TYPE_BASE
/* For internal use only. These definitions *must* match those in queue.c. */
#define queueQUEUE_TYPE_BASE ( ( uint8_t ) 0U ) //隊列
#define queueQUEUE_TYPE_SET ( ( uint8_t ) 0U ) //隊列集
#define queueQUEUE_TYPE_MUTEX ( ( uint8_t ) 1U ) //互斥信號量
#define queueQUEUE_TYPE_COUNTING_SEMAPHORE ( ( uint8_t ) 2U ) //計數型信號量
#define queueQUEUE_TYPE_BINARY_SEMAPHORE ( ( uint8_t ) 3U ) //二值信號量
#define queueQUEUE_TYPE_RECURSIVE_MUTEX ( ( uint8_t ) 4U ) //遞歸互斥信號量
?創建隊列API流程:
創建隊列:xQueueCreat() ——>實際執行的是xQueueGenericCreat()?
1、計算隊列需要多大內存xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize );
2、為隊列申請內存,申請大小為sizeof( Queue_t ) + xQueueSizeInBytes,前面部分存放結構體成員,后面存放隊列項
3、判斷內存是否申請成功,成功即計算出隊列項存儲的首地址pucQueueStorage = ( uint8_t * ) pxNewQueue;
4、調用prvInitialiseNewQueue()初始化新隊列pxNewQueue
<1>初始化隊列結構體成員變量
<2>調用xQueueGenericReset()復位隊列
①初始化其他隊列結構體成員變量
②判斷要復位的隊列是否為新創建的隊列
若不是新創建的隊列,那就復位它,將列表xTasksWaitingToSend移出
若是新創建的隊列,那就初始化xTasksWaitingToSend,xTasksWaitingToReceive這兩個列表
入隊API:

帶有覆寫功能的函數只適用于隊列項為1的隊列?
四個任務級入隊函數調用的其實是相同的一個函數
BaseType_t xQueueGenericSend( QueueHandle_t xQueue,
const void * const pvItemToQueue,
TickType_t xTicksToWait,
const BaseType_t xCopyPosition )@xQueue :隊列句柄,指明要向哪個隊列發送數據,創建隊列成功后返回此隊列句柄
@pvItemToQueue:指向要發送的消息,發送的過程中將這個消息拷貝到隊列中
@xTicksToWait:阻塞時間
@xCopyPosition:寫入隊列位置,有三種方式
queueSEND_TO_BACK:? ?寫入隊列尾部
queueSEND_TO_FRONT: 寫入隊列頭部
queueOVERWRITE:? ? ? ? ? ?覆寫隊列(僅用于隊列的隊列長度為1)返回值:
pdTRUE :向隊列發送消息成功
errQUEUE_FULL:隊列已滿,發送消息失敗? ? ? ? ? ? ? ? ? ? ? ? ?

出隊API?

BaseType_t xQueueReceive( QueueHandle_t xQueue,
void * const pvBuffer, TickType_t xTicksToWait )
BaseType_t xQueuePeek( QueueHandle_t xQueue,
void * const pvBuffer, TickType_t xTicksToWait )
@xQueue:待讀取的隊列
@pvBuffer:消息讀取緩沖區
@xTicksToWait:阻塞時間
返回值:讀取成功與失敗

三、信號量
3.1 簡介
信號量是一種解決同步問題的機制,可以實現對共享資源的有序訪問其中,
“同步”指的是任務間的同步,即信號量可以使得一個任務等待另一個任務完成某件事情后才繼續執行;
“有序訪問”指的是對被多任務或中斷訪問的共享資源(如全局變量)的管理,當一個任務在訪問(讀取或寫入)一個共享資源時,信號量可以防止其他任務或中斷在這期間訪問(讀取或寫入)這個共享資源
舉一個例子,假設某個停車場有 100 個停車位(共享資源),這個 100 個停車位對所有人(訪問共享資源的任務或中斷)開放。如果有一個人要在這個停車場停車,那么就需要先判斷這個停車場是否還有空車位(判斷信號量是否有資源), 如果此時停車場正好有空車位(信號量有資源),那么就可以直接將車開入空車位進行停車(獲取信號量成功), 如果此時停車場已經沒有空車位了(信號量沒有資源),那么這個人可以選擇不停車(獲取信號量失敗),也可以選擇等待(任務阻塞)其他人將車開出停車場(釋放信號量資源), 讓后再將車停入空車位。
在上面的這個例子中,空車位的數量相當于信號量的資源數,獲取信號量相當于占用了空車位,而釋放信號量就相當于讓出了占用的空車位。信號量用于管理共享資源的場景相當于對共享資源上了個鎖,只有任務成功獲取到了鎖的鑰匙,才能夠訪問這個共享資源,訪問完共享資源后還得歸還鑰匙,當然鑰匙可以不只一把,即信號量可以有多個資源

3.2 二值信號量
3.2.1簡介
二值信號量實際上就是一個隊列長度為 1 的隊列,在這種情況下,隊列就只有空(0)和滿(1)兩種情況,二值信號量通常用于互斥訪問或任務同步, 與互斥信號量比較類似,但是二值信號量有可能會導致優先級翻轉的問題。
互斥訪問:相當于有一扇門,兩個任務,誰獲得鑰匙誰進入,只有一個任務可以打開門
互斥信號量:二值信號量存在優先級翻轉,互斥信號存在優先級繼承

釋放信號量相當于將該標志位置滿(1),獲取標志相當于將標志位置空(0)
3.2.2相關API函數
使用二值信號量的過程:創建二值信號量-->釋放二值信號量(相當于入隊)-->獲取二值信號量(相當于出隊)
函數 | 描述 |
xSemaphoreCreateBinary() | 使用動態方式創建二值信號量,創建成功的返回值為二值信號量的句柄 |
xSemaphoreCreateBinaryStatic() | 使用靜態方式創建二值信號量 |
xSemaphoreTake() | 獲取信號量 |
xSemaphoreTakeFromISR(). | 在中斷中獲取信號量 |
xSemaphoreGive() | 釋放信號量 |
xSemaphoreGiveFromISR() | 在中斷中釋放信號量 |
vSemaphoreDelete() | 刪除信號量 |
創建二值信號量函數xSemaphoreCreateBinary(void)其實是調用函數xQueueGenericCreate( 1, ( semSEMAPHORE_QUEUE_ITEM_LENGTH ), (queueQUEUE_TYPE_BINARY_SEMAPHORE ) ),
@1:隊列長度為 1?
@semSEMAPHORE_QUEUE_ITEM_LENGTH:空
@queueQUEUE_TYPE_BINARY_SEMAPHORE:二值信號量
返回值:
創建成功返回句柄
創建失敗
釋放信號量函數xSemaphoreGive(SemaphoreHandle_t xSemaphore)其實調用函數
xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), NULL, semGIVE_BLOCK_TIME, queueSEND_TO_BACK);
@xSemaphore:句柄
@NULL:要傳入的數據?
@semGIVE_BLOCK_TIME:阻塞時間為0,不支持阻塞
@queueSEND_TO_BACK:尾部插入
返回值:釋放信號量成功;釋放信號量失敗
獲取信號量函數xSemaphoreTake(xSemaphore,xBlockTime)其實調用函數xQueueSemaphoreTake()來實現的
@xSemaphore:句柄
@xBlockTime:阻塞時間,用戶可設置
返回值:獲取信號量成功;超時,獲取信號量失敗
3.3 計數型信號量?
3.3.1簡介
計數型信號量相當于隊列長度大于1的隊列,因此計數型信號量能夠容納多個資源,這在計數信號量被創建的時候確定的
使用場景一,時間計數:在這種場合下,每次事件發生后,在事件處理函數中釋放計數型信號量(計數型信號量的資源數加 1),其他等待事件發生的任務獲取計數型信號量(計數型信號量的資源數減 1),這么一來等待事件發生的任務就可以在成功獲取到計數型信號量之后執行相應的操作。在這種場合下,計數型信號量的資源數一般在創建時設置為 0。
使用場景二,資源管理:在這種場合下,計數型信號量的資源數代表著共享資源的可用數量一個任務想要訪問共享資源,就必須先獲取這個共享資源的計數型信號量,之后在成功獲取了計數型信號量之后,才可以對這個共享資源進行訪問操作,當然,在使用完共享資源后也要釋放這個共享資源的計數型信號量。在這種場合下,計數型信號量的資源數一般在創建時設置為受其管理的共享資源的最大可用數量
3.3.2相關API函數
使用計數型信號量的過程:創建計數型信號量-->釋放計數型信號量(相當于入隊)-->獲取計數型信號量(相當于出隊)
計數型信號量的就是一個隊列長度為計數型信號量最大資源數的隊列,而隊列的非空閑項目數量就是用來記錄計數型信號量的可用資源的
函數 | 描述 |
xSemaphoreCreateCounting() | 使用動態方式創建計數型信號量 |
xSemaphoreCreateCountingStatic() | 使用靜態方式創建計數型信號量 |
xSemaphoreTake() | 獲取信號量 |
xSemaphoreTakeFromISR() | 在中斷中獲取信號量 |
xSemaphoreGive() | 釋放信號量 |
xSemaphoreGiveFromISR() | 在中斷中釋放信號量 |
vSemaphoreDelete() | 刪除信號量 |
uxSemaphoreGetCount | 獲取信號量的計數值 |
創建計數型信號量:xSemaphoreCreateCounting(uxMaxCount, uxInitialCount)其實調用函數xQueueCreateCountingSemaphore( ( uxMaxCount ), ( uxInitialCount ))
@ uxMaxCount:計數值的最大限定
@uxInitialCount:計數值的初始值
返回值:NULL:失敗;創建成功返回計數型信號量句柄
?獲取信號量的計數值:uxSemaphoreGetCount()其實調用函數uxQueueMessagesWaiting(QueueHandle_t xSemaphore)
@xSemaphore:句柄
返回值:當前信號量計數值大小
3.4 優先級翻轉?
優先級翻轉:當一個高優先級任務因獲取一個被低優先級任務獲取而處于沒有資源狀態的二值信號量時,這個高優先級的任務將被阻塞,直到低優先級的任務釋放二值信號量,而在這之前,如果有一個優先級介于這個高優先級任務和低優先級任務之間的任務就緒,那么這個中等優先級的任務就會搶占低優先級任務的運行, 這么一來,這三個任務中優先級最高的任務反而要最后才運行
在使用二值信號量和計數型信號量的時候,經常會遇到優先級翻轉的問題,優先級在搶占式內核中是非常常見的,但是在實時操作系統中是不允許出現優先級翻轉的,因為優先級翻轉會破壞任務的預期順序,可能會導致未知的嚴重后果,下面展示了一個優先級翻轉的例子
例如:
?前提條件:
任務H優先級最高,其先進行阻塞,獲取信號量,釋放信號量
任務M優先級中等,其先進行阻塞,循環打印
任務L優先級最低,其先運行,獲取信號量,釋放信號量
任務L先運行,獲取信號量后,信號量為0;此時任務H就緒,搶占任務L,任務H進行獲取信號量,但是由于信號量由任務L獲取后未釋放,所以目前H無法獲得信號量而進行阻塞,此時任務L就緒運行,運行過程中任務M就緒對任務L進行搶占并運行,運行完畢后阻塞,由于任務L沒有釋放信號量,任務H一直被阻塞,任務L運行,釋放信號量,任務H立刻獲取信號量;也即是說當任務L阻塞時,優先級最高的任務L應該時執行的,但是由于任務L把持這信號量,導致任務H因等待信號量被阻塞,被任務M搶占運行。?
高優先級任務被低優先級任務阻塞,導致高優先級任務遲遲得不到調度,但其他中等優先級的任務可以搶占CPU資源,從現象看就像中等優先級的任務比高優先級的任務有更高的優先權(即優先級翻轉)
3.5 互斥信號量
3.5.1 簡介?
互斥信號量其實就是一個擁有優先級繼承的二值信號量,在同步的應用中(任務與任務或中斷與任務之間的同步)二值信號量最適合。互斥信號量適合用于那些需要互斥訪問的應用中。在互斥訪問中互斥信號量相當于一把鑰匙, 當任務想要訪問共享資源的時候就必須先獲得這把鑰匙,當訪問完共享資源以后就必須歸還這把鑰匙,這樣其他的任務就可以拿著這把鑰匙去訪問資源。
當一個互斥信號量正在被一個低優先級的任務持有時, 如果此時有個高優先級的任務也嘗試獲取這個互斥信號量,那么這個高優先級的任務就會被阻塞。不過這個高優先級的任務會將低優先級任務的優先級提升到與自己相同的優先級,這個過程就是優先級繼承。
優先級繼承盡可能的減少了高優先級任務處于阻塞態的時間,并且將“優先級翻轉”的影響降到最低。
優先級繼承并不能完全的消除優先級翻轉的問題,它只是盡可能的降低優先級翻轉帶來的影響。實時應用應該在設計之初就要避免優先級翻轉的發生
互斥信號量不能用于中斷服務函數中,原因如下:
(1) 互斥信號量有任務優先級繼承的機制, 但是中斷不是任務,沒有任務優先級, 所以互斥信號量只能用與任務中,不能用于中斷服務函數。
(2) 中斷服務函數中不能因為要等待互斥信號量而設置阻塞時間進入阻塞態。
3.5.2 相關API
使用流程(與二值信號量有區別):創建互斥信號量-->獲取信號量-->釋放信號量
注意:創建互斥信號量是,函數內部會主動釋放信號量
函數 | 描述 |
xSemaphoreCreateMutex() | 使用動態方式創建互斥信號量 |
xSemaphoreCreateMutexStatic() | 使用靜態方式創建互斥信號量 |
xSemaphoreTake() | 獲取信號量 |
xSemaphoreGive() | 釋放信號量 |
vSemaphoreDelete() | 刪除信號量 |
互斥信號量的就是一個隊列長度為 1 的隊列, 且隊列項目的大小為 0, 而隊列的非空閑項目數量就是互斥信號量的資源數