Waiting for a message at a Queue, OSQPend()


程式清單 L6.22 是 OSQPend() 函式的程式碼。OSQPend() 函式首先檢查 ECB 是否是由 OSQCreate() 函式建立的 [L6.22(1)],接著,該函式檢查 message queue 中是否有消息可用 (即 .OSQEntries 是否大於 0) [L6.22(2)]。如果有,OSQPend() 函式將指向消息的指標複製到 msg 變數中,並讓 .OSQOut 指標指向佇列中的下一個單元 [L6.22(3)],然後將佇列中的有效消息數減 1 [L6.22(4)]。因為 message queue 是一個 circular buffer,OSQPend() 函式需要檢查 .OSQOut 是否超過了佇列中的最後一個單元 [L6.22(5)]。當發生當 .OSQOut 超過陣列的長度時,就要將 .OSQOut 重新調整到指向佇列的起始單元 [L6.22(6)]。這是我們呼叫 OSQPend() 函式時所期望的,也是執行 OSQPend() 函式最快的路徑。


如果這時 message queue 中沒有消息 (.OSEventEntries 是 0),OSQPend() 函式檢查它的呼叫者是否是 ISR [L6.22(7)]。像 OSSemPend() 和 OSMboxPend() 函式一樣,不能在 ISR 中呼叫 OSQPend(),因為 ISR 是不能等待的。但是,如果 message queue 中有消息,即使從 ISR 中呼叫 OSQPend() 函式,也一樣是成功的。


如果 message queue 中沒有消息,呼叫 OSQPend() 函式的 task 進入休眠的狀態 [L6.22(8)]。當有其他的 task 向該 message queue 發送了消息或者等待時間 time-out,並且該 task 成為 HPT 時,OSSched() 會返回 [L6.22(9)]。這時,OSQPend() 要檢查是否有消息被放到該 task 的 TCB 中 [L6.22(10)]。如果有,那麼該次函式呼叫成功,會把 task 的 TCB 中指向 message queue 的指標刪除 [L6.22(17)],並將相對應的消息被返回到呼叫者 [L6.22(17)]。


在 OSQPend() 函式中,透過檢查 task 的 TCB 中的 .OSTCBStat 欄位,可以知道是否等到時間 time-out。如果其相對應的 OS_STAT_Q 位元被設為 1,說明該 task 已經等待 time-out [L6.22(12)]。這時,透過呼叫函式 OSEventTo() 可以將 task 從 message queue 的 wait list 中刪除 [L6.22(13)]。這時,因為 message queue 中沒有消息,所以返回的指標是 NULL [L6.22(14)]。


如果 TCB 的狀態旗標位元中的 OS_STAT_Q 位元沒有被設為 1,說明有 task 發出了一條消息。OSQPend() 函式從佇列中取出該消息 [L6.22(15)]。然後,將 task 的 TCB 所指到的 ECB 指標刪除 [L6.22(16)]。








程式清單 L6.22 Waiting for a message to arrive at a queue



