標籤:
之前說到事件,講了事件,訊號量和互斥訊號量,還有一個隊列沒說,今天說說隊列.
隊列是用在任務之間傳送多個訊息的時候,a任務發送訊息,b任務發送訊息,然後c任務可以依次去提取出b和a傳遞的訊息,不會造成系統的阻塞,他的實現結構如下
在隊列的實現中,也是使用事件ecb,OSEventType為OS_EVENT_TYPE_Q類型,而其OSEventPtr指向一個QS_Q結構的指標,該結構的定義如下
typedef struct os_q { /* QUEUE CONTROL BLOCK */
struct os_q *OSQPtr; /* 指向下一個隊列控制塊 */
void **OSQStart; /* 指向訊息指標數組的起始地址*/
void **OSQEnd; /* 指向訊息指標數組的下一個單元,構成迴圈數組緩衝區 */
void **OSQIn; /*指向插入訊息的位置,其與OSQEnd相等時,調整到指向數組的起始單元*/
void **OSQOut; /*指向被取出訊息的位置,其與OSQEnd相等時,調整到指向數組的起始單元 */
INT16U OSQSize; /*數組的長度*/
INT16U OSQEntries; /*以存放訊息指標的元素數目*/
} OS_Q;
我們可以稱呼這種結構叫做QCB,隊列控制快.在系統初始化的時候會有很多個隊列控制快,系統初始化的時候將隊列控制快串連成一個鏈表,如下
OS_EXT OS_Q OSQTbl[OS_MAX_QS];
隊列控制快數量為OS_MAX_QS,該參數在os_cfg.h檔案中指定
#define OS_MAX_QS 5u
系統存放的空閑隊列控制塊鏈表指標為
OS_EXT OS_Q *OSQFreeList;
在系統初始化的時候os_init函數中調用OS_QInit函數,該函數中有如下代碼
for (ix = 0u; ix < (OS_MAX_QS - 1u); ix++) { /* Init. list of free QUEUE control blocks */
ix_next = ix + 1u;
pq1 = &OSQTbl[ix];
pq2 = &OSQTbl[ix_next];
pq1->OSQPtr = pq2;
}
pq1 = &OSQTbl[ix];
pq1->OSQPtr = (OS_Q *)0;
OSQFreeList = &OSQTbl[0];
可以看到,這一段類似於tcb和ecb的初始化,將OSQTbl結構串連成鏈表,鏈表頭為OSQFreeList,之後,當建立隊列的時候從空閑鏈表中取出鏈表頭作為隊列事件,如下
if (pq != (OS_Q *)0) {
OSQFreeList = OSQFreeList->OSQPtr;
OS_EXIT_CRITICAL();
pq->OSQStart = start;
pq->OSQEnd = &start[size];
pq->OSQIn = start;
pq->OSQOut = start;
pq->OSQSize = size;
pq->OSQEntries = 0u;
pevent->OSEventType = OS_EVENT_TYPE_Q;
pevent->OSEventCnt = 0u;
pevent->OSEventPtr = pq;
可以看到第二句話裡面就是講空閑隊列控制塊鏈表的鏈表頭後移動到下一個元素,然後將start元素進行賦值,這個start元素師建立的時候給定的,參數如下
OS_EVENT *OSQCreate (void **start,INT16U size)
第一個參數為一個指標數組,第二個參數為指標數組大小,也就是說,start指向一個指標數組的第一個元素地址,而數組裡面存放的是指向具體的訊息的指標,我們可以形象的說,start相當於一個messagetable的首地址.
系統發送訊息佇列的時候使用函數OSQPost函數,其中核心代碼為
*pq->OSQIn++ = pmsg;
pq->OSQEntries++;
if (pq->OSQIn == pq->OSQEnd) {
pq->OSQIn = pq->OSQStart;
這段代碼向列表中添加一個元素,然後當添加的元素超過end的時候迴圈到頭上,一般來說,隊列取出元素會分為先進先出和後進先出兩種,我們先來看看申請隊列的函數就能明白ucos採用了什麼方式,申請隊列的函數為OSQPend函數
if (pq->OSQEntries > 0u) {
pmsg = *pq->OSQOut++;
pq->OSQEntries--;
if (pq->OSQOut == pq->OSQEnd) {
pq->OSQOut = pq->OSQStart;
}
這段代碼需要和上面兩段結合起來看,初始化的時候OSQIn和OSQOut都指向start頭,插入的時候OSQIn++,取出的時候OSQOut++,當插入第三個元素之後, OSQIn指向了start+3,但是OSQOut依然是指向start的,取出元素的時候取出的是最早添加到隊列中的元素,因此可以認為,系統的隊列維護方式是先進先出, OSQEntries標識系統中該隊列含有的訊號數量,只有當數量不為0的時候,才能從隊列中取出元素
另外,為了系統靈活性,ucos提供了函數可以發送訊息到隊列開頭,函數為OSQPostFront,實現如下
pq->OSQOut--;
*pq->OSQOut = pmsg;
pq->OSQEntries++;
可以看到,這個函數的實現和OSQPost的實現方式並不同, OSQPost的實現依靠的是OSQIn,而OSQPostFront依靠的是OSQOut,原因仔細想想也很簡單,因為OSQIn和OSQOut是相關聯的,我們來打個比方,如果此時OSQOut和OSQIn都指向start+1的位置,此時OSQPost三個元素,那麼OSQIn為start+4,但是OSQOut依然是start+1,這個時候我們要在隊列的頭部插入一個元素,那麼選擇有兩種,第一個是將之前插入的元素每一個都往後移動,使得OSQOut指向剛剛插入的元素,這樣需要迴圈移植3個元素,時間不可測,犯了作業系統的大忌,所以不可取,不如直接將元素排入佇列, OSQOut往前移動一位,此時OSQOut為start,到OSQIn距離為四個元素,很好的實現了目標
但是,實現這個的前提是我們實現了迴圈隊列,而訊號隊列正好是迴圈隊列.
說到這裡隊列的原理基本講清楚了,接下來總結一下
建立訊息佇列
QSQCreate
請求訊息佇列
OSQPend
向訊息佇列發送訊息
OSQPost
發送訊息到隊列頭部
OSQPostFront
清空訊息佇列
OSQFlush
刪除訊息佇列
OSQDel
查詢訊息佇列
OSQQuery
訊息佇列使用了訊息佇列控制塊qcb,隊列控制塊構成空閑鏈表,建立隊列每次從空閑鏈表中去取出一個元素,訊息控制塊內部擁有一個迴圈鏈表,over
ucos隊列的實現--源碼分析