葉柯陽 王宜懷 徐婷婷 劉長勇
1(蘇州大學(xué)計(jì)算機(jī)科學(xué)與技術(shù)學(xué)院 江蘇 蘇州 215006) 2(武夷學(xué)院數(shù)學(xué)與計(jì)算機(jī)學(xué)院 福建 武夷山 354300)
mbedOS是一款面向ARM Cortex-M系列處理器的開源實(shí)時(shí)操作系統(tǒng)(Real-Time Operating System,RTOS)[1]。它于2014年由ARM公司推出,專為物聯(lián)網(wǎng) (Internet of Things,IoT) 中的“物體”設(shè)計(jì)[2],包含了開發(fā)基于Arm Cortex-M微控制器的聯(lián)網(wǎng)產(chǎn)品所需的所有特性,包括安全性、連接性、RTOS傳感器和I/O設(shè)備的驅(qū)動(dòng)程序,具有堆棧共用和事件驅(qū)動(dòng)兩大代表性特點(diǎn)。
目前,針對(duì)嵌入式實(shí)時(shí)操作系統(tǒng)優(yōu)先級(jí)反轉(zhuǎn)問題的研究集中在VxWorks操作系統(tǒng)、WinCE操作系統(tǒng)、μC/OS-Ⅱ操作系統(tǒng)等,而對(duì)mbedOS實(shí)時(shí)操作系統(tǒng)避免優(yōu)先級(jí)反轉(zhuǎn)問題的機(jī)制研究方面缺乏相關(guān)材料。為此,本文將利用Cortex-M4內(nèi)核的STM32微控制器,基于Kinetis Design Studio 3.1.0 IDE開發(fā)環(huán)境和SD-mbedOS工程框架[3],通過一個(gè)測試工程對(duì)mbedOS避免優(yōu)先級(jí)反轉(zhuǎn)問題的機(jī)制進(jìn)行分析,剖析其從各線程啟動(dòng),到申請(qǐng)互斥量,再到最終釋放互斥量的全過程,結(jié)合相關(guān)函數(shù)、關(guān)鍵代碼、流程圖、時(shí)序圖等分析其實(shí)現(xiàn)的原理。通過對(duì)mbedOS避免優(yōu)先級(jí)反轉(zhuǎn)問題機(jī)制的剖析,可為 mbedOS的應(yīng)用研究和在不同微控制器上的移植提供基礎(chǔ),也可為不同實(shí)時(shí)操作系統(tǒng)下避免優(yōu)先級(jí)反轉(zhuǎn)問題機(jī)制的比較分析提供參考。
“火星探路者”于1997年7月4日在火星表面著陸。在開始的幾天內(nèi)工作穩(wěn)定,并傳回大量數(shù)據(jù),但是幾天后,“探路者”開始出現(xiàn)系統(tǒng)復(fù)位、數(shù)據(jù)丟失的現(xiàn)象。究其原因是發(fā)生了優(yōu)先級(jí)反轉(zhuǎn)問題[4]。
當(dāng)線程以獨(dú)占方式使用共享資源時(shí),可能出現(xiàn)低優(yōu)先級(jí)線程先于高優(yōu)先級(jí)線程被運(yùn)行的現(xiàn)象,這就是優(yōu)先級(jí)反轉(zhuǎn)問題,可進(jìn)行如下一般性描述。
假設(shè)有三個(gè)線程Ta、Tb、Tc,其優(yōu)先級(jí)分別記為Pa、Pb、Pc,且有Pa>Pb>Pc,Ta和Tc使用一個(gè)共享資源S,Tb并不使用共享資源S,用信號(hào)量x(x=0,1)標(biāo)識(shí)對(duì)S的獨(dú)占訪問,初始時(shí)x=1,表示可獨(dú)占S。設(shè)t0時(shí)刻,Tc先運(yùn)行并獲取信號(hào)量(即x由1變?yōu)?,表示S已被占用),使用S。t1時(shí)刻,Ta被調(diào)度運(yùn)行,因?yàn)镻a>Pc,故搶占Tc獲得CPU使用權(quán)。Ta運(yùn)行至t2時(shí)刻,需訪問S,但Tc并沒有釋放S(即x仍為0),所以Ta放入阻塞隊(duì)列,直到x=1,才能從阻塞隊(duì)列中移出,放入就緒隊(duì)列,被重新調(diào)度運(yùn)行。t3時(shí)刻,Tb搶占Tc獲得運(yùn)行,此時(shí)就出現(xiàn)了Tb雖然優(yōu)先級(jí)比Ta低,卻比Ta先運(yùn)行的現(xiàn)象,不合理,這就是優(yōu)先級(jí)反轉(zhuǎn)問題。表1給出了上述過程的運(yùn)行時(shí)序。
表1 優(yōu)先級(jí)反轉(zhuǎn)過程
對(duì)于嵌入式實(shí)時(shí)操作系統(tǒng)來說,最重要的指標(biāo)在于確認(rèn)線程執(zhí)行的時(shí)間是可預(yù)測的,即要確保在任何時(shí)刻執(zhí)行某個(gè)線程都不能超過某個(gè)特定的時(shí)間。然而由于本身基于優(yōu)先級(jí)設(shè)計(jì)的線程,每個(gè)優(yōu)先級(jí)不同的線程往往對(duì)應(yīng)著現(xiàn)實(shí)中執(zhí)行的任務(wù),若發(fā)生了優(yōu)先級(jí)反轉(zhuǎn),會(huì)導(dǎo)致低優(yōu)先級(jí)線程比高優(yōu)先級(jí)線程先執(zhí)行,造成線程調(diào)度時(shí)時(shí)間的不確定性,破壞實(shí)時(shí)系統(tǒng)的實(shí)時(shí)性,嚴(yán)重時(shí)可能導(dǎo)致系統(tǒng)崩潰。因此處理好這類問題對(duì)于實(shí)時(shí)操作系統(tǒng)的正常運(yùn)行至關(guān)重要。
常見的避免優(yōu)先級(jí)反轉(zhuǎn)方法一般有兩種,分別為優(yōu)先級(jí)繼承和優(yōu)先級(jí)天花板。
(1) 優(yōu)先級(jí)繼承。優(yōu)先級(jí)繼承(priority inheritance)是指通過臨時(shí)提升持有資源的低優(yōu)先級(jí)線程的優(yōu)先級(jí)至請(qǐng)求訪問同一資源的高優(yōu)先級(jí)線程的優(yōu)先級(jí)來避免優(yōu)先級(jí)反轉(zhuǎn)的方法[5]。
(2)優(yōu)先級(jí)天花板。優(yōu)先級(jí)天花板(priority ceiling)是指通過將申請(qǐng)并得到資源的線程的優(yōu)先級(jí)臨時(shí)提升至所有可能使用該資源的線程中最高優(yōu)先級(jí)線程的優(yōu)先級(jí)(即天花板)來避免優(yōu)先級(jí)反轉(zhuǎn)的方法[5]。
許多嵌入式實(shí)時(shí)操作系統(tǒng),例如μC/OS-Ⅱ、VxWorks、WinCE等針對(duì)這一問題都有自己的處理方式。μC/OS-Ⅱ操作系統(tǒng)提供互斥信號(hào)量mutex來避免優(yōu)先級(jí)反轉(zhuǎn),其利用的是優(yōu)先級(jí)置頂協(xié)議[6]。VxWorks操作系統(tǒng)對(duì)優(yōu)先級(jí)反轉(zhuǎn)問題采用優(yōu)先級(jí)繼承協(xié)議,整體上使用互斥信號(hào)量,按優(yōu)先級(jí)與先入先出隊(duì)列兩種方式排列等待對(duì)該互斥信號(hào)量進(jìn)行上鎖的線程,在創(chuàng)建互斥信號(hào)量時(shí),選擇SEM_INVERSION_ SAFE與SEM_Q_PRIORITY兩種選擇值的域,即可避免優(yōu)先權(quán)反轉(zhuǎn)[7]。WinCE實(shí)時(shí)操作系統(tǒng)軟件采用優(yōu)先級(jí)繼承方式來避免優(yōu)先級(jí)反轉(zhuǎn),當(dāng)高優(yōu)先級(jí)線程被低優(yōu)先級(jí)線程阻塞時(shí),會(huì)提升低優(yōu)先級(jí)線程的優(yōu)先級(jí)至高優(yōu)先級(jí)線程的優(yōu)先級(jí),從而避免優(yōu)先級(jí)反轉(zhuǎn)現(xiàn)象[8]。
本文將使用一個(gè)測試工程來對(duì)mbedOS避免優(yōu)先級(jí)反轉(zhuǎn)問題機(jī)制進(jìn)行流程分析。首先對(duì)部分關(guān)鍵代碼進(jìn)行分析,然后給出調(diào)度時(shí)序,最后對(duì)執(zhí)行流程進(jìn)行分段解析。測試工程的功能是:創(chuàng)建三個(gè)用戶線程Ta、Tb、Tc,初始優(yōu)先級(jí)分別設(shè)置為26、25、24(在mbedOS中數(shù)字越大優(yōu)先級(jí)越高,將此處三個(gè)線程的優(yōu)先級(jí)分別命名為Pa、Pb、Pc),啟動(dòng)運(yùn)行順序?yàn)門c、Ta、Tb,其中Tc和Ta使用同一互斥量來申請(qǐng)對(duì)共享資源S的獨(dú)占使用,Tb并不使用共享資源S。測試工程通過串口輸出三個(gè)線程調(diào)度時(shí)使用互斥量避免優(yōu)先級(jí)反轉(zhuǎn)的詳細(xì)過程。為了確保整體執(zhí)行流程能夠循環(huán)執(zhí)行,過程中使用到了延時(shí)函數(shù)(會(huì)將線程放入延時(shí)隊(duì)列),此處模擬線程到達(dá)的先后順序?yàn)椋航?jīng)過1秒后Tc到達(dá),再經(jīng)過4秒后Ta、Tb到達(dá)(注意此處到達(dá)順序?yàn)橄萒a后Tb)。測試工程的執(zhí)行流程如圖1所示。
圖1 測試工程執(zhí)行流程
3.1.1互斥量控制塊
在mbedOS中使用互斥量控制塊的方式來描述互斥量,數(shù)據(jù)結(jié)構(gòu)如下:
typedef struct osRtxMutex_s
{
uint8_t id;
//互斥量ID
uint8_t state;
//互斥量狀態(tài)
uint8_t flags;
//互斥量標(biāo)志
uint8_t attr;
//互斥量屬性
const char *name;
//互斥量名稱
osRtxThread_t *thread_list;
//互斥量阻塞隊(duì)列
osRtxThread_t *owner_thread;
//互斥量私有線程
struct osRtxMutex_s *owner_prev;
//指向前一個(gè)互斥量
struct osRtxMutex_s *owner_next;
//指向下一個(gè)互斥量
uint8_t lock;
//互斥鎖
uint8_t padding[3];
//保留
} osRtxMutex_t;
其中互斥量屬性attr包括嵌套型互斥量(osMutexRecursive)、內(nèi)部優(yōu)先級(jí)互斥量(osMutexPrioInherit)和健壯互斥量(osMutexRobust)。當(dāng)高優(yōu)先級(jí)的線程等待已被低優(yōu)先級(jí)的線程鎖定互斥量時(shí),若該互斥量擁有內(nèi)部優(yōu)先級(jí)屬性,則低優(yōu)先級(jí)的線程會(huì)以高優(yōu)先級(jí)線程的優(yōu)先級(jí)運(yùn)行,這種方式是以繼承的形式進(jìn)行傳遞的。當(dāng)線程解鎖互斥量時(shí),線程的優(yōu)先級(jí)自動(dòng)變?yōu)樗瓉淼膬?yōu)先級(jí)。mbedOS正是利用了這一關(guān)鍵屬性來避免優(yōu)先級(jí)反轉(zhuǎn)問題。
3.1.2互斥量鎖定函數(shù)
在mbedOS中,線程可通過使用互斥量對(duì)象調(diào)用lock函數(shù)的方式申請(qǐng)鎖定互斥量。lock函數(shù)內(nèi)部調(diào)用順序?yàn)閘ock→osMutexAcquire→_ _svcMutexAcquire→觸發(fā)SVC中斷服務(wù)程序SVC_Handler→實(shí)際互斥量鎖定函數(shù)svcRtxMutexAcquire。
其中涉及到優(yōu)先級(jí)部分的關(guān)鍵代碼如下:
if ((mutex->attr & osMutexPrioInherit) !=0U) {
if (mutex->owner_thread->priority
mutex->owner_thread->priority=thread->priority;
osRtxThreadListSort(mutex->owner_thread);
}
}
代碼段分析:if((mutex->attr & osMutexPrioInherit)!=0U)語句表示判斷該互斥量對(duì)象是否具有內(nèi)部優(yōu)先級(jí)屬性,若包含則進(jìn)一步判斷該互斥量私有線程的優(yōu)先級(jí)mutex->owner_thread->priority是否小于當(dāng)前運(yùn)行線程的優(yōu)先級(jí)thread->priority,若小于則將當(dāng)前運(yùn)行線程的優(yōu)先級(jí)賦值給該互斥量私有線程的優(yōu)先級(jí),即所謂的優(yōu)先級(jí)繼承。
3.1.3互斥量解鎖函數(shù)
在mbedOS中,線程可通過使用互斥量對(duì)象調(diào)用unlock函數(shù)的方式申請(qǐng)解鎖互斥量。unlock函數(shù)內(nèi)部調(diào)用順序?yàn)閡nlock→osMutexRelease→_ _svcMutexRelease→觸發(fā)SVC中斷服務(wù)程序SVC_Handler→實(shí)際互斥量解鎖函數(shù)svcRtxMutexRelease。
其中涉及到優(yōu)先級(jí)部分的關(guān)鍵代碼如下:
if ((mutex->attr & osMutexPrioInherit)!=0U) {
priority=thread->priority_base;
mutex0=thread->mutex_list;
while (mutex0 != NULL) {
if ((mutex0->thread_list!=NULL) && (mutex0->thread_list->priority>priority)) {
priority=mutex0->thread_list->priority;
}
mutex0=mutex0->owner_next;
}
thread->priority=priority;
}
代碼段分析:if((mutex->attr & osMutexPrioInherit)!=0U)語句表示判斷該互斥量是否具有內(nèi)部優(yōu)先級(jí)屬性,priority和mutex0均表示臨時(shí)局部變量,thread表示當(dāng)前運(yùn)行線程。若該互斥量包含內(nèi)部優(yōu)先級(jí)屬性,則首先獲取當(dāng)前運(yùn)行線程初始化時(shí)的優(yōu)先級(jí)thread->priority_base賦值給priority以及當(dāng)前運(yùn)行線程所擁有的互斥量列表指針thread->mutex_list賦值給mutex0。然后使用while循環(huán)找到mutex0所在的互斥量列表中的所有互斥量所擁有的所有線程,獲取優(yōu)先級(jí)最高的線程的優(yōu)先級(jí)并賦值給priority。最后將當(dāng)前運(yùn)行線程的優(yōu)先級(jí)變?yōu)閜riority。
特別要指出的是,上述while循環(huán)語句是為了避免當(dāng)?shù)蛢?yōu)先級(jí)線程與多個(gè)高優(yōu)先級(jí)線程嵌套使用多個(gè)互斥量時(shí)可能造成的優(yōu)先級(jí)反轉(zhuǎn)現(xiàn)象。由于只有當(dāng)系統(tǒng)中存在三個(gè)或三個(gè)以上線程時(shí)才可能發(fā)生優(yōu)先級(jí)反轉(zhuǎn)現(xiàn)象,故此處以四個(gè)線程為例。假設(shè)有兩個(gè)高優(yōu)先級(jí)線程Ta和Tb、一個(gè)中優(yōu)先級(jí)線程Tc以及一個(gè)低優(yōu)先級(jí)線程Td,優(yōu)先級(jí)分別為Pa、Pb、Pc、Pd,其中Pa>Pb>Pc>Pd,Ta和Td使用同一互斥量mutex1共享資源S1, Tb和Td使用同一互斥量mutex2共享資源S2,且Td首先申請(qǐng)鎖定互斥量mutex2,再在釋放mutex2之前申請(qǐng)鎖定互斥量mutex1(連續(xù)鎖定多個(gè)互斥量遵循后進(jìn)先出原則,即后鎖定的互斥量先釋放)。設(shè)t0時(shí)刻,Td首先到達(dá)并鎖定了互斥量mutex2和互斥量mutex1。t1時(shí)刻Ta和Tb到達(dá),等待Td解鎖各自需要的互斥量。Tc也在t1時(shí)刻到達(dá),由于Ta和Tb調(diào)用互斥量鎖定函數(shù)svcRtxMutexAcquire時(shí)已將Td的優(yōu)先級(jí)提升至最高值Pa,故Tc會(huì)進(jìn)入等待狀態(tài),等待Td運(yùn)行。t2時(shí)刻,Td解鎖互斥量mutex1,若此時(shí)未使用上述while循環(huán)語句,Td會(huì)直接降為其初始優(yōu)先級(jí)Pd,此時(shí)Ta會(huì)搶占Td獲得CPU使用權(quán),鎖定互斥量mutex1并執(zhí)行。t3時(shí)刻,Ta運(yùn)行完畢,此時(shí)處于等待狀態(tài)的Td由于優(yōu)先級(jí)低于Tc,會(huì)被Tc搶占CPU使用權(quán),使得Td無法解鎖互斥量mutex2,從而導(dǎo)致優(yōu)先級(jí)低于Tb的線程Tc卻先于Tb運(yùn)行,造成優(yōu)先級(jí)反轉(zhuǎn)現(xiàn)象,具體過程如表2所示。
表2 四線程優(yōu)先級(jí)反轉(zhuǎn)過程
續(xù)表2
若只涉及到一個(gè)低優(yōu)先級(jí)線程和一個(gè)高優(yōu)先級(jí)線程對(duì)于同一個(gè)互斥量的使用,則在低優(yōu)先級(jí)線程(即上述代碼中的thread)解鎖互斥量的過程中,由于thread擁有的互斥量列表已在執(zhí)行上述代碼之前釋放,故mutex0為空,while循環(huán)實(shí)際上并不執(zhí)行,而是直接通過語句:
priority=thread->priority_base;
thread->priority=priority;
將thread的優(yōu)先級(jí)轉(zhuǎn)變?yōu)槠涑跏純?yōu)先級(jí)。
本文的測試工程在STM32微控制器[13]上進(jìn)行。STM32片內(nèi)Flash存儲(chǔ)區(qū)大小為256 KB,一般用來存放中斷向量、程序代碼、常量等;片內(nèi)RAM存儲(chǔ)區(qū)大小為32 KB,一般用于存放初始化的全局變量、靜態(tài)變量、局部變量等。
3.2.1線程調(diào)度時(shí)序分析
測試工程涉及到的三個(gè)線程具體調(diào)度時(shí)序圖如圖2所示。
圖2 線程調(diào)度時(shí)序圖
其中“?”表示線程或隊(duì)列的有效運(yùn)行時(shí)間,實(shí)線箭頭表示線程運(yùn)行、進(jìn)入隊(duì)列、申請(qǐng)互斥量或改變優(yōu)先級(jí),虛線箭頭表示從隊(duì)列取線程(互斥量)或返回申請(qǐng)互斥量結(jié)果。
3.2.2執(zhí)行流程分段解析
目前,國內(nèi)外針對(duì)嵌入式實(shí)時(shí)操作系統(tǒng)的優(yōu)先級(jí)反轉(zhuǎn)問題采用的分析方法,大多為一個(gè)基于示例圖的淺層實(shí)驗(yàn)[9-10],均沒有完整地將處理機(jī)制分析清楚。針對(duì)上述問題,本文將采用基于時(shí)序圖并在代碼中相應(yīng)位置插入printf語句的方法進(jìn)行分析。插入printf語句的分析方式是應(yīng)用最廣泛的調(diào)試技術(shù)之一,可應(yīng)用于計(jì)算機(jī)視覺中來輸出中間結(jié)果[11],亦可應(yīng)用于程序功能分析中得到故障產(chǎn)生的信息[12]。該方法具有簡單、清晰、直觀等優(yōu)點(diǎn)。
(1) 線程啟動(dòng)。從芯片上電到mbedOS 啟動(dòng)完成后,會(huì)最終轉(zhuǎn)到app_init函數(shù)執(zhí)行,然后可在app_init函數(shù)中創(chuàng)建用戶線程[3]。在該函數(shù)中創(chuàng)建并先后啟動(dòng)了三個(gè)用戶線程Tc、Ta、Tb,然后阻塞該函數(shù)的運(yùn)行。為確保線程能正常被創(chuàng)建,不被其他線程打斷,在創(chuàng)建用戶線程的過程中,使用了互斥量。
printf輸出結(jié)果如下:
0-1.當(dāng)前運(yùn)行的主線程(2000FF80)啟動(dòng)線程Tc.
4-1.互斥量(20001694)的互斥鎖=0,表示未鎖定,當(dāng)前運(yùn)行線程(2000FF80)可以申請(qǐng)?jiān)摶コ饬?/p>
4-2.互斥鎖變?yōu)?,表示互斥量申請(qǐng)成功
8.互斥鎖變?yōu)?,表示完全解鎖,將當(dāng)前互斥量(20001694)從當(dāng)前運(yùn)行線程(2000FF80)擁有的互斥量列表(2000FFAC)中移除
*9-1.當(dāng)前線程=2000FF80的初始優(yōu)先級(jí)=24,當(dāng)前優(yōu)先級(jí)=24
*9-2.釋放互斥量后,當(dāng)前線程=2000FF80的初始優(yōu)先級(jí)=24,當(dāng)前優(yōu)先級(jí)=24
0-2.當(dāng)前運(yùn)行的主線程(2000FF80)啟動(dòng)線程Ta.
4-1.互斥量(20001814)的互斥鎖=0,表示未鎖定,當(dāng)前運(yùn)行線程(2000FF80)可以申請(qǐng)?jiān)摶コ饬?/p>
4-2.互斥鎖變?yōu)?,表示互斥量申請(qǐng)成功
8.互斥鎖變?yōu)?,表示完全解鎖,將當(dāng)前互斥量(20001814)從當(dāng)前運(yùn)行線程(2000FF80)擁有的互斥量列表(2000FFAC)中移除
*9-1.當(dāng)前線程=2000FF80的初始優(yōu)先級(jí)=24,當(dāng)前優(yōu)先級(jí)=24
*9-2.釋放互斥量后,當(dāng)前線程=2000FF80的初始優(yōu)先級(jí)=24,當(dāng)前優(yōu)先級(jí)=24
0-3.當(dāng)前運(yùn)行的主線程(2000FF80)啟動(dòng)線程Tb.
4-1.互斥量(20001754)的互斥鎖=0,表示未鎖定,當(dāng)前運(yùn)行線程(2000FF80)可以申請(qǐng)?jiān)摶コ饬?/p>
4-2.互斥鎖變?yōu)?,表示互斥量申請(qǐng)成功
8.互斥鎖變?yōu)?,表示完全解鎖,將當(dāng)前互斥量(20001754)從當(dāng)前運(yùn)行線程(2000FF80)擁有的互斥量列表(2000FFAC)中移除
*9-1.當(dāng)前線程=2000FF80的初始優(yōu)先級(jí)=24,當(dāng)前優(yōu)先級(jí)=24
*9-2.釋放互斥量后,當(dāng)前線程=2000FF80的初始優(yōu)先級(jí)=24,當(dāng)前優(yōu)先級(jí)=24
******Tc、Ta和Tb啟動(dòng)完成,同時(shí)阻塞主線程******
(2) Tc申請(qǐng)鎖定互斥量。阻塞主線程后,由于初始時(shí)Tc延時(shí)1秒,而Ta和Tb延時(shí)5秒,故Tc會(huì)先從延時(shí)隊(duì)列中移出放入就緒隊(duì)列,搶占空閑線程獲得CPU使用權(quán)。由于互斥鎖為0,Tc申請(qǐng)鎖定互斥量成功,互斥鎖變?yōu)?,同時(shí)點(diǎn)亮藍(lán)燈。
printf輸出結(jié)果如下:
5.當(dāng)前就緒隊(duì)列中最高優(yōu)先級(jí)線程(20001834)取代當(dāng)前運(yùn)行線程(200016B4),開始運(yùn)行
1.Tc(200016B4)獲得CPU使用權(quán),藍(lán)燈亮
1-1.Tc申請(qǐng)鎖定互斥量
4-1.互斥量(20001880)的互斥鎖=0,表示未鎖定,當(dāng)前運(yùn)行線程(200016B4)可以申請(qǐng)?jiān)摶コ饬?/p>
4-2.互斥鎖變?yōu)?,表示互斥量申請(qǐng)成功
1-2.Tc鎖定互斥量成功,將鎖定15秒
(3) Ta申請(qǐng)鎖定互斥量。在Tc鎖定互斥量4秒后,Ta和Tb從延時(shí)隊(duì)列中移出,放入到就緒隊(duì)列,由于Pa大于Pb,故mbedOS會(huì)從就緒隊(duì)列中取出Ta激活運(yùn)行。又因?yàn)镻a大于Pc,故Ta會(huì)搶占Tc獲得CPU使用權(quán),Tc被放入就緒隊(duì)列,同時(shí)熄滅藍(lán)燈。
但當(dāng)Ta運(yùn)行至申請(qǐng)鎖定互斥量時(shí),由于此時(shí)互斥量已被Tc鎖定(互斥鎖為1),Ta申請(qǐng)互斥量失敗,因此會(huì)將Tc的優(yōu)先級(jí)提升至與Ta的優(yōu)先級(jí)相同(即使用優(yōu)先級(jí)繼承方法將Tc的優(yōu)先級(jí)提升至Pa),然后將Tc放入就緒隊(duì)列重新排序,Ta自身進(jìn)入等待隊(duì)列和互斥量阻塞隊(duì)列,將CPU使用權(quán)讓給Tc,等待Tc解鎖互斥量。
printf輸出結(jié)果如下:
5.當(dāng)前就緒隊(duì)列中最高優(yōu)先級(jí)線程(20001834)取代當(dāng)前運(yùn)行線程(200016B4),開始運(yùn)行
2.Ta(20001834)搶占Tc獲得CPU使用權(quán),藍(lán)燈暗
2-1.Ta申請(qǐng)鎖定互斥量
6.互斥鎖=1,表示已鎖定(其所有者線程=200016B4),互斥量申請(qǐng)失敗
*7-1.優(yōu)先級(jí)繼承前,當(dāng)前互斥量私有線程=200016B4的優(yōu)先級(jí)=24低于當(dāng)前運(yùn)行線程=20001834的優(yōu)先級(jí)=26
*7-2.優(yōu)先級(jí)繼承后,當(dāng)前互斥量私有線程=200016B4的優(yōu)先級(jí)被提升至與當(dāng)前運(yùn)行線程=20001834的優(yōu)先級(jí)=26相同
*7-3.將當(dāng)前互斥量私有線程=200016B4從互斥量私有線程列表中移出,并放到就緒隊(duì)列(200001D0)中重新排序
6-1.將當(dāng)前運(yùn)行線程(20001834)放到等待隊(duì)列(200001E4)
6-2.從就緒隊(duì)列(200001D0)獲取優(yōu)先級(jí)最高的線程(200016B4),并設(shè)置為激活態(tài)準(zhǔn)備運(yùn)行
6-3.將當(dāng)前運(yùn)行線程(20001834)放入互斥量阻塞隊(duì)列(20001888):20001834->0->80019A9
(4) Tc解鎖互斥量。Tc重新獲得CPU使用權(quán)后,繼續(xù)運(yùn)行。由于互斥量是由Tc鎖定的,因此Tc能成功解鎖互斥量。在解鎖過程中,由于當(dāng)前運(yùn)行線程Tc將互斥量從它的互斥量列表中移出,故此時(shí)mutex0為空,因此在互斥量解鎖函數(shù)svcRtxReleaseMutex中會(huì)直接將Tc的優(yōu)先級(jí)降為其初始優(yōu)先級(jí)Pc。解鎖后互斥鎖為0,同時(shí)點(diǎn)亮藍(lán)燈,Tc放入就緒隊(duì)列,又開始等待執(zhí)行新一輪的執(zhí)行過程。此時(shí)互斥量會(huì)從互斥量列表移出,并移轉(zhuǎn)給正在等待互斥量的Ta,之后Ta放入就緒隊(duì)列,由于Pa>Pb>Pc,故在就緒隊(duì)列中Ta處于隊(duì)首位置。mbedOS從就緒隊(duì)列中取出優(yōu)先級(jí)最高的Ta激活運(yùn)行,Ta成功鎖定互斥量,互斥鎖變?yōu)?。
printf輸出結(jié)果如下:
1-3.Tc解鎖互斥量成功,藍(lán)燈亮
8.互斥鎖變?yōu)?,表示完全解鎖,將當(dāng)前互斥量(20001880)從當(dāng)前運(yùn)行線程(200016B4)擁有的互斥量列表(200016E0)中移除
*9-1.當(dāng)前線程=200016B4的初始優(yōu)先級(jí)=24,當(dāng)前優(yōu)先級(jí)=26
*9-2.釋放互斥量后,當(dāng)前線程=200016B4的初始優(yōu)先級(jí)=24,當(dāng)前優(yōu)先級(jí)=24
10-1.從互斥量阻塞隊(duì)列(20001888)中獲取優(yōu)先級(jí)最高的互斥量等待線程=20001834
10-2.將線程(20001834)從等待隊(duì)列(200001E4)中移出
10-3.將線程(20001834)放到就緒隊(duì)列(200001D0)
11.此時(shí)就緒隊(duì)列=200001D0中的線程:20001834->20001774->20001328
10-4.將剛獲取的線程(20001834)設(shè)置為互斥量所有者,互斥鎖變?yōu)?
(5) Ta解鎖互斥量。Ta運(yùn)行5秒后,由于互斥量是由Ta鎖定的,因此Ta能成功解鎖互斥量,解鎖后互斥鎖為0,同時(shí)熄滅藍(lán)燈。互斥量從互斥量列表移出,同時(shí)為了重復(fù)上述演示過程,Ta放入延時(shí)隊(duì)列5秒,5秒之后從延時(shí)隊(duì)列移出放入就緒隊(duì)列,又開始等待執(zhí)行新一輪的執(zhí)行過程。
printf輸出結(jié)果如下:
5.當(dāng)前就緒隊(duì)列中最高優(yōu)先級(jí)線程(20001834)取代當(dāng)前運(yùn)行線程(200016B4),開始運(yùn)行
2-2.Ta鎖定互斥量成功,將鎖定5秒
2-3.Ta解鎖互斥量成功,藍(lán)燈暗
8.互斥鎖變?yōu)?,表示完全解鎖,將當(dāng)前互斥量(20001880)從當(dāng)前運(yùn)行線程(20001834)擁有的互斥量列表(20001860)中移除
*9-1.當(dāng)前線程=20001834的初始優(yōu)先級(jí)=26,當(dāng)前優(yōu)先級(jí)=26
*9-2.釋放互斥量后,當(dāng)前線程=20001834的初始優(yōu)先級(jí)=26,當(dāng)前優(yōu)先級(jí)=26
(6) Tb運(yùn)行。在Ta進(jìn)入延時(shí)隊(duì)列后,mbedOS從就緒隊(duì)列中取出優(yōu)先級(jí)最高的Tb激活運(yùn)行。Tb運(yùn)行5秒后釋放CPU使用權(quán),為了重復(fù)上述演示過程,Tb放入延時(shí)隊(duì)列4秒,之后從延時(shí)隊(duì)中移出放入就緒隊(duì)列,又開始等待執(zhí)行新一輪的執(zhí)行過程。printf輸出結(jié)果如下:
3.Tb(20001774)獲得CPU使用權(quán),將運(yùn)行5秒,成功避免優(yōu)先級(jí)反轉(zhuǎn)
5.當(dāng)前就緒隊(duì)列中最高優(yōu)先級(jí)線程(20001834)取代當(dāng)前運(yùn)行線程(20001774),開始運(yùn)行
3-1.taskB釋放CPU使用權(quán)
其中地址2000FF80表示主線程,地址200016B4表示Tc,地址20001834表示Ta,地址20001774表示Tb,地址80019A9表示缺省處理函數(shù)DefaultISR,地址20001328表示空閑線程(系統(tǒng)中無運(yùn)行線程時(shí)該線程被調(diào)度運(yùn)行)。
通過上述流程分析,mbedOS中避免優(yōu)先級(jí)反轉(zhuǎn)問題機(jī)制可歸納如下:
mbedOS整體上使用的是基于互斥量的優(yōu)先級(jí)繼承方法來避免優(yōu)先級(jí)反轉(zhuǎn),其中優(yōu)先級(jí)繼承方法包括優(yōu)先級(jí)提升與優(yōu)先級(jí)恢復(fù)兩部分。當(dāng)線程調(diào)用svcRtxMutexAcquire函數(shù)申請(qǐng)鎖定互斥量時(shí),系統(tǒng)首先判斷該互斥量是否已鎖定,若已鎖定,則進(jìn)一步判斷該互斥量是否具有內(nèi)部優(yōu)先級(jí)屬性osMutexPrioInherit,若是,則通過語句mutex->owner_thread->priority=thread->priority將當(dāng)前運(yùn)行線程(thread)的優(yōu)先級(jí)賦給該互斥量的私有線程(mutex->owner_thread),即優(yōu)先級(jí)繼承方法中的優(yōu)先級(jí)提升;當(dāng)線程調(diào)用svcRtxMutexRelease函數(shù)申請(qǐng)鎖定互斥量時(shí),系統(tǒng)首先判斷該互斥量是否具有內(nèi)部優(yōu)先級(jí)屬性osMutexPrioInherit,若是,則獲取當(dāng)前運(yùn)行線程的互斥量列表,若除了該互斥量以外,還具有其他互斥量,則遍歷其他互斥量的私有線程列表,獲取其中最高優(yōu)先級(jí)線程的優(yōu)先級(jí),并將其賦給當(dāng)前運(yùn)行線程,否則直接將當(dāng)前運(yùn)行線程優(yōu)先級(jí)變?yōu)槠涑跏純?yōu)先級(jí),即優(yōu)先級(jí)繼承方法中的優(yōu)先級(jí)恢復(fù)。
優(yōu)先級(jí)反轉(zhuǎn)問題是每一個(gè)實(shí)時(shí)操作系統(tǒng)所必須要考慮到的問題,一旦出現(xiàn),就會(huì)破壞系統(tǒng)的實(shí)時(shí)性,輕則造成系統(tǒng)邏輯紊亂,嚴(yán)重時(shí)甚至?xí)?dǎo)致系統(tǒng)崩潰。本文首先通過歷史上出現(xiàn)過的問題引出優(yōu)先級(jí)反轉(zhuǎn),并分析其現(xiàn)象、避免的意義以及基本解決方案,然后利用Cortex-M4內(nèi)核的STM32微控制器,基于Kinetis Design Studio 3.1.0 IDE開發(fā)環(huán)境和SD-mbedOS工程框架,構(gòu)建一個(gè)測試工程對(duì)mbedOS避免優(yōu)先級(jí)反轉(zhuǎn)問題的機(jī)制進(jìn)行了詳細(xì)剖析。通過剖析,有助于快速理解mbedOS中互斥量的使用以及避免優(yōu)先級(jí)反轉(zhuǎn)問題的詳細(xì)過程,可為 mbedOS的應(yīng)用研究和在不同微控制器上的移植提供基礎(chǔ),也可為不同實(shí)時(shí)操作系統(tǒng)下避免優(yōu)先級(jí)反轉(zhuǎn)問題機(jī)制的比較分析提供參考。