void *OSQPend (OS_EVENT *pevent, INT16U timeout, INT8U *err)
{
void *msg;
OS_Q *pq;


OS_ENTER_CRITICAL();

if (pevent->OSEventType != OS_EVENT_TYPE_Q) { (1)
OS_EXIT_CRITICAL();
*err = OS_ERR_EVENT_TYPE;
return ((void *) 0);
}

pq = pevent->OSEventPtr;
if (pq->OSQEntries != 0) { (2)
msg = *pq->OSQOut++; (3)
pq->OSQEntries--; (4)

if (pq->OSQOut == pq->OSQEnd) { (5)
pq->OSQOut = pq->OSQStart; (6)
}

OS_EXIT_CRITICAL();
*err = OS_NO_ERR;

} else if (OSIntNesting > 0) { (7)
OS_EXIT_CRITICAL();
*err = OS_ERR_PEND_ISR;

} else {
OSTCBCur->OSTCBStat |= OS_STAT_Q; (8)
OSTCBCur->OSTCBDly = timeout;
OSEventTaskWait(pevent);
OS_EXIT_CRITICAL();

OSSched(); (9)

OS_ENTER_CRITICAL();

if ((msg = OSTCBCur->OSTCBMsg) != (void *) 0) { (10)
OSTCBCur->OSTCBMsg = (void *) 0;
OSTCBCur->OSTCBStat = OS_STAT_RDY;
OSTCBCur->OSTCBEventPtr = (OS_EVENT *) 0; (11)
OS_EXIT_CRITICAL();
*err = OS_NO_ERR;

} else if (OSTCBCur->OSTCBStat & OS_STAT_Q) { (12)
OSEventTO(pevent); (13)
OS_EXIT_CRITICAL();
msg = (void *) 0; (14)
*err = OS_TIMEOUT;

} else {
msg = *pq->OSQOut++; (15)
pq->OSQEntries--;

if (pq->OSQOut == pq->OSQEnd) {
pq->OSQOut = pq->OSQStart;
}

OSTCBCur->OSTCBEventPtr = (OS_EVENT *) 0; (16)
OS_EXIT_CRITICAL();
*err = OS_NO_ERR;
}
}
return (msg); (17)
}

Sending a message to a queue (FIFO), OSQPost()


程式清單 L6.23 是 OSQPost() 函式的程式碼。在確認 ECB 是 message queue 後 [L6.23(1)],OSQPost() 函式檢查是否有 task 在等待該 message queue 中的消息 [L6.23(2)]。當 ECB 的 .OSEventGrp 欄位為非 0 值時,說明該 message queue 的 wait list 中有 task 再等待消息。這時,呼叫 OSEventTaskRdy() 函式從 list 中取出 HPT [L6.23(3)],並將它設為 READY 狀態。然後呼叫函式 OSSched() [L6.23(4)] 來進行 task 的排程。如果上面取出的 task 的優先權在整個 ready list 中最高的,而且 OSQPost() 函式不是 ISR 呼叫的,就執行 task switch,讓該 HPT 被執行。否則的話,OSSched() 函式會直接返回,呼叫 OSQPost() 函式的 task 將會繼續執行。


如果沒有 task 在等待該 message queue 中的消息,而且此時 message queue 未滿 [L6.23(5)],指向該消息的指標被插入到 message queue 中 [L6.23(6)]。這樣,下一個呼叫 OSQPend() 函式的 task 就可以馬上得到該消息。注意,如果此時 message queue 已滿,那麼該消息將由於不能插入到 message queue 中而失去。


此外,如果 OSQPost() 函式是由 ISR 呼叫的,那麼即使產生了 HPT,也不會在呼叫 OSSched() 函式時發生 task switch。這個動作一直要等到巢狀中斷的最外層 ISR 呼叫 OSIntExit() 函式時才能進行。








程式清單 L6.23 Depositing a message in a queue (FIFO)



INT8U OSQPost (OS_EVENT *pevent, void *msg)
{
OS_Q *pq;


OS_ENTER_CRITICAL();

if (pevent->OSEventType != OS_EVENT_TYPE_Q) { (1)
OS_EXIT_CRITICAL();
return (OS_ERR_EVENT_TYPE);
}

if (pevent->OSEventGrp) { (2)
OSEventTaskRdy(pevent, msg, OS_STAT_Q); (3)
OS_EXIT_CRITICAL();

OSSched(); (4)

return (OS_NO_ERR);

} else {
pq = pevent->OSEventPtr;

if (pq->OSQEntries >= pq->OSQSize) { (5)
OS_EXIT_CRITICAL();
return (OS_Q_FULL);

} else {
*pq->OSQIn++ = msg; (6)
pq->OSQEntries++;

if (pq->OSQIn == pq->OSQEnd) {
pq->OSQIn = pq->OSQStart;
}

OS_EXIT_CRITICAL();
}
return (OS_NO_ERR);
}
}

