UCOSii(四)——任務(wù)的通信與同步
一、任務(wù)的通信方式
1.1 共享內(nèi)存
進(jìn)程間的通信方式有兩種,一種是使用共享內(nèi)存,這種方式基本不依賴OS,也沒(méi)有相應(yīng)的系統(tǒng)開(kāi)銷。另一種則需要OS支持,通過(guò)建立鏈接器實(shí)現(xiàn)任務(wù)間的通信。
Message Passing Share Memory
依賴內(nèi)核,需要預(yù)先建立Link,內(nèi)核負(fù)擔(dān)開(kāi)銷 無(wú)需預(yù)先建立Link,用戶進(jìn)程負(fù)責(zé)開(kāi)銷
只有建立鏈接的雙方才可以通信 所有進(jìn)程都可以訪問(wèn)
需提供Link Creation、Link Capacity、 Message Lost等機(jī)制 需要提供互斥存取
兩種通信方式的區(qū)別
在UCOSii中,多個(gè)任務(wù)使用同一塊內(nèi)存區(qū)域需要提供一種互斥存取的方法。否則該段共享數(shù)據(jù)很有可能在被訪問(wèn)前就被其他任務(wù)重置了。
利用關(guān)中斷宏OS_ENTER_CRITICAL()、OS_EXIT_CRITICAL()以及開(kāi)調(diào)度鎖是利用函數(shù) OSSchedLock()、 OSSchekUnlock()可以實(shí)現(xiàn)單任務(wù)對(duì)某一資源的暫時(shí)性獨(dú)享。
用這種方法實(shí)現(xiàn)數(shù)據(jù)共享存在很大的局限性,一個(gè)簡(jiǎn)單的例子,當(dāng)一個(gè)共享資源允許被多個(gè)任務(wù)同時(shí)占用,這種方式就很低效。
RTOS會(huì)提供信號(hào)量、郵箱和消息隊(duì)列來(lái)支持任務(wù)間的通信與同步,即使是非實(shí)時(shí)性操作系統(tǒng)也同樣有這樣的接口,這已經(jīng)類似于一種規(guī)范。
1.2 信號(hào)量
信號(hào)量的概念最初由Edsger Dijkstra提出。
假定有多個(gè)任務(wù)需要讀寫(xiě)一塊板卡上的flash芯片,如果他們之間沒(méi)有協(xié)商,而是各自單獨(dú)對(duì)flash進(jìn)行讀寫(xiě),就會(huì)使flash的讀寫(xiě)操作處于不可預(yù)料的狀態(tài)中。此時(shí)可以建立一個(gè)信號(hào)量,當(dāng)有任務(wù)進(jìn)行讀寫(xiě)操作時(shí),便申請(qǐng)?jiān)撔盘?hào)量,操作完成后再釋放。如果已經(jīng)有任務(wù)占用了該信號(hào)量,請(qǐng)求信號(hào)量就會(huì)失敗,任務(wù)可以等待該信號(hào)量被釋放,再進(jìn)行相關(guān)的操作。一個(gè)共享資源也可能最多被幾個(gè)任務(wù)占用,這種情況下信號(hào)量可以為一個(gè)計(jì)數(shù)器,當(dāng)有任務(wù)占用共享資源時(shí),信號(hào)量減一。
1.3 郵箱
很明顯,信號(hào)量只解決了共享資源的占用的問(wèn)題。它不能傳遞信息,假如某下位機(jī)有一個(gè)專門(mén)用來(lái)解釋上位機(jī)控制命令的任務(wù),當(dāng)上位機(jī)沒(méi)有數(shù)據(jù)傳送過(guò)來(lái)時(shí),該任務(wù)處于掛起狀態(tài)。但當(dāng)通訊中斷發(fā)生后,該任務(wù)不僅要知道已經(jīng)有控制字被傳送過(guò)來(lái),還需要知道該控制字是什么。所以信號(hào)量在這里并不適用,解決的辦法是建立一個(gè)郵箱。由于要傳遞的信息很可能超過(guò)一個(gè)常規(guī)變量的大小,所以郵箱的內(nèi)容是一個(gè)指針。將命令解釋器的優(yōu)先級(jí)設(shè)置為高于其他任務(wù),它始終調(diào)等待一個(gè)郵箱。將該郵箱里指針的值指向接收緩沖區(qū),該任務(wù)就開(kāi)始處理控制字。
1.4 消息隊(duì)列
消息隊(duì)列是一組指針,它可以被看成是一組郵箱的集合。
假設(shè)有一個(gè)流水線分揀系統(tǒng),傳感器會(huì)檢測(cè)貨物的一些物理參數(shù)。每來(lái)一個(gè)產(chǎn)品,系統(tǒng)就建立一個(gè)關(guān)于該產(chǎn)品的結(jié)構(gòu)體用于描述該產(chǎn)品的屬性。如果使用郵箱,那么在一個(gè)產(chǎn)品被分揀任務(wù)處理前,新到產(chǎn)品就必須延時(shí)處理。使用消息隊(duì)列,可以將一組指針推入隊(duì)列,每一個(gè)指針都描述一個(gè)產(chǎn)品。這樣分揀任務(wù)可以根據(jù)先后入隊(duì)列的順序,依次分揀每個(gè)產(chǎn)品。
二、事件控制塊
2.1 Ecb的結(jié)構(gòu)
事件控制塊Ecb用來(lái)維護(hù)一個(gè)事件控制塊的所有信息,該結(jié)構(gòu)體不僅包含信號(hào)量/郵箱/消息隊(duì)列的值,還包含等待它的任務(wù)列表。
Ecb反映了一種樸素的簡(jiǎn)化程序邏輯結(jié)構(gòu)的思想。用統(tǒng)一的數(shù)據(jù)結(jié)構(gòu)來(lái)描述對(duì)象的屬性,再在處理程序里統(tǒng)一處理。對(duì)信號(hào)量/郵箱/消息隊(duì)列的創(chuàng)建、維護(hù)都只是讀寫(xiě)Ecb,在調(diào)度程序里,統(tǒng)一處理Ecb。
Ecb原型
typedef struct {
void *OSEventPtr; /* 指向消息或者消息隊(duì)列的指針 */
INT8U OSEventTbl[OS_EVENT_TBL_SIZE]; /* 等待任務(wù)列表 */
INT16U OSEventCnt; /* 計(jì)數(shù)器(當(dāng)事件是信號(hào)量時(shí)) */
INT8U OSEventType; /* 事件類型 */
INT8U OSEventGrp; /* 等待任務(wù)所在的組 */
} OS_EVENT;
OSEventType表示事件類型,信號(hào)量/郵箱/消息隊(duì)列。
如果事件是信號(hào)量,OSEventCnt表示信號(hào)量的值。
如果事件是郵箱或者消息隊(duì)列,OSEventPtr表示指向消息或者消息隊(duì)列的指針。
OSEventTbl和OSEventGrp用來(lái)表示等待該事件的任務(wù)組。
2.2 Ecb通用操作
為了減少代碼量,UCOSii將一些與Event有關(guān)的,會(huì)被重用的操作寫(xiě)成了獨(dú)立的函數(shù)。
初始化Ecb
當(dāng)Ecb被建立時(shí),需要對(duì)其進(jìn)行清0,防止該段Ram里已經(jīng)被寫(xiě)入了值。
使一個(gè)任務(wù)就緒態(tài)
使用OSSemPost(),OSMboxPost(),OSQPost(),和 OSQPostFront()發(fā)送一個(gè)事件后,事件等待列表中優(yōu)先級(jí)最高的任務(wù)要被置于就緒狀態(tài),這時(shí)調(diào)用OSEventTaskRdy()函數(shù)。
該函數(shù)被用來(lái)使等待事件發(fā)生的最高優(yōu)先級(jí)任務(wù)得到該事件后,使他進(jìn)入就緒狀態(tài)。
因?yàn)閁cosii強(qiáng)制所有任務(wù)不同優(yōu)先級(jí),所以可以通過(guò)任務(wù)優(yōu)先級(jí)取得Tcb的指針。然后該函數(shù)將OSTCBDly和OSTCBEventPtr結(jié)束任務(wù)延時(shí),標(biāo)志事件結(jié)束,再將事件的相應(yīng)參數(shù)(信號(hào)量的指,消息和隊(duì)列指針的值)傳遞給Tcb。最后,該函數(shù)還要判斷該任務(wù)是否因?yàn)檎{(diào)用OSTaskSuspend()而被掛機(jī)。如果沒(méi)有,則進(jìn)入就緒隊(duì)列。
OSTaskSuspend()是一個(gè)額外的機(jī)制,凡是調(diào)用OSTaskSuspend()被掛機(jī)的任務(wù),只能調(diào)用OSTaskResume()來(lái)進(jìn)行恢復(fù)。
使一個(gè)任務(wù)等待某事件
當(dāng)使用OSSemPend(),OSMboxPend()或者 OSQPend()函數(shù)讓某個(gè)任務(wù)等待某事件時(shí),要調(diào)用OSEventTaskWait()函數(shù)。
該函數(shù)將任務(wù)從就緒列表中刪除,再加入Ecb中的等待列表。
使一個(gè)任務(wù)因等待時(shí)間超時(shí)而進(jìn)入就緒狀態(tài)
當(dāng)使用SSemPend(),OSMboxPend()或者 OSQPend()讓任務(wù)等待某個(gè)事件,而等待事件超時(shí)后,將調(diào)用OSTimeTick()函數(shù)。
該函數(shù)清除Ecb等待列表里的任務(wù),被將Tcb里標(biāo)記事件的指針清0。
三、相關(guān)函數(shù)
3.1 信號(hào)量
信號(hào)量的建立,OSSemCreate()
初始化一個(gè)信號(hào)量要從劃給Ecb的內(nèi)存里申請(qǐng)一塊空閑區(qū)域,然后初始化一個(gè)Ecb,并將Ecb里的事件類型標(biāo)記為信號(hào)量。
這個(gè)函數(shù)會(huì)返回一個(gè)指向Ecb塊的指針,之后對(duì)信號(hào)量的操作都要通過(guò)這個(gè)指針來(lái)實(shí)現(xiàn)。
等待一個(gè)信號(hào)量,OSSemPend()
OSSemPend()先判斷事件的類型,如果是信號(hào)量且值不為0。就把信號(hào)量的值減一,函數(shù)返回請(qǐng)求成功的標(biāo)志。
在信號(hào)量被占用時(shí),任務(wù)將被掛起。只有當(dāng)?shù)却瑫r(shí),或者得到該信號(hào)量時(shí),任務(wù)才被喚醒。
發(fā)送一個(gè)信號(hào)量,OSSemPost()
OSSemPost()同樣先判斷參數(shù)里的指針是否指向一個(gè)信號(hào)量的Ecb。
如果有任務(wù)正在等待該信號(hào)量,那么把等待列表里優(yōu)先級(jí)最高的任務(wù)移除,讓它就緒。
否則就把信號(hào)量加1。
無(wú)等待地請(qǐng)求一個(gè)信號(hào)量,OSSemAccept()
OSSemAccept()用來(lái)在中斷程序中請(qǐng)求信號(hào)量,其實(shí)它只是取得當(dāng)前信號(hào)量的值。
如果當(dāng)前信號(hào)量的值不為0,那么OSSemAccept()會(huì)在取得該值后,將計(jì)數(shù)器減1。
查詢信號(hào)量狀態(tài),OSSemQuery()
OSSemQuery()返回就緒列表和信號(hào)量的值,在查詢之前要先建立一個(gè)接收返回值的結(jié)構(gòu)體。因?yàn)闊o(wú)需包含Ecb中的類型和消息指針,所以該結(jié)構(gòu)體類型和Ecb是不同的。
3.2 郵箱
創(chuàng)建一個(gè)郵箱,OSMboxCreate()
和OSSemCreate()的區(qū)別是,Ecb的類型為郵箱,并且郵箱的初始值會(huì)被寫(xiě)成參數(shù)傳遞來(lái)的值。
等待一個(gè)郵箱中的消息,OSMboxPend()
和OSSemPend()的區(qū)別是,OSMboxPend()判斷該指針是否是NULL,不是就取得該指針,否則就掛起開(kāi)始等待該郵箱。
發(fā)送一個(gè)消息到郵箱中,OSMboxPost()
和Sem的Post函數(shù)區(qū)別是,如果郵箱已經(jīng)有非空指針存在里面,函數(shù)會(huì)返回郵箱滿了的錯(cuò)誤。
無(wú)等待地從郵箱中得到一個(gè)消息, OSMboxAccept()
清空郵箱,并且返回郵箱里的指針。
查詢一個(gè)郵箱的狀態(tài), OSMboxQuery()
返回Ecb的部分內(nèi)容,不包含類型和信號(hào)量計(jì)數(shù)器。
3.3 消息隊(duì)列
UCOSii里針對(duì)消息隊(duì)列的函數(shù)有7個(gè)。分別是OSQCreate(),OSQPend(),OSQPost(),OSQPostFront(),OSQAccept(),OSQFlush()和 OSQQuery()函數(shù)
其中大部分和信號(hào)量/郵箱的函數(shù)在思想上是一致的。因?yàn)橄㈥?duì)列是一個(gè)循環(huán)隊(duì)列的結(jié)構(gòu),因此在推送消息的時(shí)候,存在先進(jìn)先出和后進(jìn)先出的問(wèn)題。即新消息到來(lái)時(shí),到底是插入在隊(duì)列的尾部,還是插入在隊(duì)列的前部。OSQPost(),OSQPostFront()分別對(duì)應(yīng)兩種不同的入棧方式。
OSQFlush()用來(lái)清空隊(duì)列。
為了支持消息隊(duì)列的特性,要在使用消息隊(duì)列時(shí),預(yù)先定義隊(duì)列的最大值。而且為了支持隊(duì)列特性,在Ecb里的指針實(shí)際指向的是一個(gè)隊(duì)列控制塊,相關(guān)的算法就不列舉了。