μC/OS-II的任務(wù)之間的通訊與同步
在μC/OS-II中,有多種方法可以保護(hù)任務(wù)之間的共享數(shù)據(jù)和提供任務(wù)之間的通訊。在前面的章節(jié)中,已經(jīng)講到了其中的兩種:
一是利用宏OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()來關(guān)閉中斷和打開中斷。當(dāng)兩個任務(wù)或者一個任務(wù)和一個中斷服務(wù)子程序共享某些數(shù)據(jù)時(shí),可以采用這種方法,詳見3.00節(jié)臨界段、8.03.02節(jié) OS_ENTER_CRITICAL() 和OS_EXIT_CRITICAL()及9.03.02節(jié)臨界段,OS_CPU.H;
二是利用函數(shù)OSSchedLock()和OSSchekUnlock()對μC/OS-II中的任務(wù)調(diào)度函數(shù)上鎖和開鎖。用這種方法也可以實(shí)現(xiàn)數(shù)據(jù)的共享,詳見3.06節(jié) 給調(diào)度器上鎖和開鎖。
本章將介紹另外三種用于數(shù)據(jù)共享和任務(wù)通訊的方法:信號量、郵箱和消息隊(duì)列。
圖F6.1介紹了任務(wù)和中斷服務(wù)子程序之間是如何進(jìn)行通訊的。
一個任務(wù)或者中斷服務(wù)子程序可以通過事件控制塊ECB(EventCONtrolBlocks)來向另外的任務(wù)發(fā)信號[F6.1A(1)]。這里,所有的信號都被看成是事件(Event)。這也說明為什么上面把用于通訊的數(shù)據(jù)結(jié)構(gòu)叫做事件控制塊。一個任務(wù)還可以等待另一個任務(wù)或中斷服務(wù)子程序給它發(fā)送信號[F6.1A(2)]。這里要注意的是,只有任務(wù)可以等待事件發(fā)生,中斷服務(wù)子程序是不能這樣做的。對于處于等待狀態(tài)的任務(wù),還可以給它指定一個最長等待時(shí)間,以此來防止因?yàn)榈却氖录]有發(fā)生而無限期地等下去。
多個任務(wù)可以同時(shí)等待同一個事件的發(fā)生[F6.1B]。在這種情況下,當(dāng)該事件發(fā)生后,所有等待該事件的任務(wù)中,優(yōu)先級最高的任務(wù)得到了該事件并進(jìn)入就緒狀態(tài),準(zhǔn)備執(zhí)行。上面講到的事件,可以是信號量、郵箱或者消息隊(duì)列等。當(dāng)事件控制塊是一個信號量時(shí),任務(wù)可以等待它,也可以給它發(fā)送消息。
圖6.1事件控制塊的使用
6.1 事件控制塊ECB
μC/OS-II通過uCOS_II.H中定義的OS_EVENT數(shù)據(jù)結(jié)構(gòu)來維護(hù)一個事件控制塊的所有信息[程序清單L6.1],也就是本章開篇講到的事件控制塊ECB。該結(jié)構(gòu)中除了包含了事件本身的定義,如用于信號量的計(jì)數(shù)器,用于指向郵箱的指針,以及指向消息隊(duì)列的指針數(shù)組等,還定義了等待該事件的所有任務(wù)的列表。程序清單L6.1是該數(shù)據(jù)結(jié)構(gòu)的定義。
程序清單L6.1ECB數(shù)據(jù)結(jié)構(gòu)
typedefSTruct{
void*OSEventPtr;/*指向消息或者消息隊(duì)列的指針*/
INT8UOSEventTbl[OS_EVENT_TBL_SIZE];/*等待任務(wù)列表*/
INT16UOSEventCnt;/*計(jì)數(shù)器(當(dāng)事件是信號量時(shí))*/
INT8UOSEventType;/*時(shí)間類型*/
INT8UOSEventGrp;/*等待任務(wù)所在的組*/
}OS_EVENT;
.OSEventPtr指針,只有在所定義的事件是郵箱或者消息隊(duì)列時(shí)才使用。 當(dāng)所定義的事件是郵箱時(shí),它指向一個消息,而當(dāng)所定義的事件是消息隊(duì)列時(shí),它指向一個數(shù)據(jù)結(jié)構(gòu),詳見6.06節(jié)消息郵箱和6.07節(jié)消息隊(duì)列。
.OSEventTbl[] 和 .OSEventGrp 很像前面講到的OSRdyTbl[]和OSRdyGrp,只不過前兩者包含的是等待某事件的任務(wù),而后兩者包含的是系統(tǒng)中處于就緒狀態(tài)的任務(wù)。(見3.04節(jié)就緒表)
.OSEventCnt 當(dāng)事件是一個信號量時(shí),.OSEventCnt是用于信號量的計(jì)數(shù)器,(見6.05節(jié)信號量)。
.OSEventType 定義了事件的具體類型。它可以是信號量(OS_EVENT_SEM)、郵箱
(OS_EVENT_TYPE_MBOX)或消息隊(duì)列(OS_EVENT_TYPE_Q)中的一種。用戶要根據(jù)該域的具體值
來調(diào)用相應(yīng)的系統(tǒng)函數(shù),以保證對其進(jìn)行的操作的正確性。
每個等待事件發(fā)生的任務(wù)都被加入到該事件事件控制塊中的等待任務(wù)列表中,該列表包括.OSEventGrp和.OSEventTbl[]兩個域。變量前面的[.]說明該變量是數(shù)據(jù)結(jié)構(gòu)的一個域。在這
里,所有的任務(wù)的優(yōu)先級被分成8組(每組8個優(yōu)先級),分別對應(yīng).OSEventGrp中的8位。當(dāng)
某組中有任務(wù)處于等待該事件的狀態(tài)時(shí),.OSEventGrp中對應(yīng)的位就被置位。相應(yīng)地,該任務(wù)
在.OSEventTbl[]中的對應(yīng)位也被置位。.OSEventTbl[]數(shù)組的大小由系統(tǒng)中任務(wù)的最低優(yōu)先級
決定,這個值由uCOS_II.H中的OS_LOWEST_PRIO常數(shù)定義。這樣,在任務(wù)優(yōu)先級比較少的情況
下,減少μC/OS-II對系統(tǒng)RAM的占用量。
當(dāng)一個事件發(fā)生后,該事件的等待事件列表中優(yōu)先級最高的任務(wù),也即在.OSEventTbl[]中,所有被置1的位中,優(yōu)先級代碼最小的任務(wù)得到該事件。圖F6.2給出
了.OSEventGrp和.OSEventTbl[]之間的對應(yīng)關(guān)系。該關(guān)系可以描述為:
當(dāng).OSEventTbl[0]中的任何一位為1時(shí),.OSEventGrp中的第0位為1。
當(dāng).OSEventTbl[1]中的任何一位為1時(shí),.OSEventGrp中的第1位為1。
當(dāng).OSEventTbl[2]中的任何一位為1時(shí),.OSEventGrp中的第2位為1。
當(dāng).OSEventTbl[3]中的任何一位為1時(shí),.OSEventGrp中的第3位為1。
當(dāng).OSEventTbl[4]中的任何一位為1時(shí),.OSEventGrp中的第4位為1。
當(dāng).OSEventTbl[5]中的任何一位為1時(shí),.OSEventGrp中的第5位為1。
當(dāng).OSEventTbl[6]中的任何一位為1時(shí),.OSEventGrp中的第6位為1。
當(dāng).OSEventTbl[7]中的任何一位為1時(shí),.OSEventGrp中的第7位為1。
圖F6.2事件的等待任務(wù)列表
下面的代碼將一個任務(wù)放到事件的等待任務(wù)列表中。
程序清單L6.2——將一個任務(wù)插入到事件的等待任務(wù)列表中
pevent->OSEventGrp|=OSMapTbl[prio>>3];
pevent->OSEventTbl[prio>>3]|=OSMapTbl[prio&0x07];
其中,prio是任務(wù)的優(yōu)先級,pevent是指向事件控制塊的指針。
從程序清單L6.2可以看出,插入一個任務(wù)到等待任務(wù)列表中所花的時(shí)間是相同的,和表中現(xiàn)有多少個任務(wù)無關(guān)。從圖F6.2中可以看出該算法的原理:任務(wù)優(yōu)先級的最低3位決定了該任務(wù)在相應(yīng)的.OSEventTbl[]中的位置,緊接著的3位則決定了該任務(wù)優(yōu)先級在.OSEventTbl[]中的字節(jié)索引。該算法中用到的查找表OSMapTbl[](定義在OS_CORE.C中)一般在ROM中實(shí)現(xiàn)。[!--empirenews.page--]
表T6.1OSMapTbl[]
從等待任務(wù)列表中刪除一個任務(wù)的算法則正好相反,如程序清單L6.3所示。
程序清單L6.3從等待任務(wù)列表中刪除一個任務(wù)
if((pevent->OSEventTbl[prio>>3]&=~OSMapTbl[prio&0x07])==0){
pevent->OSEventGrp&=~OSMapTbl[prio>>3];
}
該代碼清除了任務(wù)在.OSEventTbl[]中的相應(yīng)位,并且,如果其所在的組中不再有處于等待該事件的任務(wù)時(shí)(即.OSEventTbl[prio>>3]為0),將.OSEventGrp中的相應(yīng)位也清除了。和上面的由任務(wù)優(yōu)先級確定該任務(wù)在等待表中的位置的算法類似,從等待任務(wù)列表中查找處于等待狀態(tài)的最高優(yōu)先級任務(wù)的算法,也不是從.OSEventTbl[0]開始逐個查詢,而是采用了查找另一個表OSUnMapTbl[256](見文件OS_CORE.C)。這里,用于索引的8位分別代表對應(yīng)的8組中有任務(wù)處于等待狀態(tài),其中的最低位具有最高的優(yōu)先級。用這個值索引,首先得到最高優(yōu)先級任務(wù)所在的組的位置(0~7之間的一個數(shù))。然后利用.OSEventTbl[]中對應(yīng)字節(jié)再在OSUnMapTbl[]中查找,就可以得到最高優(yōu)先級任務(wù)在組中的位置(也是0~7之間的一個數(shù))。
這樣,最終就可以得到處于等待該事件狀態(tài)的最高優(yōu)先級任務(wù)了。程序清單L6.4是該算法的具體實(shí)現(xiàn)代碼。
程序清單L6.4在等待任務(wù)列表中查找最高優(yōu)先級的任務(wù)
y=OSUnMapTbl[pevent->OSEventGrp];
x=OSUnMapTbl[pevent->OSEventTbl[y]];
prio=(y<<3)+x;
舉例來說,如果.OSEventGrp的值是01101000(二進(jìn)制),而對應(yīng)的OSUnMapTbl[.OSEventGrp]值為3,說明最高優(yōu)先級任務(wù)所在的組是3。類似地,如果.OSEventTbl[3]的值是11100100(二進(jìn)制),OSUnMapTbl[.OSEventTbl[3]]的值為2,則處于等待狀態(tài)的任務(wù)的最高優(yōu)先級是3×8+2=26。
在μC/OS-II中,事件控制塊的總數(shù)由用戶所需要的信號量、郵箱和消息隊(duì)列的總數(shù)決定。
該值由OS_CFG.H中的#defineOS_MAX_EVENTS定義。 在調(diào)用OSInit()時(shí) (見3.11節(jié), μC/OS-II
的初始化),所有事件控制塊被鏈接成一個單向鏈表——空閑事件控制塊鏈表(圖F6.3)。每
當(dāng)建立一個信號量、郵箱或者消息隊(duì)列時(shí),就從該鏈表中取出一個空閑事件控制塊,并對它進(jìn)
行初始化。因?yàn)樾盘柫?、郵箱和消息隊(duì)列一旦建立就不能刪除,所以事件控制塊也不能放回到
空閑事件控制塊鏈表中。
圖F6.3空閑事件控制塊鏈表——Figure6.3
對于事件控制塊進(jìn)行的一些通用操作包括:
y 初始化一個事件控制塊
y 使一個任務(wù)進(jìn)入就緒態(tài)
y 使一個任務(wù)進(jìn)入等待該事件的狀態(tài)
y 因?yàn)榈却瑫r(shí)而使一個任務(wù)進(jìn)入就緒態(tài)
為了避免代碼重復(fù)和減短程代碼長度,μC/OS-II將上面的操作用4個系統(tǒng)函數(shù)實(shí)現(xiàn),它們是:OSEventWaitListInit(),OSEventTaskRdy(),OSEventWait()和OSEventTO()。
6.2 初始化一個事件控制塊,OSEventWaitListInit()
程序清單L6.5是函數(shù)OSEventWaitListInit()的源代碼。當(dāng)建立一個信號量、郵箱或者消息隊(duì)列時(shí),相應(yīng)的建立函數(shù)OSSemInit(),OSMboxCreate(),或者OSQCreate()通過調(diào)用
OSEventWaitListInit()對事件控制塊中的等待任務(wù)列表進(jìn)行初始化。該函數(shù)初始化一個空的等
待任務(wù)列表,其中沒有任何任務(wù)。該函數(shù)的調(diào)用參數(shù)只有一個,就是指向需要初始化的事件控
制塊的指針pevent。
程序清單L6.5初始化ECB塊的等待任務(wù)列表
voidOSEventWaitListInit(OS_EVENT*pevent)
{
INT8Ui;
pevent->OSEventGrp=0x00;
for(i=0;i
pevent->OSEventTbl[i]=0x00;
}
}
6.3 使一個任務(wù)進(jìn)入就緒態(tài),OSEventTaskRdy()
程序清單L6.6是函數(shù)OSEventTaskRdy()的源代碼。當(dāng)發(fā)生了某個事件,該事件等待任務(wù)列表中的最高優(yōu)先級任務(wù)(HighestPriorityTask–HPT)要置于就緒態(tài)時(shí),該事件對應(yīng)的OSSemPost(),OSMboxPost(),OSQPost(),和OSQPostFront()函數(shù)調(diào)用OSEventTaskRdy()實(shí)現(xiàn)該操作。換句話說,該函數(shù)從等待任務(wù)隊(duì)列中刪除HPT任務(wù)(HighestPriorityTask),并把該任務(wù)置于就緒態(tài)。圖F6.4給出了OSEventTaskRdy()函數(shù)最開始的4個動作。
該函數(shù)首先計(jì)算HPT任務(wù)在.OSEventTbl[]中的字節(jié)索引[L6.6/F6.4(1)],其結(jié)果是一個從0到OS_LOWEST_PRIO/8+1之間的數(shù),并利用該索引得到該優(yōu)先級任務(wù)在.OSEventGrp中的位屏蔽碼[L6.6/F6.4(2)](從表T6.1可以得到該值)。然后,OSEventTaskRdy()函數(shù)判斷HPT任務(wù)在.OSEventTbl[]中相應(yīng)位的位置[L6.6/F6.4(3)], 其結(jié)果是一個從0到OS_LOWEST_PRIO/8+1
之間的數(shù),以及相應(yīng)的位屏蔽碼[L6.6/F6.4(4)]。根據(jù)以上結(jié)果,OSEventTaskRdy()函數(shù)計(jì)算
出HPT任務(wù)的優(yōu)先級[L6.6(5)],然后就可以從等待任務(wù)列表中刪除該任務(wù)了[L6.6(6)]。
任務(wù)的任務(wù)控制塊中包含有需要改變的信息。知道了HPT任務(wù)的優(yōu)先級,就可以得到指向該任務(wù)的任務(wù)控制塊的指針[L6.6(7)]。因?yàn)樽罡邇?yōu)先級任務(wù)運(yùn)行條件已經(jīng)得到滿足,必須停止OSTimeTick()函數(shù)對.OSTCBDly域的遞減操作,所以O(shè)SEventTaskRdy()直接將該域清澈0[L6.6(8)]。因?yàn)樵撊蝿?wù)不再等待該事件的發(fā)生,所以O(shè)SEventTaskRdy()函數(shù)將其任務(wù)控制塊中指向事件控制塊的指針指向NULL[L6.6(9)]。如果OSEventTaskRdy()是由OSMboxPost()或者OSQPost()調(diào)用的,該函數(shù)還要將相應(yīng)的消息傳遞給HPT,放在它的任務(wù)控制塊中[L6.6(10)]。
另外,當(dāng)OSEventTaskRdy()被調(diào)用時(shí),位屏蔽碼msk作為參數(shù)傳遞給它。該參數(shù)是用于對任務(wù)控制塊中的位清零的位屏蔽碼,和所發(fā)生事件的類型相對應(yīng)[L6.6(11)]。最后,根據(jù).OSTCBStat判斷該任務(wù)是否已處于就緒狀態(tài)[L6.6(12)]。如果是,則將HPT插入到μC/OS-II的就緒任務(wù)列表中[L6.6(13)]。注意,HPT任務(wù)得到該事件后不一定進(jìn)入就緒狀態(tài),也許該任務(wù)已經(jīng)由于其它[!--empirenews.page--]
原因掛起了。[見4.07節(jié),掛起一個任務(wù),OSTaskSuspend(),和4.08節(jié),恢復(fù)一個任務(wù),
OSTaskResume()]。
另外,.OSEventTaskRdy()函數(shù)要在中斷禁止的情況下調(diào)用。
程序清單L6.6使一個任務(wù)進(jìn)入就緒狀態(tài)
voidOSEventTaskRdy(OS_EVENT*pevent,void*msg,INT8Umsk)
{
OS_TCB*ptcb;
INT8Ux;
INT8Uy;
INT8Ubitx;
INT8Ubity;
INT8Uprio;
y=OSUnMapTbl[pevent->OSEventGrp];(1)
bity=OSMapTbl[y];(2)
x=OSUnMapTbl[pevent->OSEventTbl[y]];(3)
bitx=OSMapTbl[x];(4)
prio=(INT8U)((y<<3)+x);(5)
if((pevent->OSEventTbl[y]&=~bitx)==0){(6)
pevent->OSEventGrp&=~bity;
}
ptcb=OSTCBPrioTbl[prio];(7)
ptcb->OSTCBDly=0;(8)
ptcb->OSTCBEventPtr=(OS_EVENT*)0;(9)
#if(OS_Q_EN&&(OS_MAX_QS>=2))||OS_MBOX_EN
ptcb->OSTCBMsg=msg;(10)
#else
msg=msg;
#endif
ptcb->OSTCBStat&=~msk;(11)
if(ptcb->OSTCBStat==OS_STAT_RDY){(12)
OSRdyGrp|=bity;(13)
OSRdyTbl[y]|=bitx;
}
}
圖F6.4使一個任務(wù)進(jìn)入就緒狀態(tài)——Figure6.4
6.4 使一個任務(wù)進(jìn)入等待某事件發(fā)生狀態(tài),OSEventTaskWait()
程序清單L6.7是OSEventTaskWait()函數(shù)的源代碼。當(dāng)某個任務(wù)要等待一個事件的發(fā)生時(shí),相應(yīng)事件的OSSemPend(),OSMboxPend()或者OSQPend()函數(shù)會調(diào)用該函數(shù)將當(dāng)前任務(wù)從就緒任務(wù)表中刪除,并放到相應(yīng)事件的事件控制塊的等待任務(wù)表中。
程序清單L6.7使一個任務(wù)進(jìn)入等待狀態(tài)
voidOSEventTaskWait(OS_EVENT*pevent)
{
OSTCBCur->OSTCBEventPtr=pevent;(1)
if((OSRdyTbl[OSTCBCur->OSTCBY]&=~OSTCBCur->OSTCBBitX)==0){(2)
OSRdyGrp&=~OSTCBCur->OSTCBBitY;
}
pevent->OSEventTbl[OSTCBCur->OSTCBY]|=OSTCBCur->OSTCBBitX;(3)
pevent->OSEventGrp|=OSTCBCur->OSTCBBitY;
}
在該函數(shù)中,首先將指向事件控制塊的指針放到任務(wù)的任務(wù)控制塊中[L6.7(1)],接著將任務(wù)從就緒任務(wù)表中刪除[L6.7(2)],并把該任務(wù)放到事件控制塊的等待任務(wù)表中[L6.7(3)]。
6.5 由于等待超時(shí)而將任務(wù)置為就緒態(tài),OSEventTO()
程序清單L6.8是OSEventTO()函數(shù)的源代碼。當(dāng)在預(yù)先指定的時(shí)間內(nèi)任務(wù)等待的事件沒有發(fā)生時(shí),OSTimeTick()函數(shù)會因?yàn)榈却瑫r(shí)而將任務(wù)的狀態(tài)置為就緒。在這種情況下,事件的OSSemPend(),OSMboxPend()或者OSQPend()函數(shù)會調(diào)用OSEventTO()來完成這項(xiàng)工作。該函數(shù)負(fù)責(zé)從事件控制塊中的等待任務(wù)列表里將任務(wù)刪除[L6.8(1)],并把它置成就緒狀態(tài)[L6.8(2)]。最后,從任務(wù)控制塊中將指向事件控制塊的指針刪除[L6.8(3)]。用戶應(yīng)當(dāng)注意,調(diào)用OSEventTO()也應(yīng)當(dāng)先關(guān)中斷。
程序清單L6.8因?yàn)榈却瑫r(shí)將任務(wù)置為就緒狀態(tài)
voidOSEventTO(OS_EVENT*pevent)
{
if((pevent->OSEventTbl[OSTCBCur->OSTCBY]&=~OSTCBCur->OSTCBBitX) ==0)
{ (1)
pevent->OSEventGrp&=~OSTCBCur->OSTCBBitY;
}
OSTCBCur->OSTCBStat=OS_STAT_RDY;(2)
OSTCBCur->OSTCBEventPtr=(OS_EVENT*)0;(3)
}
6.6 信號量
μC/OS-II中的信號量由兩部分組成:一個是信號量的計(jì)數(shù)值,它是一個16位的無符號整數(shù) (0到65,535之間) ; 另一個是由等待該信號量的任務(wù)組成的等待任務(wù)表。 用戶要在OS_CFG.H中將OS_SEM_EN開關(guān)量常數(shù)置成1,這樣μC/OS-II才能支持信號量。
在使用一個信號量之前, 首先要建立該信號量, 也即調(diào)用OSSemCreate()函數(shù)(見下一節(jié)) ,
對信號量的初始計(jì)數(shù)值賦值。該初始值為0到65,535之間的一個數(shù)。如果信號量是用來表示一個或者多個事件的發(fā)生, 那么該信號量的初始值應(yīng)設(shè)為0。 如果信號量是用于對共享資源的訪問,那么該信號量的初始值應(yīng)設(shè)為1(例如,把它當(dāng)作二值信號量使用)。最后,如果該信號量是用來表示允許任務(wù)訪問n個相同的資源,那么該初始值顯然應(yīng)該是n,并把該信號量作為一個可計(jì)數(shù)的信號量使用。
μC/OS-II提供了5個對信號量進(jìn)行操作的函數(shù)。它們是:OSSemCreate(),OSSemPend(),
OSSemPost(),OSSemAccept()和OSSemQuery()函數(shù)。圖F6.5說明了任務(wù)、中斷服務(wù)子程序和
信號量之間的關(guān)系。圖中用鑰匙或者旗幟的符號來表示信號量:如果信號量用于對共享資源的
訪問,那么信號量就用鑰匙符號。符號旁邊的數(shù)字N代表可用資源數(shù)。對于二值信號量,該值
就是1;如果信號量用于表示某事件的發(fā)生,那么就用旗幟符號。這時(shí)的數(shù)字N代表事件已經(jīng)發(fā)
生的次數(shù)。從圖F6.5中可以看出OSSemPost()函數(shù)可以由任務(wù)或者中斷服務(wù)子程序調(diào)用,而
OSSemPend()和OSSemQuery()函數(shù)只能有任務(wù)程序調(diào)用。
圖F6.5任務(wù)、中斷服務(wù)子程序和信號量之間的關(guān)系——Figure6.5
6.6.1 建立一個信號量,OSSemCreate()
程序清單L6.9是OSSemCreate()函數(shù)的源代碼。首先,它從空閑任務(wù)控制塊鏈表中得到一個事件控制塊[L6.9(1)],并對空閑事件控制鏈表的指針進(jìn)行適當(dāng)?shù)恼{(diào)整,使它指向下一個空閑的事件控制塊[L6.9(2)]。如果這時(shí)有任務(wù)控制塊可用[L6.9(3)],就將該任務(wù)控制塊的事件類型設(shè)置成信號量OS_EVENT_TYPE_SEM[L6.9(4)]。其它的信號量操作函數(shù)OSSem???()通過檢查該域來保證所操作的任務(wù)控制塊類型的正確。例如,這可以防止調(diào)用OSSemPost()函數(shù)對一個用作郵箱的任務(wù)控制塊進(jìn)行操作[6.06節(jié),郵箱]。接著,用信號量的初始值對任務(wù)控制塊進(jìn)行初始化[L6.9(5)],并調(diào)用 OSEventWaitListInit()函數(shù)對事件控制任務(wù)控制塊的等待任務(wù)列表進(jìn)行初始化[見6.01節(jié),初始化一個任務(wù)控制塊,OSEventWaitListInit()][L6.9(6)]。因?yàn)樾盘柫空诒怀跏蓟?,所以這時(shí)沒有任何任務(wù)等待該信號量。最后,OSSemCreate()返回給調(diào)用函數(shù)一個指向任務(wù)控制塊的指針。以后對信號量的所有操作,如OSSemPend(),OSSemPost(),OSSemAccept()和OSSemQuery()都是通過該指針完成的。因此,這個指針實(shí)際上就是該信號量的句柄。如果系統(tǒng)中沒有可用的任務(wù)控制塊,OSSemCreate()將返回一個NULL指針。[!--empirenews.page--]
值得注意的是,在μC/OS-II中,信號量一旦建立就不能刪除了,因此也就不可能將一個已分配的任務(wù)控制塊再放回到空閑ECB鏈表中。如果有任務(wù)正在等待某個信號量,或者某任務(wù)的運(yùn)行依賴于某信號量的出現(xiàn)時(shí),刪除該任務(wù)是很危險(xiǎn)的。
程序清單L6.9建立一個信號量
OS_EVENT*OSSemCreate(INT16Ucnt)
{
OS_EVENT*pevent;
OS_ENTER_CRITICAL();
pevent=OSEventFreeList;(1)
if(OSEventFreeList!=(OS_EVENT*)0){(2)
OSEventFreeList=(OS_EVENT*)OSEventFreeList->OSEventPtr;
}
OS_EXIT_CRITICAL();
if(pevent!=(OS_EVENT*)0){(3)
pevent->OSEventType=OS_EVENT_TYPE_SEM;(4)
pevent->OSEventCnt=cnt;(5)
OSEventWaitListInit(pevent);(6)
}
return(pevent);(7)
}
6.6.2 等待一個信號量,OSSemPend()
程序清單L6.10是OSSemPend()函數(shù)的源代碼。它首先檢查指針pevent所指的任務(wù)控制塊是否是由OSSemCreate()建立的[L6.10(1)]。如果信號量當(dāng)前是可用的(信號量的計(jì)數(shù)值大于0)[L6.10(2)],將信號量的計(jì)數(shù)值減1[L6.10(3)],然后函數(shù)將“無錯”錯誤代碼返回給它的調(diào)用函數(shù)。顯然,如果正在等待信號量,這時(shí)的輸出正是我們所希望的,也是運(yùn)行OSSemPend()函數(shù)最快的路徑。
如果此時(shí)信號量無效(計(jì)數(shù)器的值是0),OSSemPend()函數(shù)要進(jìn)一步檢查它的調(diào)用函數(shù)是不是中斷服務(wù)子程序[L6.10(4)]。在正常情況下,中斷服務(wù)子程序是不會調(diào)用OSSemPend()函數(shù)的。這里加入這些代碼,只是為了以防萬一。當(dāng)然,在信號量有效的情況下,即使是中斷服務(wù)
子程序調(diào)用的OSSemPend(),函數(shù)也會成功返回,不會出任何錯誤。
如果信號量的計(jì)數(shù)值為0,而OSSemPend()函數(shù)又不是由中斷服務(wù)子程序調(diào)用的,則調(diào)
用OSSemPend()函數(shù)的任務(wù)要進(jìn)入睡眠狀態(tài),等待另一個任務(wù)(或者中斷服務(wù)子程序)發(fā)出該信
號量(見下節(jié))。OSSemPend()允許用戶定義一個最長等待時(shí)間作為它的參數(shù),這樣可以避免該
任務(wù)無休止地等待下去。如果該參數(shù)值是一個大于0的值,那么該任務(wù)將一直等到信號有效或
者等待超時(shí)。如果該參數(shù)值為0,該任務(wù)將一直等待下去。OSSemPend()函數(shù)通過將任務(wù)控制塊
中的狀態(tài)標(biāo)志.OSTCBStat置1,把任務(wù)置于睡眠狀態(tài)[L6.10(5)],等待時(shí)間也同時(shí)置入任務(wù)控
制塊中[L6.10(6)],該值在OSTimeTick()函數(shù)中被逐次遞減。注意,OSTimeTick()函數(shù)對每個
任務(wù)的任務(wù)控制塊的.OSTCBDly域做遞減操作(只要該域不為0)[見3.10節(jié),時(shí)鐘節(jié)拍]。真
正將任務(wù)置入睡眠狀態(tài)的操作在OSEventTaskWait()函數(shù)中執(zhí)行[見6.03節(jié),讓一個任務(wù)等待
某個事件,OSEventTaskWait()][L6.10(7)]。
因?yàn)楫?dāng)前任務(wù)已經(jīng)不是就緒態(tài)了,所以任務(wù)調(diào)度函數(shù)將下一個最高優(yōu)先級的任務(wù)調(diào)入,準(zhǔn)備運(yùn)行[L6.10(8)]。當(dāng)信號量有效或者等待時(shí)間到后,調(diào)用OSSemPend()函數(shù)的任務(wù)將再一次成為最高優(yōu)先級任務(wù)。這時(shí)OSSched()函數(shù)返回。這之后,OSSemPend()要檢查任務(wù)控制塊中的狀態(tài)標(biāo)志,看該任務(wù)是否仍處于等待信號量的狀態(tài)[L6.10(9)]。如果是,說明該任務(wù)還沒有被OSSemPost()函數(shù)發(fā)出的信號量喚醒。事實(shí)上,該任務(wù)是因?yàn)榈却瑫r(shí)而由TimeTick()函數(shù)把它置為就緒狀態(tài)的。這種情況下,OSSemPend()函數(shù)調(diào)用 OSEventTO()函數(shù)將任務(wù)從等待任務(wù)列表中刪除[L6.10(10)],并返回給它的調(diào)用任務(wù)一個“超時(shí)”的錯誤代碼。如果任務(wù)的任務(wù)控制塊中的OS_STAT_SEM標(biāo)志位沒有置位,就認(rèn)為調(diào)用 OSSemPend()的任務(wù)已經(jīng)得到了該信號量,將指向信號量ECB的指針從該任務(wù)的任務(wù)控制塊中刪除,并返回給調(diào)用函數(shù)一個“無錯”的錯誤代碼[L6.10(11)]。
程序清單L6.10等待一個信號量
voidOSSemPend(OS_EVENT*pevent,INT16Utimeout,INT8U*err)
{
OS_ENTER_CRITICAL();
if(pevent->OSEventType!=OS_EVENT_TYPE_SEM){(1)
OS_EXIT_CRITICAL();
*err=OS_ERR_EVENT_TYPE;
}
if(pevent->OSEventCnt>0){(2)
pevent->OSEventCnt--;(3)
OS_EXIT_CRITICAL();
*err=OS_NO_ERR;
}elseif(OSIntNesting>0){(4)
OS_EXIT_CRITICAL();
*err=OS_ERR_PEND_ISR;
}else{
OSTCBCur->OSTCBStat|=OS_STAT_SEM;(5)
OSTCBCur->OSTCBDly=timeout;(6)
OSEventTaskWait(pevent);(7)
OS_EXIT_CRITICAL();
OSSched();(8)
OS_ENTER_CRITICAL();
if(OSTCBCur->OSTCBStat&OS_STAT_SEM){(9)
OSEventTO(pevent);(10)
OS_EXIT_CRITICAL();
*err=OS_TIMEOUT;
}else{
OSTCBCur->OSTCBEventPtr=(OS_EVENT*)0;(11)
OS_EXIT_CRITICAL();
*err=OS_NO_ERR;
}
}
}
6.6.3 發(fā)送一個信號量,OSSemPost()
程序清單L6.11是OSSemPost()函數(shù)的源代碼。它首先檢查參數(shù)指針pevent指向的任務(wù)控制塊是否是OSSemCreate()函數(shù)建立的[L6.11(1)],接著檢查是否有任務(wù)在等待該信號量[L6.11(2)]。如果該任務(wù)控制塊中的.OSEventGrp域不是0,說明有任務(wù)正在等待該信號量。這時(shí),就要調(diào)用函數(shù)OSEventTaskRdy()[見6.02節(jié),使一個任務(wù)進(jìn)入就緒狀態(tài),OSEventTaskRdy()],把其中的最高優(yōu)先級任務(wù)從等待任務(wù)列表中刪除[L6.11(3)]并使它進(jìn)入就緒狀態(tài)。然后,調(diào)用OSSched()任務(wù)調(diào)度函數(shù)檢查該任務(wù)是否是系統(tǒng)中的最高優(yōu)先級的就緒任務(wù)[L6.11(4)]。如果是,這時(shí)就要進(jìn)行任務(wù)切換[當(dāng)OSSemPost()函數(shù)是在任務(wù)中調(diào)用的],準(zhǔn)備執(zhí)行該就緒任務(wù)。如果不是,OSSched()直接返回,調(diào)用 OSSemPost()的任務(wù)得以繼續(xù)執(zhí)行。如果這時(shí)沒有任務(wù)在等待該信號量,該信號量的計(jì)數(shù)值就簡單地加1[L6.11(5)]。
上面是由任務(wù)調(diào)用OSSemPost()時(shí)的情況。當(dāng)中斷服務(wù)子程序調(diào)用該函數(shù)時(shí),不會發(fā)生上面的任務(wù)切換。如果需要,任務(wù)切換要等到中斷嵌套的最外層中斷服務(wù)子程序調(diào)用OSIntExit()函數(shù)后才能進(jìn)行(見3.09節(jié),μC/OS-II中的中斷)。[!--empirenews.page--]
程序清單L6.11發(fā)出一個信號量
INT8UOSSemPost(OS_EVENT*pevent)
{
OS_ENTER_CRITICAL();
if(pevent->OSEventType!=OS_EVENT_TYPE_SEM){(1)
OS_EXIT_CRITICAL();
return(OS_ERR_EVENT_TYPE);
}
if(pevent->OSEventGrp){(2)
OSEventTaskRdy(pevent,(void*)0,OS_STAT_SEM);(3)
OS_EXIT_CRITICAL();
OSSched();(4)
return(OS_NO_ERR);
}else{
if(pevent->OSEventCnt<65535){
pevent->OSEventCnt++;(5)
OS_EXIT_CRITICAL();
return(OS_NO_ERR);
}else{
OS_EXIT_CRITICAL();
return(OS_SEM_OVF);
}
}
}
6.6.4 無等待地請求一個信號量,OSSemAccept()
當(dāng)一個任務(wù)請求一個信號量時(shí),如果該信號量暫時(shí)無效,也可以讓該任務(wù)簡單地返回,而不是進(jìn)入睡眠等待狀態(tài)。這種情況下的操作是由OSSemAccept()函數(shù)完成的,其源代碼見程序清單L6.12。該函數(shù)在最開始也是檢查參數(shù)指針pevent指向的事件控制塊是否是由OSSemCreate()函數(shù)建立的[L6.12(1)],接著從該信號量的事件控制塊中取出當(dāng)前計(jì)數(shù)值[L6.12(2)],并檢查該信號量是否有效(計(jì)數(shù)值是否為非0值)[L6.12(3)]。如果有效,則將信號量的計(jì)數(shù)值減1[L6.12(4)],然后將信號量的原有計(jì)數(shù)值返回給調(diào)用函數(shù)[L6.12(5)]。調(diào)用函數(shù)需要對該返回值進(jìn)行檢查。如果該值是0,說明該信號量無效。如果該值大于0,說明該信號量有效,同時(shí)該值也暗示著該信號量當(dāng)前可用的資源數(shù)。應(yīng)該注意的是,這些可用資源中,已經(jīng)被該調(diào)用函數(shù)自身占用了一個(該計(jì)數(shù)值已經(jīng)被減1)。中斷服務(wù)子程序要請求信號量時(shí),只能用OSSemAccept()而不能用OSSemPend(),因?yàn)橹袛喾?wù)子程序是不允許等待的。
程序清單L6.12無等待地請求一個信號量
INT16UOSSemAccept(OS_EVENT*pevent)
{
INT16Ucnt;
OS_ENTER_CRITICAL();
if(pevent->OSEventType!=OS_EVENT_TYPE_SEM){(1)
OS_EXIT_CRITICAL();
return(0);
}
cnt=pevent->OSEventCnt;(2)
if(cnt>0){(3)
pevent->OSEventCnt--;(4)
}
OS_EXIT_CRITICAL();
return(cnt);(5)
}
6.6.5 查詢一個信號量的當(dāng)前狀態(tài),OSSemQuery()
在應(yīng)用程序中,用戶隨時(shí)可以調(diào)用函數(shù)OSSemQuery()[程序清單L6.13]來查詢一個信號量的當(dāng)前狀態(tài)。該函數(shù)有兩個參數(shù):一個是指向信號量對應(yīng)事件控制塊的指針pevent。該指針是在生產(chǎn)信號量時(shí),由OSSemCreate()函數(shù)返回的;另一個是指向用于記錄信號量信息的數(shù)據(jù)結(jié)構(gòu)OS_SEM_DATA(見uCOS_II.H)的指針pdata。因此,調(diào)用該函數(shù)前,用戶必須先定義該結(jié)構(gòu)變量,用于存儲信號量的有關(guān)信息。在這里,之所以使用一個新的數(shù)據(jù)結(jié)構(gòu)的原因在于,調(diào)用函數(shù)應(yīng)該只關(guān)心那些和特定信號量有關(guān)的信息, 而不是象OS_EVENT數(shù)據(jù)結(jié)構(gòu)包含的很全面的信息。
該數(shù)據(jù)結(jié)構(gòu)只包含信號量計(jì)數(shù)值.OSCnt和等待任務(wù)列表.OSEventTbl[]、.OSEventGrp,而OS_EVENT中還包含了另外的兩個域.OSEventType和.OSEventPtr。
和其它與信號量有關(guān)的函數(shù)一樣,OSSemQuery()也是先檢查pevent指向的事件控制塊是否是OSSemCreate()產(chǎn)生的[L6.13(1)],然后將等待任務(wù)列表[L6.13(2)]和計(jì)數(shù)值[L6.13(3)]
從OS_EVENT結(jié)構(gòu)拷貝到OS_SEM_DATA結(jié)構(gòu)變量中去。
程序清單L6.13查詢一個信號量的狀態(tài)
INT8UOSSemQuery(OS_EVENT*pevent,OS_SEM_DATA*pdata)
{
INT8Ui;
INT8U*psrc;
INT8U*pdest;
OS_ENTER_CRITICAL();
if(pevent->OSEventType!=OS_EVENT_TYPE_SEM){(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
*pdest++=*psrc++;
}
pdata->OSCnt=pevent->OSEventCnt;(3)
OS_EXIT_CRITICAL();
return(OS_NO_ERR);
}
6.7 郵箱
郵箱是μC/OS-II中另一種通訊機(jī)制, 它可以使一個任務(wù)或者中斷服務(wù)子程序向另一個任務(wù)發(fā)送一個指針型的變量。該指針指向一個包含了特定“消息”的數(shù)據(jù)結(jié)構(gòu)。為了在μC/OS-II中使用郵箱,必須將OS_CFG.H中的OS_MBOX_EN常數(shù)置為1。
使用郵箱之前,必須先建立該郵箱。該操作可以通過調(diào)用OSMboxCreate()函數(shù)來完成(見下節(jié)),并且要指定指針的初始值。一般情況下,這個初始值是NULL,但也可以初始化一個郵
箱,使其在最開始就包含一條消息。如果使用郵箱的目的是用來通知一個事件的發(fā)生(發(fā)送一
條消息),那么就要初始化該郵箱為NULL,因?yàn)樵陂_始時(shí),事件還沒有發(fā)生。如果用戶用郵箱
來共享某些資源,那么就要初始化該郵箱為一個非NULL的指針。在這種情況下,郵箱被當(dāng)成一
個二值信號量使用。
μC/OS-II提供了5種對郵箱的操作:OSMboxCreate(),OSMboxPend(),OSMboxPost(),
OSMboxAccept()和 OSMboxQuery()函數(shù)。圖F6.6描述了任務(wù)、中斷服務(wù)子程序和郵箱之間的關(guān)
系,這里用符號“I”表示郵箱。郵箱包含的內(nèi)容是一個指向一條消息的指針。一個郵箱只能包
含一個這樣的指針(郵箱為滿時(shí)),或者一個指向NULL的指針(郵箱為空時(shí))。從圖F6.6可以看出,任務(wù)或者中斷服務(wù)子程序可以調(diào)用函數(shù)OSMboxPost(),但是只有任務(wù)可以調(diào)用函數(shù)OSMboxPend()和OSMboxQuery()。
圖F6.6任務(wù)、中斷服務(wù)子程序和郵箱之間的關(guān)系
6.7.1 建立一個郵箱,OSMboxCreate()
程序清單L6.14是OSMboxCreate()函數(shù)的源代碼,基本上和函數(shù)OSSemCreate()相似。不同之處在于事件控制塊的類型被設(shè)置成OS_EVENT_TYPE_MBOX[L6.14(1)], 以及使用.OSEventPtr域來容納消息指針,而不是使用.OSEventCnt域[L6.14(2)]。[!--empirenews.page--]
OSMboxCreate()函數(shù)的返回值是一個指向事件控制塊的指針[L6.14(3)]。這個指針在調(diào)用函數(shù)OSMboxPend(),OSMboxPost(),OSMboxAccept()和OSMboxQuery()時(shí)使用。因此,該指針可以看作是對應(yīng)郵箱的句柄。值得注意的是,如果系統(tǒng)中已經(jīng)沒有事件控制塊可用,函數(shù)OSMboxCreate()將返回一個NULL指針。
郵箱一旦建立,是不能被刪除的。比如,如果有任務(wù)正在等待一個郵箱的信息,這時(shí)刪除該郵箱,將有可能產(chǎn)生災(zāi)難性的后果。
程序清單L6.14建立一個郵箱
OS_EVENT*OSMboxCreate(void*msg)
{
OS_EVENT*pevent;
OS_ENTER_CRITICAL();
pevent=OSEventFreeList;
if(OSEventFreeList!=(OS_EVENT*)0){
OSEventFreeList=(OS_EVENT*)OSEventFreeList->OSEventPtr;
}
OS_EXIT_CRITICAL();
if(pevent!=(OS_EVENT*)0){
pevent->OSEventType=OS_EVENT_TYPE_MBOX;(1)
pevent->OSEventPtr=msg;(2)
OSEventWaitListInit(pevent);
}
return(pevent);(3)
}
6.7.2 等待一個郵箱中的消息,OSMboxPend()
程序清單L6.15是OSMboxPend()函數(shù)的源代碼。 同樣, 它和OSSemPend()也很相似,因此,在這里只講述其中的不同之處。OSMboxPend()首先檢查該事件控制塊是由 OSMboxCreate()函數(shù)建立的[L6.15(1)]。當(dāng).OSEventPtr域是一個非NULL的指針時(shí),說明該郵箱中有可用的消息[L6.15(2)]。這種情況下,OSMboxPend()函數(shù)將該域的值復(fù)制到局部變量msg中,然后將.OSEventPtr置為NULL[L6.15(3)]。這正是我們所期望的,也是執(zhí)行 OSMboxPend()函數(shù)最快的路徑。
如果此時(shí)郵箱中沒有消息是可用的(.OSEventPtr域是NULL指針),OSMboxPend()函數(shù)檢查它的調(diào)用者是否是中斷服務(wù)子程序[L6.15(4)]。象OSSemPend()函數(shù)一樣,不能在中斷服務(wù)子程序中調(diào)用OSMboxPend(),因?yàn)橹袛喾?wù)子程序是不能等待的。這里的代碼同樣是為了以防萬一。但是,如果郵箱中有可用的消息,即使從中斷服務(wù)子程序中調(diào)用OSMboxPend()函數(shù),也一樣是成功的。
如果郵箱中沒有可用的消息,OSMboxPend()的調(diào)用任務(wù)就被掛起,直到郵箱中有了消息或者等待超時(shí)[L6.15(5)]。當(dāng)有其它的任務(wù)向該郵箱發(fā)送了消息后(或者等待時(shí)間超時(shí)),這時(shí),該任務(wù)再一次成為最高優(yōu)先級任務(wù),OSSched()返回。這時(shí),OSMboxPend()函數(shù)要檢查是否有消息被放到該任務(wù)的任務(wù)控制塊中[L6.15(6)]。如果有,那么該次函數(shù)調(diào)用成功,對應(yīng)的消息被返回到調(diào)用函數(shù)。
程序清單L6.15等待一個郵箱中的消息
void*OSMboxPend(OS_EVENT*pevent,INT16Utimeout,INT8U*err)
{
void*msg;
OS_ENTER_CRITICAL();
if(pevent->OSEventType!=OS_EVENT_TYPE_MBOX){(1)
OS_EXIT_CRITICAL();
*err=OS_ERR_EVENT_TYPE;
return((void*)0);
}
msg=pevent->OSEventPtr;
if(msg!=(void*)0){(2)
pevent->OSEventPtr=(void*)0;(3)
OS_EXIT_CRITICAL();
*err=OS_NO_ERR;
}elseif(OSIntNesting>0){(4)
OS_EXIT_CRITICAL();
*err=OS_ERR_PEND_ISR;
}else{
OSTCBCur->OSTCBStat|=OS_STAT_MBOX;(5)
OSTCBCur->OSTCBDly=timeout;
OSEventTaskWait(pevent);
OS_EXIT_CRITICAL();
OSSched();
OS_ENTER_CRITICAL();
if((msg=OSTCBCur->OSTCBMsg)!=(void*)0){(6)
OSTCBCur->OSTCBMsg=(void*)0;
OSTCBCur->OSTCBStat=OS_STAT_RDY;
OSTCBCur->OSTCBEventPtr=(OS_EVENT*)0;
OS_EXIT_CRITICAL();
*err=OS_NO_ERR;
}elseif(OSTCBCur->OSTCBStat&OS_STAT_MBOX){(7)
OSEventTO(pevent);(8)
OS_EXIT_CRITICAL();
msg=(void*)0;(9)
*err=OS_TIMEOUT;
}else{
msg=pevent->OSEventPtr;(10)
pevent->OSEventPtr=(void*)0;
(11)
OSTCBCur->OSTCBEventPtr=(OS_EVENT*)0;(12)
OS_EXIT_CRITICAL();
*err=OS_NO_ERR;
}
}
return(msg);
}
在OSMboxPend()函數(shù)中,通過檢查任務(wù)控制塊中的.OSTCBStat域中的OS_STAT_MBOX位,可以知道是否等待超時(shí)。如果該域被置1,說明任務(wù)等待已經(jīng)超時(shí)[L6.15(7)]。這時(shí),通過調(diào)用函數(shù)OSEventTo()可以將任務(wù)從郵箱的等待列表中刪除[L6.15(8)]。因?yàn)榇藭r(shí)郵箱中沒有消息,所以返回的指針是NULL[L6.15(9)]。如果OS_STAT_MBOX位沒有被置1,說明所等待的消息已經(jīng)被發(fā)出。OSMboxPend()的調(diào)用函數(shù)得到指向消息的指針[L6.15(10)]。此后,OSMboxPend()函數(shù)通過將郵箱事件控制塊的.OSEventPtr域置為NULL清空該郵箱,并且要將任務(wù)任務(wù)控制塊中指向郵箱事件控制塊的指針刪除[L6.15(12)]。
6.7.3 發(fā)送一個消息到郵箱中,OSMboxPost()
程序清單L6.16是OSMboxPost()函數(shù)的源代碼。檢查了事件控制塊是否是一個郵箱后[L6.16(1)],OSMboxPost()函數(shù)還要檢查是否有任務(wù)在等待該郵箱中的消息[L6.16(2)]。如果事件控制塊中的OSEventGrp域包含非零值,就暗示著有任務(wù)在等待該消息。這時(shí),調(diào)用OSEventTaskRdy()將其中的最高優(yōu)先級任務(wù)從等待列表中刪除[見6.02節(jié),使一個任務(wù)進(jìn)入就緒狀態(tài),OSEventTaskRdy()][L6.16(3)],加入系統(tǒng)的就緒任務(wù)列表中,準(zhǔn)備運(yùn)行。然后,調(diào)用OSSched()函數(shù)[L6.16(4)],檢查該任務(wù)是否是系統(tǒng)中最高優(yōu)先級的就緒任務(wù)。如果是,執(zhí)行任
務(wù)切換[僅當(dāng)OSMboxPost()函數(shù)是由任務(wù)調(diào)用時(shí)],該任務(wù)得以執(zhí)行。如果該任務(wù)不是最高優(yōu)先
級的任務(wù),OSSched()返回,OSMboxPost()的調(diào)用函數(shù)繼續(xù)執(zhí)行。如果沒有任何任務(wù)等待該消息,
指向消息的指針就被保存到郵箱中[L6.16(6)](假設(shè)此時(shí)郵箱中的指針不是非NULL的[L6.16(5)])。這樣,下一個調(diào)用OSMboxPend()函數(shù)的任務(wù)就可以立刻得到該消息了。
注意,如果OSMboxPost()函數(shù)是從中斷服務(wù)子程序中調(diào)用的,那么,這時(shí)并不發(fā)生上下文的切換。如果需要,中斷服務(wù)子程序引起的上下文切換只發(fā)生在中斷嵌套的最外層中斷服務(wù)子程序?qū)SIntExit()函數(shù)的調(diào)用時(shí)(見3.09節(jié),μC/OS-II中的中斷)。[!--empirenews.page--]
程序清單L6.16向郵箱中發(fā)送一條消息
INT8UOSMboxPost(OS_EVENT*pevent,void*msg)
{
OS_ENTER_CRITICAL();
if(pevent->OSEventType!=OS_EVENT_TYPE_MBOX){(1)
OS_EXIT_CRITICAL();
return(OS_ERR_EVENT_TYPE);
}
if(pevent->OSEventGrp){(2)
OSEventTaskRdy(pevent,msg,OS_STAT_MBOX);(3)
OS_EXIT_CRITICAL();
OSSched();(4)
return(OS_NO_ERR);
}else{
if(pevent->OSEventPtr!=(void*)0){(5)
OS_EXIT_CRITICAL();
return(OS_MBOX_FULL);
}else{
pevent->OSEventPtr=msg;(6)
OS_EXIT_CRITICAL();
return(OS_NO_ERR);
}
}
}
6.7.4 無等待地從郵箱中得到一個消息,OSMboxAccept()
應(yīng)用程序也可以以無等待的方式從郵箱中得到消息。這可以通過程序清單L6.17中的OSMboxAccept()函數(shù)來實(shí)現(xiàn)。OSMboxAccept()函數(shù)開始也是檢查事件控制塊是否是由OSMboxCreate()函數(shù)建立的 [L6.17(1)]。接著,它得到郵箱中的當(dāng)前內(nèi)容[L6.17(2)],并判斷是否有消息是可用的[L6.17(3)]。如果郵箱中有消息,就把郵箱清空[L6.17(4)],而郵箱中原來指向消息的指針被返回給OSMboxAccept()的調(diào)用函數(shù)[L6.17(5)]。OSMboxAccept()函數(shù)的調(diào)用函數(shù)必須檢查該返回值是否為NULL。如果該值是NULL,說明郵箱是空的,沒有可用的消息。
如果該值是非NULL值,說明郵箱中有消息可用,而且該調(diào)用函數(shù)已經(jīng)得到了該消息。中斷服務(wù)子程序在試圖得到一個消息時(shí), 應(yīng)該使用OSMboxAccept()函數(shù), 而不能使用OSMboxPend()函數(shù)。
OSMboxAccept()函數(shù)的另一個用途是,用戶可以用它來清空一個郵箱中現(xiàn)有的內(nèi)容。
程序清單L6.17無等待地從郵箱中得到消息
void*OSMboxAccept(OS_EVENT*pevent)
{
void*msg;
OS_ENTER_CRITICAL();
if(pevent->OSEventType!=OS_EVENT_TYPE_MBOX){(1)
OS_EXIT_CRITICAL();
return((void*)0);
}
msg=pevent->OSEventPtr;(2)
if(msg!=(void*)0){(3)
pevent->OSEventPtr=(void*)0;(4)
}
OS_EXIT_CRITICAL();
return(msg);(5)
}
6.7.5 查詢一個郵箱的狀態(tài),OSMboxQuery()
OSMboxQuery()函數(shù)使應(yīng)用程序可以隨時(shí)查詢一個郵箱的當(dāng)前狀態(tài)。程序清單L6.18是該函數(shù)的源代碼。它需要兩個參數(shù):一個是指向郵箱的指針pevent。該指針是在建立該郵箱時(shí),由OSMboxCreate()函數(shù)返回的;另一個是指向用來保存有關(guān)郵箱的信息的OS_MBOX_DATA(見
uCOS_II.H)數(shù)據(jù)結(jié)構(gòu)的指針pdata。在調(diào)用OSMboxCreate()函數(shù)之前,必須先定義該結(jié)構(gòu)變量,
用來保存有關(guān)郵箱的信息。之所以定義一個新的數(shù)據(jù)結(jié)構(gòu),是因?yàn)檫@里關(guān)心的只是和特定郵箱
有關(guān)的內(nèi)容,而非整個OS_EVENT數(shù)據(jù)結(jié)構(gòu)的內(nèi)容。后者還包含了另外兩個域
(.OSEventCnt和.OSEventType),而OS_MBOX_DATA只包含郵箱中的消息指針(.OSMsg)和該郵箱現(xiàn)有的等待任務(wù)列表(.OSEventTbl[]和.OSEventGrp)。
和前面的所以函數(shù)一樣,該函數(shù)也是先檢查事件控制是否是郵箱[L6.18(1)]。然后,將郵箱中的等待任務(wù)列表[L6.18(2)]和郵箱中的消息[L6.18(3)]從OS_EVENT數(shù)據(jù)結(jié)構(gòu)復(fù)制到OS_MBOX_DATA數(shù)據(jù)結(jié)構(gòu)。
程序清單L6.18查詢郵箱的狀態(tài)
INT8UOSMboxQuery(OS_EVENT*pevent,OS_MBOX_DATA*pdata)
{
INT8Ui;
INT8U*psrc;
INT8U*pdest;
OS_ENTER_CRITICAL();
if(pevent->OSEventType!=OS_EVENT_TYPE_MBOX){(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
*pdest++=*psrc++;
}
pdata->OSMsg=pevent->OSEventPtr;(3)
OS_EXIT_CRITICAL();
return(OS_NO_ERR);
}
6.7.6 用郵箱作二值信號量
一個郵箱可以被用作二值的信號量。 首先, 在初始化時(shí), 將郵箱設(shè)置為一個非零的指針(如
void*1)。這樣,一個任務(wù)可以調(diào)用OSMboxPend()函數(shù)來請求一個信號量,然后通過調(diào)用
OSMboxPost()函數(shù)來釋放一個信號量。程序清單L6.19說明了這個過程是如何工作的。如果用
戶只需要二值信號量和郵箱,這樣做可以節(jié)省代碼空間。這時(shí)可以將OS_SEM_EN設(shè)置為0,只使
用郵箱就可以了。
程序清單L6.19使用郵箱作為二值信號量
OS_EVENT*MboxSem;
voIDTask1(void*pdata)
{
INT8Uerr;
for(;;){
OSMboxPend(MboxSem,0,&err);/*獲得對資源的訪問權(quán)*/
.
./*任務(wù)獲得信號量,對資源進(jìn)行訪問*/
.
OSMboxPost(MboxSem,(void*)1);/*釋放對資源的訪問權(quán)*/
}
}
6.7.7 用郵箱實(shí)現(xiàn)延時(shí),而不使用OSTimeDly()
郵箱的等待超時(shí)功能可以被用來模仿OSTimeDly()函數(shù)的延時(shí),如程序清單L6.20所示。
如果在指定的時(shí)間段TIMEOUT內(nèi),沒有消息到來,Task1()函數(shù)將繼續(xù)執(zhí)行。這和OSTimeDly(TIMEOUT)功能很相似。但是,如果Task2()在指定的時(shí)間結(jié)束之前,向該郵箱發(fā)送了一個“啞”消息,Task1()就會提前開始繼續(xù)執(zhí)行。這和調(diào)用OSTimeDlyResume()函數(shù)的功能是一樣的。注意,這里忽略了對返回的消息的檢查,因?yàn)榇藭r(shí)關(guān)心的不是得到了什么樣的消息。
程序清單L6.20使用郵箱實(shí)現(xiàn)延時(shí)
OS_EVENT*MboxTimeDly;
voidTask1(void*pdata)
{
INT8Uerr;
for(;;){
OSMboxPend(MboxTimeDly, TIMEOUT,&err);/*延時(shí)該任務(wù)*/
.
./*延時(shí)結(jié)束后執(zhí)行的代碼*/
.
}
}
voidTask2(void*pdata)[!--empirenews.page--]
{
INT8Uerr;
for(;;){
OSMboxPost(MboxTimeDly,(void*)1);/*取消任務(wù)1的延時(shí)*/
.
.
}
}
6.8 消息隊(duì)列
消息隊(duì)列是μC/OS-II中另一種通訊機(jī)制, 它可以使一個任務(wù)或者中斷服務(wù)子程序向另一個任務(wù)發(fā)送以指針方式定義的變量。因具體的應(yīng)用有所不同,每個指針指向的數(shù)據(jù)結(jié)構(gòu)變量也有所不同。為了使用μC/OS-II的消息隊(duì)列功能,需要在OS_CFG.H文件中,將OS_Q_EN常數(shù)設(shè)置為1,并且通過常數(shù)OS_MAX_QS來決定μC/OS-II支持的最多消息隊(duì)列數(shù)。
在使用一個消息隊(duì)列之前, 必須先建立該消息隊(duì)列。 這可以通過調(diào)用OSQCreate()函數(shù) (見
6.07.01節(jié)),并定義消息隊(duì)列中的單元數(shù)(消息數(shù))來完成。
μC/OS-II提供了7個對消息隊(duì)列進(jìn)行操作的函數(shù):OSQCreate(),OSQPend(),OSQPost(),
OSQPostFront(),OSQAccept(),OSQFlush()和OSQQuery()函數(shù)。圖F6.7是任務(wù)、中斷服務(wù)子程序和消息隊(duì)列之間的關(guān)系。其中,消息隊(duì)列的符號很像多個郵箱。實(shí)際上,我們可以將消息隊(duì)列看作時(shí)多個郵箱組成的數(shù)組,只是它們共用一個等待任務(wù)列表。每個指針?biāo)赶虻臄?shù)據(jù)結(jié)構(gòu)是由具體的應(yīng)用程序決定的。N代表了消息隊(duì)列中的總單元數(shù)。當(dāng)調(diào)用OSQPend()或者OSQAccept()之前,調(diào)用N次OSQPost()或者OSQPostFront()就會把消息隊(duì)列填滿。從圖F6.7中可以看出,一個任務(wù)或者中斷服務(wù)子程序可以調(diào)用OSQPost(),OSQPostFront(),OSQFlush()或者OSQAccept()函數(shù)。但是,只有任務(wù)可以調(diào)用OSQPend()和OSQQuery()函數(shù)。
圖F6.7任務(wù)、中斷服務(wù)子程序和消息隊(duì)列之間的關(guān)系——Figure6.7
圖F6.8是實(shí)現(xiàn)消息隊(duì)列所需要的各種數(shù)據(jù)結(jié)構(gòu)。這里也需要事件控制塊來記錄等待任務(wù)列表[F6.8(1)],而且,事件控制塊可以使多個消息隊(duì)列的操作和信號量操作、郵箱操作相同的代碼。當(dāng)建立了一個消息隊(duì)列時(shí),一個隊(duì)列控制塊(OS_Q結(jié)構(gòu),見OS_Q.C文件)也同時(shí)被建立,并通過OS_EVENT中的.OSEventPtr域鏈接到對應(yīng)的事件控制塊[F6.8(2)]。 在建立一個消息隊(duì)列之前,必須先定義一個含有與消息隊(duì)列最大消息數(shù)相同個數(shù)的指針數(shù)組[F6.8(3)]。數(shù)組的起始地址以及數(shù)組中的元素?cái)?shù)作為參數(shù)傳遞給OSQCreate()函數(shù)。事實(shí)上,如果內(nèi)存占用了連續(xù)的地址空間,也沒有必要非得使用指針數(shù)組結(jié)構(gòu)。
文件OS_CFG.H中的常數(shù)OS_MAX_QS定義了在μC/OS-II中可以使用的最大消息隊(duì)列數(shù),這個值最小應(yīng)為2。μC/OS-II在初始化時(shí)建立一個空閑的隊(duì)列控制塊鏈表,如圖F6.9所示。
圖F6.8用于消息隊(duì)列的數(shù)據(jù)結(jié)構(gòu)——Figure6.8
圖F6.9空閑隊(duì)列控制塊鏈表——Figure6.9
隊(duì)列控制塊是一個用于維護(hù)消息隊(duì)列信息的數(shù)據(jù)結(jié)構(gòu),它包含了以下的一些域。這里,仍然在各個變量前加入一個[.]來表示它們是數(shù)據(jù)結(jié)構(gòu)中的一個域。
.OSQPtr在空閑隊(duì)列控制塊中鏈接所有的隊(duì)列控制塊。一旦建立了消息隊(duì)列,該域就不再有用了。
.OSQStart是指向消息隊(duì)列的指針數(shù)組的起始地址的指針。用戶應(yīng)用程序在使用消息隊(duì)列之前必須先定義該數(shù)組。
.OSQEnd是指向消息隊(duì)列結(jié)束單元的下一個地址的指針。該指針使得消息隊(duì)列構(gòu)成一個循環(huán)的緩沖區(qū)。
.OSQIn 是指向消息隊(duì)列中插入下一條消息的位置的指針。當(dāng).OSQIn和.OSQEnd相等時(shí),.OSQIn被調(diào)整指向消息隊(duì)列的起始單元。
.OSQOut 是指向消息隊(duì)列中下一個取出消息的位置的指針。當(dāng).OSQOut和.OSQEnd相等時(shí),.OSQOut被調(diào)整指向消息隊(duì)列的起始單元。
.OSQSize 是消息隊(duì)列中總的單元數(shù)。該值是在建立消息隊(duì)列時(shí)由用戶應(yīng)用程序決定的。在μC/OS-II中,該值最大可以是65,535。
.OSQEntries 是消息隊(duì)列中當(dāng)前的消息數(shù)量。當(dāng)消息隊(duì)列是空的時(shí),該值為0。當(dāng)消息隊(duì)列滿了以后,該值和.OSQSize值一樣。在消息隊(duì)列剛剛建立時(shí),該值為0。
消息隊(duì)列最根本的部分是一個循環(huán)緩沖區(qū),如圖F6.10。其中的每個單元包含一個指針。
隊(duì)列未滿時(shí),.OSQIn[F6.10(1)]指向下一個存放消息的地址單元。如果隊(duì)列已滿(.OSQEntries
與.OSQSize相等),.OSQIn[F6.10(3)]則與.OSQOut指向同一單元。如果在.OSQIn指向的單元
插入新的指向消息的指針,就構(gòu)成 FIFO(First-In-First-Out)隊(duì)列。相反,如果在.OSQOut
指向的單元的下一個單元插入新的指針,就構(gòu)成LIFO隊(duì)列(Last-In-First-Out)[F6.10(2)]。
當(dāng).OSQEntries和.OSQSize相等時(shí),說明隊(duì)列已滿。消息指針總是從.OSQOut[F6.10(4)]指向
的單元取出。指針.OSQStart和.OSQEnd [F6.10(5)]定義了消息指針數(shù)組的頭尾,以便在.OSQIn
和.OSQOut到達(dá)隊(duì)列的邊緣時(shí),進(jìn)行邊界檢查和必要的指針調(diào)整,實(shí)現(xiàn)循環(huán)功能。
圖F6.10消息隊(duì)列是一個由指針組成的循環(huán)緩沖區(qū)——Figure6.10
6.8.1 建立一個消息隊(duì)列,OSQCreate()
程序清單L6.21是OSQCreate()函數(shù)的源代碼。該函數(shù)需要一個指針數(shù)組來容納指向各個消息的指針。該指針數(shù)組必須聲名為void類型。
OSQCreate()首先從空閑事件控制塊鏈表中取得一個事件控制塊(見圖F6.3)[L6.21(1)],并對剩下的空閑事件控制塊列表的指針做相應(yīng)的調(diào)整,使它指向下一個空閑事件控制塊[L6.21(2)]。 接著, OSQCreate()函數(shù)從空閑隊(duì)列控制塊列表中取出一個隊(duì)列控制塊[L6.21(3)]。
如果有空閑隊(duì)列控制塊是可以的,就對其進(jìn)行初始化[L6.21(4)]。然后該函數(shù)將事件控制塊的類型設(shè)置為OS_EVENT_TYPE_Q[L6.21(5)],并使其.OSEventPtr指針指向隊(duì)列控制塊[L6.21(6)]。OSQCreate()還要調(diào)用OSEventWaitListInit()函數(shù)對事件控制塊的等待任務(wù)列表初始化[見6.01節(jié),初始化一個事件控制塊,OSEventWaitListInit()][L6.21(7)]。因?yàn)榇藭r(shí)消息隊(duì)列正在初始化,顯然它的等待任務(wù)列表是空的。最后,OSQCreate()向它的調(diào)用函數(shù)返回一個指向事件控制塊的指針[L6.21(9)]。該指針將在調(diào)用OSQPend(),OSQPost(),OSQPostFront(),OSQFlush(),OSQAccept()和OSQQuery()等消息隊(duì)列處理函數(shù)時(shí)使用。因此,該指針可以被看作是對應(yīng)消息隊(duì)列的句柄。值得注意的是,如果此時(shí)沒有空閑的事件控制塊,OSQCreate()函數(shù)將返回一個NULL指針。如果沒有隊(duì)列控制塊可以使用,為了不浪費(fèi)事件控制塊資源,OSQCreate()函數(shù)將把剛剛?cè)〉玫氖录刂茐K重新返還給空閑事件控制塊列表[L6.21(8)]。[!--empirenews.page--]
另外,消息隊(duì)列一旦建立就不能再刪除了。試想,如果有任務(wù)正在等待某個消息隊(duì)列中的消息,而此時(shí)又刪除該消息隊(duì)列,將是很危險(xiǎn)的。
程序清單L6.21建立一個消息隊(duì)列
OS_EVENT*OSQCreate(void**start,INT16Usize)
{
OS_EVENT*pevent;
OS_Q*pq;
OS_ENTER_CRITICAL();
pevent=OSEventFreeList;(1)
if(OSEventFreeList!=(OS_EVENT*)0){
OSEventFreeList=(OS_EVENT*)OSEventFreeList->OSEventPtr;(2)
}
OS_EXIT_CRITICAL();
if(pevent!=(OS_EVENT*)0){
OS_ENTER_CRITICAL();
pq=OSQFreeList;(3)
if(OSQFreeList!=(OS_Q*)0){
OSQFreeList=OSQFreeList->OSQPtr;
}
OS_EXIT_CRITICAL();
if(pq!=(OS_Q*)0){
pq->OSQStart=start;(4)
pq->OSQEnd=&start[size];
pq->OSQIn=start;
pq->OSQOut=start;
pq->OSQSize=size;
pq->OSQEntries=0;
pevent->OSEventType=OS_EVENT_TYPE_Q;(5)
pevent->OSEventPtr=pq;(6)
OSEventWaitListInit(pevent);(7)
}else{
OS_ENTER_CRITICAL();
pevent->OSEventPtr=(void*)OSEventFreeList;(8)
OSEventFreeList=pevent;
OS_EXIT_CRITICAL();
pevent=(OS_EVENT*)0;
}
}
return(pevent);(9)
}
6.8.2 等待一個消息隊(duì)列中的消息,OSQPend()
程序清單L6.22是OSQPend()函數(shù)的源代碼。OSQPend()函數(shù)首先檢查事件控制塊是否是由OSQCreate()函數(shù)建立的[L6.22(1)],接著,該函數(shù)檢查消息隊(duì)列中是否有消息可用(即.OSQEntries是否大于0) [L6.22(2)]。 如果有, OSQPend()函數(shù)將指向消息的指針復(fù)制到msg變量中, 并讓.OSQOut指針指向隊(duì)列中的下一個單元[L6.22(3)],然后將隊(duì)列中的有效消息數(shù)減1[L6.22(4)]。因?yàn)橄㈥?duì)列是一個循環(huán)的緩沖區(qū),OSQPend()函數(shù)需要檢查.OSQOut是否超過了
隊(duì)列中的最后一個單元[L6.22(5)]。當(dāng)發(fā)生這種越界時(shí),就要將.OSQOut重新調(diào)整到指向隊(duì)列的起始單元[L6.22(6)]。這是我們調(diào)用OSQPend()函數(shù)時(shí)所期望的,也是執(zhí)行OSQPend()函數(shù)最快的路徑。
程序清單L6.22在一個消息隊(duì)列中等待一條消息
void*OSQPend(OS_EVENT*pevent,INT16Utimeout,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;
}elseif(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;
}elseif(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)
}
如果這時(shí)消息隊(duì)列中沒有消息(.OSEventEntries是0),OSQPend()函數(shù)檢查它的調(diào)用者是否是中斷服務(wù)子程序[L6.22(7)]。象OSSemPend()和OSMboxPend()函數(shù)一樣,不能在中斷服務(wù)子程序中調(diào)用OSQPend(), 因?yàn)橹袛喾?wù)子程序是不能等待的。 但是, 如果消息隊(duì)列中有消息,即使從中斷服務(wù)子程序中調(diào)用OSQPend()函數(shù),也一樣是成功的。
如果消息隊(duì)列中沒有消息,調(diào)用OSQPend()函數(shù)的任務(wù)被掛起[L6.22(8)]。當(dāng)有其它的任
務(wù)向該消息隊(duì)列發(fā)送了消息或者等待時(shí)間超時(shí),并且該任務(wù)成為最高優(yōu)先級任務(wù)時(shí),OSSched()
返回[L6.22(9)]。這時(shí),OSQPend()要檢查是否有消息被放到該任務(wù)的任務(wù)控制塊中[L6.22(10)]。如果有,那么該次函數(shù)調(diào)用成功,把任務(wù)的任務(wù)控制塊中指向消息隊(duì)列的指針刪除[L6.22(17)],并將對應(yīng)的消息被返回到調(diào)用函數(shù)[L6.22(17)]。
在OSQPend()函數(shù)中,通過檢查任務(wù)的任務(wù)控制塊中的.OSTCBStat域,可以知道是否等到時(shí)間超時(shí)。如果其對應(yīng)的OS_STAT_Q位被置1,說明任務(wù)等待已經(jīng)超時(shí)[L6.22(12)]。這時(shí),通過調(diào)用函數(shù)OSEventTo()可以將任務(wù)從消息隊(duì)列的等待任務(wù)列表中刪除[L6.22(13)]。這時(shí),因?yàn)橄㈥?duì)列中沒有消息,所以返回的指針是NULL[L6.22(14)]。
如果任務(wù)控制塊標(biāo)志位中的OS_STAT_Q位沒有被置1,說明有任務(wù)發(fā)出了一條消息。
OSQPend()函數(shù)從隊(duì)列中取出該消息[L6.22(15)]。然后,將任務(wù)的任務(wù)控制中指向事件控制塊的指針刪除[L6.22(16)]。
6.8.3 向消息隊(duì)列發(fā)送一個消息(FIFO),OSQPost()
程序清單L6.23是OSQPost()函數(shù)的源代碼。在確認(rèn)事件控制塊是消息隊(duì)列后
[L6.23(1)],OSQPost()函數(shù)檢查是否有任務(wù)在等待該消息隊(duì)列中的消息[L6.23(2)]。當(dāng)事件控[!--empirenews.page--]
制塊的.OSEventGrp域?yàn)榉?值時(shí),說明該消息隊(duì)列的等待任務(wù)列表中有任務(wù)。這時(shí),調(diào)用
OSEventTaskRdy()函數(shù)[見6.02節(jié),使一個任務(wù)進(jìn)入就緒狀態(tài),OSEventTaskRdy()]從列表中取出最高優(yōu)先級的任務(wù)[L6.23(3)], 并將它置于就緒狀態(tài)。 然后調(diào)用函數(shù)OSSched()[L6.23(4)]進(jìn)行任務(wù)的調(diào)度。如果上面取出的任務(wù)的優(yōu)先級在整個系統(tǒng)就緒的任務(wù)里也是最高的,而且OSQPost()函數(shù)不是中斷服務(wù)子程序調(diào)用的,就執(zhí)行任務(wù)切換,該最高優(yōu)先級任務(wù)被執(zhí)行。否則的話,OSSched()函數(shù)直接返回,調(diào)用 OSQPost()函數(shù)的任務(wù)繼續(xù)執(zhí)行。
程序清單L6.23向消息隊(duì)列發(fā)送一條消息
INT8UOSQPost(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);
}
}
如果沒有任務(wù)等待該消息隊(duì)列中的消息,而且此時(shí)消息隊(duì)列未滿[L6.23(5)],指向該消息的指針被插入到消息隊(duì)列中[L6.23(6)]。這樣,下一個調(diào)用OSQPend()函數(shù)的任務(wù)就可以馬上得到該消息。注意,如果此時(shí)消息隊(duì)列已滿,那么該消息將由于不能插入到消息隊(duì)列中而丟失。
此外,如果OSQPost()函數(shù)是由中斷服務(wù)子程序調(diào)用的,那么即使產(chǎn)生了更高優(yōu)先級的任務(wù),也不會在調(diào)用OSSched()函數(shù)時(shí)發(fā)生任務(wù)切換。這個動作一直要等到中斷嵌套的最外層中斷服務(wù)子程序調(diào)用OSIntExit()函數(shù)時(shí)才能進(jìn)行(見3.09節(jié),μC/OS-II中的中斷)。
6.8.4 向消息隊(duì)列發(fā)送一個消息(后進(jìn)先出LIFO),OSQPostFront()
OSQPostFront()函數(shù)和OSQPost()基本上是一樣的, 只是在插入新的消息到消息隊(duì)列中時(shí),使用.OSQOut作為指向下一個插入消息的單元的指針,而不是.OSQIn。程序清單L6.24是它的源代碼。值得注意的是,.OSQOut指針指向的是已經(jīng)插入了消息指針的單元,所以再插入新的消
息指針前,必須先將.OSQOut指針在消息隊(duì)列中前移一個單元。如果.OSQOut指針指向的當(dāng)前單
元是隊(duì)列中的第一個單元[L6.24(1)],這時(shí)再前移就會發(fā)生越界,需要特別地將該指針指向隊(duì)
列的末尾[L6.24(2)]。由于.OSQEnd指向的是消息隊(duì)列中最后一個單元的下一個單元,因此.OSQOut必須被調(diào)整到指向隊(duì)列的有效范圍內(nèi)[L6.24(3)]。因?yàn)镼SQPend()函數(shù)取出的消息是
由OSQPend()函數(shù)剛剛插入的,因此OSQPostFront()函數(shù)實(shí)現(xiàn)了一個LIFO隊(duì)列。
程序清單L6.24向消息隊(duì)列發(fā)送一條消息(LIFO)
INT8UOSQPostFront(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);
}
}
6.8.5 無等待地從一個消息隊(duì)列中取得消息,OSQAccept()
如果試圖從消息隊(duì)列中取出一條消息,而此時(shí)消息隊(duì)列又為空時(shí),也可以不讓調(diào)用任務(wù)等待而直接返回調(diào)用函數(shù)。這個操作可以調(diào)用OSQAccept()函數(shù)來完成。程序清單L6.25是該函數(shù)的源代碼。 OSQAccept()函數(shù)首先查看pevent指向的事件控制塊是否是由OSQCreate()函數(shù)建立的[L6.25(1)],然后它檢查當(dāng)前消息隊(duì)列中是否有消息[L6.25(2)]。如果消息隊(duì)列中有至少一條消息,那么就從.OSQOut指向的單元中取出消息[L6.25(3)]。OSQAccept()函數(shù)的調(diào)用函數(shù)需要對OSQAccept()返回的指針進(jìn)行檢查。如果該指針是NULL值,說明消息隊(duì)列是空的,其中沒有消息可以[L6.25(4)]。否則的話,說明已經(jīng)從消息隊(duì)列中成功地取得了一條消息。當(dāng)中斷服務(wù)子程序要從消息隊(duì)列中取消息時(shí), 必須使用OSQAccept()函數(shù), 而不能使用OSQPend()函數(shù)。
程序清單L6.25無等待地從消息隊(duì)列中取一條消息
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);
}
6.8.6 清空一個消息隊(duì)列,OSQFlush()
OSQFlush()函數(shù)允許用戶刪除一個消息隊(duì)列中的所有消息,重新開始使用。程序清單L6.26是該函數(shù)的源代碼。和前面的其它函數(shù)一樣,該函數(shù)首先檢查pevent指針是否是執(zhí)行一個消息隊(duì)列[L6.26(1)],然后將隊(duì)列的插入指針和取出指針復(fù)位,使它們都指向隊(duì)列起始單元,同時(shí),將隊(duì)列中的消息數(shù)設(shè)為0[L6.26(2)]。這里,沒有檢查該消息隊(duì)列的等待任務(wù)列表是否為空,因?yàn)橹灰摰却蝿?wù)列表不空,.OSQEntries就一定是0。唯一不同的是,指針.OSQIn和.OSQOut此時(shí)可以指向消息隊(duì)列中的任何單元,不一定是起始單元。[!--empirenews.page--]
程序清單L6.26清空消息隊(duì)列
INT8UOSQFlush(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);
}
6.8.7 查詢一個消息隊(duì)列的狀態(tài),OSQQuery()
OSQQuery()函數(shù)使用戶可以查詢一個消息隊(duì)列的當(dāng)前狀態(tài)。 程序清單L6.27是該函數(shù)的源代碼。OSQQuery()需要兩個參數(shù):一個是指向消息隊(duì)列的指針pevent。它是在建立一個消息隊(duì)列時(shí),由OSQCreate()函數(shù)返回的;另一個是指向OS_Q_DATA(見uCOS_II.H)數(shù)據(jù)結(jié)構(gòu)的指針pdata。該結(jié)構(gòu)包含了有關(guān)消息隊(duì)列的信息。在調(diào)用OSQQuery()函數(shù)之前,必須先定義該數(shù)據(jù)結(jié)構(gòu)變量。OS_Q_DATA結(jié)構(gòu)包含下面的幾個域:
.OSMsg 如果消息隊(duì)列中有消息,它包含指針.OSQOut所指向的隊(duì)列單元中的內(nèi)容。如果隊(duì)列是空的,.OSMsg包含一個NULL指針。
.OSNMsgs是消息隊(duì)列中的消息數(shù)(.OSQEntries的拷貝)。
.OSQSize 是消息隊(duì)列的總的容量
.OSEventTbl[]和.OSEventGrp是消息隊(duì)列的等待任務(wù)列表。通過它們,OSQQuery()的調(diào)用函數(shù)可以得到等待該消息隊(duì)列中的消息的任務(wù)總數(shù)。
OSQQuery()函數(shù)首先檢查pevent指針指向的事件控制塊是一個消息隊(duì)列[L6.27(1)],然后復(fù)制等待任務(wù)列表[L6.27(2)]。如果消息隊(duì)列中有消息[L6.27(3)],.OSQOut指向的隊(duì)列單元中的內(nèi)容被復(fù)制到OS_Q_DATA結(jié)構(gòu)中[L6.27(4)], 否則的話, 就復(fù)制一個NULL指針[L6.27(5)]。
最后,復(fù)制消息隊(duì)列中的消息數(shù)和消息隊(duì)列的容量大小[L6.27(6)]。
程序清單L6.27程序消息隊(duì)列的狀態(tài)
INT8UOSQQuery(OS_EVENT*pevent,OS_Q_DATA*pdata)
{
OS_Q*pq;
INT8Ui;
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
*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);
}
6.8.8 使用消息隊(duì)列讀取模擬量的值
在控制系統(tǒng)中,經(jīng)常要頻繁地讀取模擬量的值。這時(shí),可以先建立一個定時(shí)任務(wù)OSTimeDly()[見5.00節(jié),延時(shí)一個任務(wù),OSTimeDly()],并且給出希望的抽樣周期。然后,如圖F6.11所示,讓A/D采樣的任務(wù)從一個消息隊(duì)列中等待消息。該程序最長的等待時(shí)間就是抽樣周期。當(dāng)沒有其它任務(wù)向該消息隊(duì)列中發(fā)送消息時(shí),A/D采樣任務(wù)因?yàn)榈却瑫r(shí)而退出等待狀態(tài)并進(jìn)行執(zhí)行。這就模仿了OSTimeDly()函數(shù)的功能。
也許,讀者會提出疑問,既然OSTimeDly()函數(shù)能完成這項(xiàng)工作,為什么還要使用消息隊(duì)列呢?這是因?yàn)椋柚㈥?duì)列,我們可以讓其它的任務(wù)向消息隊(duì)列發(fā)送消息來終止A/D采樣任務(wù)等待消息,使其馬上執(zhí)行一次A/D采樣。此外,我們還可以通過消息隊(duì)列來通知A/D采樣程序具體對哪個通道進(jìn)行采樣,告訴它增加采樣頻率等等,從而使得我們的應(yīng)用更智能化。換句話說,我們可以告訴A/D采樣程序,“現(xiàn)在馬上讀取通道3的輸入值!”之后,該采樣任務(wù)將重新開始在消息隊(duì)列中等待消息,準(zhǔn)備開始一次新的掃描過程。
圖F6.11讀模擬量輸入——Figure6.11
6.8.9 使用一個消息隊(duì)列作為計(jì)數(shù)信號量
在消息隊(duì)列初始化時(shí),可以將消息隊(duì)列中的多個指針設(shè)為非NULL值(如void*1),來實(shí)現(xiàn)計(jì)數(shù)信號量的功能。這里,初始化為非NULL值的指針數(shù)就是可用的資源數(shù)。系統(tǒng)中的任務(wù)可以通過OSQPend()來請求“信號量”,然后通過調(diào)用OSQPost()來釋放“信號量”,如程序清單L6.28。如果系統(tǒng)中只使用了計(jì)數(shù)信號量和消息隊(duì)列,使用這種方法可以有效地節(jié)省代碼空間。
這時(shí)將OS_SEM_EN設(shè)為0,就可以不使用信號量,而只使用消息隊(duì)列。值得注意的是,這種方法
為共享資源引入了大量的指針變量。也就是說,為了節(jié)省代碼空間,犧牲了RAM空間。另外,
對消息隊(duì)列的操作要比對信號量的操作慢,因此,當(dāng)用計(jì)數(shù)信號量同步的信號量很多時(shí),這種
方法的效率是非常低的。
程序清單L6.28使用消息隊(duì)列作為一個計(jì)數(shù)信號量
OS_EVENT*QSem;
void*QMsgTbl[N_RESOURCES]
voidmain(void)
{
OSInit();
.
QSem=OSQCreate(&QMsgTbl[0],N_RESOURCES);
for(i=0;i
OSQPost(Qsem,(void*)1);
}
.
.
OSTaskCreate(Task1,..,..,..);
.
.
OSStart();
}
voidTask1(void*pdata)
{
INT8Uerr;
for(;;){
OSQPend(&QSem,0,&err);/*得到對資源的訪問權(quán)*/
.
./*任務(wù)獲得信號量,對資源進(jìn)行訪問*/
.
OSMQPost(QSem,(void*)1);/*釋放對資源的訪問權(quán)*/
}
}