Sending a message to a queue (LIFO), OSQPostFront()


OSQPostFront() 函式和 OSQPost() 基本上是一樣的,只是在插入新消息到 message queue 中時,是使用 .OSQOut 作為指向下一個插入消息的單元的指標,而不是 .OSQIn。程式清單 L6.24 是它的程式碼。值得注意的是,.OSQOut 指標指向的是已經插入了消息指標的單元,所以再插入新的消息指標前,必須先將 .OSQOut 指標在 message queue 中前移一個單元。如果 .OSQOut 指標指向的當前單元是佇列中的第一個單元 [L6.24(1)],這時再前移就會發生超出邊界,需要特別地將該指標指向佇列的末尾 [L6.24(2)]。由於 .OSQEnd 指向的是 message queue 中最後一個單元的下一個單元,因此 .OSQOut 必須被調整到指向佇列的有效範圍內 [L6.24(3)]。因為 QSQPend() 函式取出的消息是由 OSQPend() 函式剛剛插入的,因此 OSQPostFront() 函式實現了一個 LIFO queue。








程式清單 L6.24 Depositing a message in a queue (LIFO)



INT8U OSQPostFront (OS_EVENT *pevent, void *msg)
{
OS_Q *pq;


OS_ENTER_CRITICAL();

if (pevent->OSEventType != OS_EVENT_TYPE_Q) {
OS_EXIT_CRITICAL();
return (OS_ERR_EVENT_TYPE);
}

if (pevent->OSEventGrp) {
OSEventTaskRdy(pevent, msg, OS_STAT_Q);
OS_EXIT_CRITICAL();

OSSched();

return (OS_NO_ERR);

} else {
pq = pevent->OSEventPtr;

if (pq->OSQEntries >= pq->OSQSize) {
OS_EXIT_CRITICAL();
return (OS_Q_FULL);

} else {
if (pq->OSQOut == pq->OSQStart) { (1)
pq->OSQOut = pq->OSQEnd; (2)

}

pq->OSQOut--; (3)
*pq->OSQOut = msg;
pq->OSQEntries++;
OS_EXIT_CRITICAL();
}
return (OS_NO_ERR);
}
}

Getting a message without waiting, OSQAccept()


如果試圖從 message queue 中取出一條消息,而此時 message queue 又為空時,也可以不讓呼叫 task 等待而直接返回呼叫者。這個操作可以呼叫 OSQAccept() 函式來完成。程式清單 L6.25 是該函式的程式碼。OSQAccept() 函式首先查看 pevent 指向的 ECB 是否是由 OSQCreate() 函式建立的 [L6.25(1)],然後它檢查當前 message queue 中是否有消息 [L6.25(2)]。如果 message queue 中有至少一條消息,那麼就從 .OSQOut 指向的單元中取出消息 [L6.25(3)]。OSQAccept() 函式的呼叫者需要對 OSQAccept() 返回的指標進行檢查。如果該指標是 NULL,說明 message queue 是空的,其中沒有消息可以用 [L6.25(4)]。否則的話,已經從 message queue 中成功地取得了一條消息。當 ISR 要從 message queue 中取消息時,必須使用 OSQAccept() 函式,而不能使用 OSQPend() 函式。








程式清單 L6.25 Getting a message without waiting



void *OSQAccept (OS_EVENT *pevent)
{
void *msg;
OS_Q *pq;


OS_ENTER_CRITICAL();

if (pevent->OSEventType != OS_EVENT_TYPE_Q) { (1)
OS_EXIT_CRITICAL();
return ((void *) 0);
}

pq = pevent->OSEventPtr;

if (pq->OSQEntries != 0) { (2)
msg = *pq->OSQOut++; (3)
pq->OSQEntries--;

if (pq->OSQOut == pq->OSQEnd) {
pq->OSQOut = pq->OSQStart;
}
} else {
msg = (void *) 0; (4)
}
OS_EXIT_CRITICAL();
return (msg);
}

