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