王庭琛,王宜懷,陳瑞雪
(蘇州大學(xué) 計(jì)算機(jī)與科學(xué)技術(shù)學(xué)院,江蘇 蘇州 215006)
大部分嵌入式產(chǎn)品的開(kāi)發(fā)中,尤其是工業(yè)控制和軍工產(chǎn)業(yè),不僅對(duì)實(shí)時(shí)性的需求較高,即系統(tǒng)需要在短時(shí)間內(nèi)對(duì)線程進(jìn)行響應(yīng)并處理,而且要求盡量不浪費(fèi)CPU的運(yùn)算資源。因此,通常會(huì)選擇在設(shè)備上使用實(shí)時(shí)操作系統(tǒng)(Real Time Operating System,RTOS)來(lái)調(diào)度各個(gè)線程,使系統(tǒng)可以多線程同時(shí)執(zhí)行且保持一定的實(shí)時(shí)性。在這種高實(shí)時(shí)性的環(huán)境中,線程的錯(cuò)誤調(diào)度可能會(huì)破壞整個(gè)系統(tǒng)的正常運(yùn)行并對(duì)系統(tǒng)的性能產(chǎn)生較大影響,因此為了保證系統(tǒng)正常運(yùn)行的同時(shí)避免因線程延遲而浪費(fèi)時(shí)間,需要在線程調(diào)度策略中加入一些機(jī)制來(lái)防止上述情況發(fā)生。
操作系統(tǒng)運(yùn)行時(shí),可能會(huì)出現(xiàn)多個(gè)線程同時(shí)申請(qǐng)使用同一公共資源的情況,當(dāng)某一線程正在使用上述公共資源時(shí),操作系統(tǒng)直接切換其他的線程運(yùn)行并奪走此公共資源,則運(yùn)行結(jié)果將取決于線程運(yùn)行的時(shí)序情況,若發(fā)生競(jìng)爭(zhēng)條件(Race Condition)現(xiàn)象,運(yùn)行結(jié)果無(wú)法預(yù)估。為了使線程正常運(yùn)行,應(yīng)在兩線程異步運(yùn)行過(guò)程中添加制約關(guān)系,如一個(gè)線程正在使用某一公共資源時(shí),另一個(gè)需要使用此公共資源的線程應(yīng)等待上一個(gè)線程使用完畢后再運(yùn)行,也就是操作系統(tǒng)需要保證對(duì)于某一公共資源在同一時(shí)刻至多有一個(gè)線程使用,此時(shí)就需要引入互斥量對(duì)線程的調(diào)度進(jìn)行約束,確保線程運(yùn)行正確并避免公共資源被破壞。
目前,嵌入式設(shè)備運(yùn)行實(shí)時(shí)操作系統(tǒng)中使用互斥量的程序進(jìn)行線程調(diào)度,然而大多數(shù)僅對(duì)其進(jìn)行了簡(jiǎn)單的原理介紹,對(duì)互斥量的實(shí)現(xiàn)過(guò)程以及操作系統(tǒng)對(duì)使用互斥量的線程調(diào)度方法的研究較少。因此,本文針對(duì)ARM 公司推出的面向物聯(lián)網(wǎng)(IoT)的mbedOS 實(shí)時(shí)操作系統(tǒng),使用ST 公司推出的基于ARM Cortex?M4 內(nèi)核的STM32L431 微控制器,配合蘇州大學(xué)基于mbedOS 研發(fā)的SD?mbedOS 工程框架,對(duì)mbedOS 的互斥量調(diào)度機(jī)制進(jìn)行深度剖析。首先對(duì)互斥量進(jìn)行介紹,其次分析mbedOS 中有關(guān)互斥量的結(jié)構(gòu)體、解鎖和上鎖互斥量的函數(shù),最后設(shè)計(jì)一個(gè)測(cè)試工程實(shí)際分析mbedOS 對(duì)使用互斥量線程的調(diào)度情況。
本文研究有助于了解mbedOS 中對(duì)于互斥量的實(shí)現(xiàn),厘清mbedOS 中對(duì)使用了互斥量的線程調(diào)度處理過(guò)程,也可為分析比較其他實(shí)時(shí)操作系統(tǒng)中互斥量的機(jī)制提供參考。
在軟件領(lǐng)域,通常使用互斥量來(lái)代表一項(xiàng)公共資源是否正在被使用,例如當(dāng)線程A 需要使用公共資源z 時(shí),線程A 會(huì)將公共資源z 的互斥量上鎖,此時(shí)如果線程B申請(qǐng)公共資源z,因公共資源z 的互斥量已經(jīng)被鎖,所以線程B 無(wú)法獲得公共資源z。申請(qǐng)失敗后,線程B 會(huì)進(jìn)入阻塞隊(duì)列等待公共資源z 的互斥量被釋放后再對(duì)其進(jìn)行重新申請(qǐng)。
在無(wú)操作系統(tǒng)的環(huán)境下,互斥量常使用全局變量來(lái)表示,并使用條件判斷語(yǔ)句來(lái)判斷互斥量是否被使用。這雖解決了線程互相搶占公共資源時(shí),破壞公共資源穩(wěn)定的問(wèn)題,但在某些特殊的情況下可能會(huì)產(chǎn)生一些其他問(wèn)題,以下將列舉幾種使用全局變量作為互斥量可能會(huì)引起的問(wèn)題:
1)同一線程重復(fù)上鎖導(dǎo)致死鎖
在同一線程運(yùn)行中,已通過(guò)一個(gè)函數(shù)獲取了公共資源的互斥量,在之后的使用過(guò)程中又直接或間接地回調(diào)這個(gè)函數(shù),此線程將再次申請(qǐng)此互斥量。由于此互斥量已在此線程使用中,第二次申請(qǐng)將無(wú)法成功,造成線程掛起形成死鎖。
2)優(yōu)先級(jí)反轉(zhuǎn)
如圖1 所示,假設(shè)系統(tǒng)中有三個(gè)優(yōu)先級(jí)依次遞減的線程T,T,T。其中T與T線程需要使用同一種公共資源,在T先運(yùn)行并獲取到互斥量的情況下,T進(jìn)入時(shí)會(huì)搶占T,在T運(yùn)行后會(huì)因申請(qǐng)不到互斥量被阻塞,系統(tǒng)將繼續(xù)運(yùn)行T。此時(shí)T進(jìn)入將搶占T運(yùn)行,就會(huì)發(fā)生T優(yōu)先級(jí)比T低卻更早獲得運(yùn)行的情況。此行為將使線程的運(yùn)行順序無(wú)法被預(yù)估,造成系統(tǒng)不穩(wěn)定。
圖1 優(yōu)先級(jí)反轉(zhuǎn)
3)線程異常導(dǎo)致互斥量無(wú)法釋放
線程在申請(qǐng)互斥量之后若出現(xiàn)異常無(wú)法繼續(xù)執(zhí)行,互斥量將無(wú)法被釋放,導(dǎo)致后續(xù)需要使用此互斥量的線程都無(wú)法正常運(yùn)行。
為防止上述錯(cuò)誤的發(fā)生,在操作系統(tǒng)實(shí)現(xiàn)的互斥量中通常增加更多的屬性,便于系統(tǒng)在調(diào)度線程時(shí)判斷互斥量和線程的不同狀態(tài),確保對(duì)線程的正確調(diào)度。
在mbedOS 中,聲明一個(gè)互斥量后,系統(tǒng)將先生成一個(gè)屬性結(jié)構(gòu)體(osMutexAttr_t)來(lái)保存此互斥量的信息供開(kāi)發(fā)人員閱讀使用,結(jié)構(gòu)體具體成員和作用如表1所示。
表1 屬性結(jié)構(gòu)體osMutexAttr_t 各成員作用
操作系統(tǒng)調(diào)度線程時(shí),實(shí)際使用的是根據(jù)屬性結(jié)構(gòu)體再創(chuàng)建出的控制塊結(jié)構(gòu)體(osRtxMutex_s),此結(jié)構(gòu)體內(nèi)除互斥量的基本信息外還保存系統(tǒng)調(diào)度時(shí)所使用到的互斥量參數(shù),結(jié)構(gòu)體具體成員和作用如表2 所示。
表2 控制塊結(jié)構(gòu)體osRtxMutex_s 各成員作用
mbedOS 中可供選擇的互斥量屬性有:
1)嵌套互斥量(osMutexRecursive),用于避免單一線程重復(fù)上鎖導(dǎo)致自身死鎖,當(dāng)互斥量擁有此屬性時(shí),互斥量可以重復(fù)上鎖,并且解鎖時(shí)需要上鎖的線程解鎖同樣次數(shù);
2)內(nèi)部?jī)?yōu)先級(jí)互斥量(osMutexPrioInherit),用于避免優(yōu)先級(jí)反轉(zhuǎn),當(dāng)互斥量擁有此屬性時(shí),線程將使用優(yōu)先級(jí)繼承(Priority Inheritance)的方法來(lái)提升正在使用互斥量的程序的優(yōu)先級(jí);
3)健壯互斥量(osMutexRobust),若互斥鎖的持有者出現(xiàn)異常,則解除互斥鎖鎖定,并對(duì)下一個(gè)使用此互斥鎖的線程拋出一個(gè)錯(cuò)誤。
mbedOS 中的互斥量包含一個(gè)阻塞隊(duì)列和一些指向與該互斥量有聯(lián)系的線程的指針,便于使用優(yōu)先級(jí)繼承方法避免優(yōu)先級(jí)反轉(zhuǎn)時(shí)快速改變有關(guān)的線程優(yōu)先級(jí)。在加解鎖時(shí),列表和指針也需要相應(yīng)的改變。
系統(tǒng)的結(jié)構(gòu)設(shè)計(jì)中,將對(duì)互斥量的操作通過(guò)多層函數(shù)進(jìn)行封裝。鎖定互斥量和解鎖互斥量的函數(shù)實(shí)際調(diào)用情況如圖2 所示。
圖2 互斥量加解鎖函數(shù)調(diào)用情況
在mbedOS 中互斥量加鎖實(shí)際使用的是svcRtxMutexAcquire 函數(shù),其運(yùn)行流程如圖3 所示。
圖3 svcRtxMutexAcquire 函數(shù)運(yùn)行流程
函數(shù)首先檢測(cè)線程、互斥量等參數(shù)的合法性,再檢測(cè)互斥量是否鎖定,若未鎖定則進(jìn)行加鎖,并將互斥量加入線程的互斥量列表中后,返回鎖定并成功退出。具體過(guò)程為:
若互斥量已鎖定,則檢查互斥量是否具有嵌套屬性且為同線程上鎖,若是則將互斥量計(jì)數(shù)增1 后退出,具體過(guò)程為:否則判斷互斥量是否設(shè)置等待時(shí)間,若未設(shè)置則申請(qǐng)失敗并退出,有設(shè)置等待時(shí)間則檢查互斥量是否含有內(nèi)部?jī)?yōu)先級(jí)屬性,有則提升為此互斥量加鎖的線程優(yōu)先級(jí)。之后再將被阻塞的線程插入互斥量阻塞隊(duì)列中,同時(shí)根據(jù)設(shè)置的等待時(shí)間大小將線程放入等待隊(duì)列或延時(shí)隊(duì)列中,獲取當(dāng)前優(yōu)先級(jí)最高的線程運(yùn)行。
在 mbedOS 中實(shí)際為互斥量解鎖的是svcRtxMutexRelease 函數(shù),其運(yùn)行流程如圖4 所示。
圖4 svcRtxMutexRelease 函數(shù)運(yùn)行流程
具體步驟如下:
首先檢測(cè)線程、互斥量等參數(shù)的合法性,再檢測(cè)互斥量是否已加鎖,若互斥量未加鎖則返回資源錯(cuò)誤,若互斥量已被鎖定,則將互斥鎖計(jì)數(shù)減1。
然后判斷互斥量計(jì)數(shù)是否為0,若為0 則互斥量已經(jīng)被解鎖,將互斥量從當(dāng)前互斥量的私有線程隊(duì)列中移出。
之后判斷互斥量是否含有內(nèi)部?jī)?yōu)先級(jí)屬性,有則將當(dāng)前線程優(yōu)先級(jí)調(diào)至被當(dāng)前線程占用的其他互斥量阻塞的最高優(yōu)先級(jí)線程相同。
最后若互斥量阻塞隊(duì)列中有線程,則將最高優(yōu)先級(jí)線程移出,同時(shí)將此線程從等待隊(duì)列中移出,放入就緒隊(duì)列。
函數(shù)在執(zhí)行過(guò)程中,需要使用公共資源時(shí)加入加鎖函數(shù),使用完畢后,加入解鎖函數(shù)便可實(shí)現(xiàn)對(duì)公共資源的互斥加鎖。
本文將分析STM32L431 微控制器使用SD?mbedOS工程框架運(yùn)行多線程優(yōu)先級(jí)相同情況的例程。SD?mbedOS 框架提供printf 函數(shù),該函數(shù)功能為通過(guò)串口向外打印文字信息,在程序中插入printf 語(yǔ)句即可在程序運(yùn)行過(guò)程中從串口收取程序運(yùn)行情況。
將例程使用的微控制器3 個(gè)引腳設(shè)置為GPIO 口并連接紅、綠、藍(lán)三色燈的引腳,同時(shí)創(chuàng)建3 個(gè)優(yōu)先級(jí)相同的小燈線程來(lái)控制每個(gè)燈的亮暗。將3 個(gè)線程分別設(shè)置為:紅燈線程點(diǎn)亮1 s,熄滅0.5 s,不設(shè)延時(shí);藍(lán)燈線程點(diǎn)亮1 s,熄滅0.5 s,延時(shí)0.5 s 啟動(dòng);綠燈線程點(diǎn)亮1 s,熄滅0.5 s,延時(shí)1 s 啟動(dòng)。
此外,為方便觀察程序走向,在互斥量的鎖定、解鎖函數(shù)中加入一些printf 語(yǔ)句,打印出互斥量的狀態(tài)與函數(shù)中的操作。例程運(yùn)行后可通過(guò)三色燈的顏色顯示情況以及串口輸出的關(guān)于三色燈操作的字符串來(lái)觀察實(shí)驗(yàn)現(xiàn)象,再根據(jù)串口輸出的互斥量操作信息分析并理解線程的切換以及互斥量的占用順序,下文為程序的詳細(xì)剖析。
在沒(méi)有互斥鎖的情況下,線程交替運(yùn)行,所有的線程都能自由地訪問(wèn)到三色燈,因此其中一個(gè)線程改變顏色后,其他的線程依然能夠訪問(wèn)到三色燈,并改變?nèi)珶舻念伾?,則可得到有兩種顏色的小燈同時(shí)亮起,三色燈觀察到混合色。觀察到的三色燈顏色如圖5 所示。
圖5 無(wú)互斥量的程序三色燈亮燈情況
為三色燈增加一個(gè)互斥量,每個(gè)線程在亮燈的期間視為占用互斥量,因此同時(shí)只能有一個(gè)線程使用三色燈亮燈,在線程初始化前先聲明互斥量:
之后在各個(gè)線程需要申請(qǐng)互斥量的時(shí)候使用以下語(yǔ)句申請(qǐng)互斥量:
在互斥量使用完畢時(shí),通過(guò)以下語(yǔ)句解鎖:
增加互斥量對(duì)三色燈訪問(wèn)進(jìn)行限制后,線程的執(zhí)行情況如圖6 所示。
詳細(xì)情況和串口輸出數(shù)據(jù)如下:
1)系統(tǒng)初始化
在系統(tǒng)開(kāi)機(jī)時(shí),對(duì)紅、藍(lán)、綠燈線程依次進(jìn)行初始化,為確保線程能正常被創(chuàng)建,不被其他線程打斷,在創(chuàng)建用戶線程的過(guò)程中使用互斥量,線程初始化后將線程移入就緒隊(duì)列按照優(yōu)先級(jí)依次執(zhí)行。初始化所有程序時(shí),已初始化的線程可以運(yùn)行,所有線程初始化后將阻塞主線程。對(duì)應(yīng)圖6 中步驟1~步驟4,創(chuàng)建紅燈線程時(shí)串口信息如下:
2)紅燈線程申請(qǐng)互斥量
系統(tǒng)從就緒隊(duì)列中紅燈線程運(yùn)行,紅燈線程開(kāi)始申請(qǐng)互斥量。由于此時(shí)互斥量為0,申請(qǐng)成功,線程被放入互斥量的私有線程隊(duì)列中,互斥量被放入線程的私有互斥量列表中。繼續(xù)運(yùn)行點(diǎn)亮紅燈,之后執(zhí)行延時(shí)函數(shù),線程被放入延時(shí)隊(duì)列中。對(duì)應(yīng)圖6 中步驟5~步驟9,串口接收到以下信息:
3)藍(lán)燈線程申請(qǐng)互斥量
藍(lán)燈線程在經(jīng)過(guò)初始化后被放入延時(shí)隊(duì)列等待0.5 s,之后從延時(shí)隊(duì)列中被取出放入就緒隊(duì)列。系統(tǒng)從就緒隊(duì)列中取得藍(lán)燈線程運(yùn)行,藍(lán)燈線程開(kāi)始申請(qǐng)互斥量。由于此時(shí)互斥量被占用,互斥量申請(qǐng)失敗返回錯(cuò)誤,藍(lán)燈線程被放入互斥量阻塞隊(duì)列和等待隊(duì)列中,對(duì)應(yīng)圖6 中步驟10~步驟14,串口接收到以下信息:
4)紅燈線程釋放互斥量
紅燈線程在延時(shí)結(jié)束后被移入就緒隊(duì)列,系統(tǒng)從就緒隊(duì)列中取得紅燈線程運(yùn)行。紅燈線程先熄滅紅燈,之后解鎖互斥量(互斥量變?yōu)?),線程被從互斥量私有線程列表移出,并且取出互斥量阻塞隊(duì)列中優(yōu)先級(jí)最高的第一個(gè)線程,即藍(lán)燈線程,將互斥量給這個(gè)線程,將線程移入互斥量私有線程,再將此線程從等待隊(duì)列中移出放入就緒隊(duì)列。對(duì)應(yīng)圖6 中步驟15~步驟23,串口接收到以下信息:
5)藍(lán)燈線程申請(qǐng)互斥量
系統(tǒng)從就緒隊(duì)列中取得藍(lán)燈線程運(yùn)行,藍(lán)燈得到互斥量(互斥量為1),點(diǎn)亮藍(lán)燈,執(zhí)行延時(shí)函數(shù)被放入延時(shí)隊(duì)列中。對(duì)應(yīng)圖6 中步驟24,串口接收到以下信息:
圖6 使用互斥量的程序線程調(diào)度時(shí)序
6)綠燈線程申請(qǐng)互斥量
綠燈線程延時(shí)1 s 結(jié)束后開(kāi)始運(yùn)行,發(fā)生如藍(lán)燈線程申請(qǐng)互斥量的綠燈申請(qǐng)失敗過(guò)程。對(duì)應(yīng)圖6 中步驟25~步驟29,串口接收到以下信息:
7)紅燈線程申請(qǐng)互斥量
發(fā)生如藍(lán)燈線程申請(qǐng)互斥量的紅燈申請(qǐng)失敗過(guò)程。對(duì)應(yīng)圖6 中步驟30~步驟34,串口接收到以下信息:
8)藍(lán)燈線程釋放互斥量
發(fā)生如紅燈線程釋放互斥量的釋放過(guò)程。對(duì)應(yīng)圖6 中步驟35~步驟43,串口接收到以下信息:
9)綠燈線程申請(qǐng)互斥量
發(fā)生情況5)藍(lán)燈線程申請(qǐng)互斥量的申請(qǐng)成功過(guò)程,對(duì)應(yīng)圖6 中步驟44,串口接收到以下信息:
10)藍(lán)燈申請(qǐng)互斥量
同藍(lán)燈線程申請(qǐng)互斥量申請(qǐng)失敗被阻塞,對(duì)應(yīng)圖6中步驟45~步驟46。
11)綠燈線程釋放互斥量
發(fā)生如紅燈線程釋放互斥量的釋放過(guò)程,對(duì)應(yīng)圖6中步驟47~步驟55,串口接收到以下信息:
12)紅燈線程申請(qǐng)互斥量
如藍(lán)燈線程申請(qǐng)互斥量的申請(qǐng)成功過(guò)程,對(duì)應(yīng)圖6中步驟56。
之后重復(fù)以上除初始化環(huán)節(jié),三色燈觀察到的亮燈情況為紅、藍(lán)、綠燈依次點(diǎn)亮1 s,無(wú)顏色混合現(xiàn)象,如圖7 所示。
圖7 有互斥量的程序三色燈亮燈情況
互斥量在多線程并發(fā)運(yùn)行時(shí),能夠保護(hù)公共資源,對(duì)限制線程的自由調(diào)度有著重要的作用。在實(shí)時(shí)操作系統(tǒng)中實(shí)現(xiàn)的互斥量中通常帶有更多屬性來(lái)避免一些錯(cuò)誤,因此程序調(diào)度也變得更加復(fù)雜。本文主要針對(duì)mbedOS 中的互斥量,對(duì)其數(shù)據(jù)結(jié)構(gòu)、操作函數(shù)功能進(jìn)行分析,最后通過(guò)一個(gè)三色燈的運(yùn)行例程,使用SD?mbedOS 在STM32L431 上對(duì)互斥量在系統(tǒng)調(diào)度時(shí)的作用進(jìn)行詳細(xì)剖析。通過(guò)剖析,讀者可快速理解mbedOS對(duì)于使用互斥量線程的調(diào)度機(jī)制,并類比實(shí)現(xiàn)在不同的微處理器上使用互斥量進(jìn)行多線程編程,同時(shí)也可為比較分析其他實(shí)時(shí)操作系統(tǒng)的互斥量提供基礎(chǔ)。