Flushing a queue, OSQFlush()


OSQFlush() 函式允許使用者清除一個 message queue 中的所有消息,重新開始使用。程式清單 L6.26 是該函式的程式碼。和前面的其他函式一樣,該函式首先檢查 pevent 指標是否是一個 message queue [L6.26(1)],然後將佇列的插入指標和取出指標重定,使它們都指向佇列起始單元,同時,將佇列中的消息數設為 0 [L6.26(2)]。這裏,沒有檢查該 message queue 的 wait list 是否為空,因為只要該 wait list 不是空的,.OSQEntries 就一定是 0。唯一不同的是,指標 .OSQIn 和 .OSQOut 此時可以指向 message queue 中的任何單元,不一定是起始單元。








程式清單 L6.26 Flushing the contents of a queue



INT8U OSQFlush (OS_EVENT *pevent)
{
OS_Q *pq;


OS_ENTER_CRITICAL();

if (pevent->OSEventType != OS_EVENT_TYPE_Q) { (1)
OS_EXIT_CRITICAL();
return (OS_ERR_EVENT_TYPE);
}

pq = pevent->OSEventPtr;
pq->OSQIn = pq->OSQStart; (2)
pq->OSQOut = pq->OSQStart;
pq->OSQEntries = 0;
OS_EXIT_CRITICAL();
return (OS_NO_ERR);
}

Obtaining the status of a queue, OSQQuery()


OSQQuery() 函式讓使用者可以查詢一個 message queue 的當前狀態。程式清單 L6.27 是該函式的程式碼。OSQQuery() 需要兩個參數:一個是指向 message queue 的指標 pevent。它是在建立一個 message queue 時,由 OSQCreate() 函式返回的;另一個是指向 OS_Q_DATA (見 uCOS_II.H) 資料結構的指標 pdata。該結構包含了有關 message queue 的資訊。在呼叫 OSQQuery() 函式之前,必須先定義該資料結構變數。OS_Q_DATA 結構包含下面的幾個欄位:


.OSMsg 如果 message queue 中有消息,它包含指標 .OSQOut 所指向的佇列單元中的內容。如果佇列是空的,.OSMsg 包含一個 NULL 指標。


.OSNMsgs 是 message queue 中的消息數 (.OSQEntries 的拷貝)。


.OSQSize 是 message queue 的總的容量


.OSEventTbl[].OSEventGrp 是 message queue 的 wait list。透過它們,OSQQuery() 的呼叫函式可以得到等待該 message queue 中的消息的 task 總數。


OSQQuery() 函式首先檢查 pevent 指標指向的 ECB 是一個 message queue [L6.27(1)],然後複製 wait list [L6.27(2)]。如果 message queue 中有消息 [L6.27(3)],.OSQOut 指向的佇列單元中的內容被複製到 OS_Q_DATA 結構中 [L6.27(4)],否則的話,就複製一個 NULL 指標 [L6.27(5)]。最後,複製 message queue 中的消息數和 message queue 的容量大小 [L6.27(6)]。








程式清單 L6.27 Obtaining the status of a queue



INT8U OSQQuery (OS_EVENT *pevent, OS_Q_DATA *pdata)
{
OS_Q *pq;
INT8U i;
INT8U *psrc;
INT8U *pdest;


OS_ENTER_CRITICAL();

if (pevent->OSEventType != OS_EVENT_TYPE_Q) { (1)
OS_EXIT_CRITICAL();
return (OS_ERR_EVENT_TYPE);
}

pdata->OSEventGrp = pevent->OSEventGrp; (2)
psrc = &pevent->OSEventTbl[0];
pdest = &pdata->OSEventTbl[0];

for (i = 0; i < OS_EVENT_TBL_SIZE; i++) {
*pdest++ = *psrc++;
}

pq = (OS_Q *)pevent->OSEventPtr;
if (pq->OSQEntries > 0) { (3)
pdata->OSMsg = pq->OSQOut; (4)
} else {
pdata->OSMsg = (void *) 0; (5)
}

pdata->OSNMsgs = pq->OSQEntries; (6)
pdata->OSQSize = pq->OSQSize;
OS_EXIT_CRITICAL();
return (OS_NO_ERR);
}

Using a message queue when reading analog inputs


在控制系統中,經常要頻繁地讀取類比訊號的值。這時,可以先建立一個 task 然後呼叫 OSTimeDly() 函式,並且給出希望的取樣週期。然後,如圖 F6.11 所示,讓 A/D 取樣的 task 從一個 message queue 中等待消息。該程式最長的等待時間就是取樣週期。當沒有其他 task 向該 message queue 中發送消息時,A/D 取樣 task 因為等待 time-out 而退出等待狀態並進行執行。這就模仿了 OSTimeDly() 函式的功能。


也許,我們會提出疑問,既然 OSTimeDly() 函式能完成這項工作,為什麼還要使用 message queue 呢?這是因為,借助 message queue,我們可以讓其他的 task 向 message queue 發送消息來終止 A/D 取樣 task 等待消息,使其馬上執行一次 A/D 取樣。此外,我們還可以透過 message queue 來通知 A/D 取樣程式具體對哪個通道進行取樣,告訴它增加取樣頻率等等,從而使得我們的應用更智慧化。換句話說,我們可以告訴 A/D 取樣程式,"現在馬上讀取通道 3 的輸入值!" 之後,該取樣 task 將重新開始在 message queue 中等待消息,準備開始一次新的掃描過程。


src="http://f23.yahoofs.com/myper/iGDql7qQSE.gxSmpGembVQ--/blog/ap_F23_20090602055104219.jpg?TTAKUxKBl5icVuMk"
align="bottom" width="410" height="239"/>


Figure 6.11 Reading analog inputs



Using a queue as a counting semaphore


在 message queue 初始化時,可以將 message queue 中的多個指標設為非 NULL值 (如 (void *) 1),來實現計數 semaphore 的功能。這裏,初始化為非 NULL 值的指標數就是可用的資源數。系統中的 task 可以透過 OSQPend() 來請求 "semaphore",然後透過呼叫 OSQPost() 來釋放 "semaphore",如程式清單 L6.28。如果系統中只使用了計數 semaphore 和 message queue,使用這種方法可以有效地節省程式碼的空間。這時將 OS_SEM_EN 設為 0,就可以不使用 semaphore,而只使用 message queue。值得注意的是,這種方法為共用資源引入了大量的指標變數。也就是說,為了節省程式碼空間,犧牲了 RAM 空間。另外,對 message queue 的操作要比對 semaphore 的操作慢,因此,當用計數 semaphore 同步的 semaphore 很多時,這種方法的效率是非常低的。








程式清單 L6.28 Using a queue as a counting semaphore



OS_EVENT    *QSem;
void *QMsgTbl[N_RESOURCES]


void main (void)
{
OSInit();
.
.
QSem = OSQCreate(&QMsgTbl[0], N_RESOURCES);

for (i = 0; i < N_RESOURCES; i++) {
OSQPost(Qsem, (void *) 1);
}
.
.
OSTaskCreate(Task1, .., .., ..);
.
.
OSStart();
}


void Task1 (void *pdata)
{
INT8U err;


for (;;) {
OSQPend(&QSem, 0, &err); /* 得到對資源的存取權 */
.
. /* task 獲得 semaphore, 對資源進行存取 */
.
OSMQPost(QSem, (void*) 1); /* 釋放對資源的存取權 */
}
}


立你斯 發表在 痞客邦 留言(0) 人氣()