期刊VIP學術指導 符合學術規范和道德
保障品質 保證專業,沒有后顧之憂
摘要:多任務系統中當一個共享地址空間簡單地用于數據交換時,為避免競爭,需要對內存的訪問上鎖,以保證訪問互斥進行。實現資源互斥訪問的方法很多,不同之處僅在于互斥的范圍和程度。這些方法包括禁止中斷,禁止搶占和使用信號量等對資源上鎖。
關鍵詞:多任務,任務優先級,搶占,調度時機,調度器
1 引言
互斥機制最強有力的方法是禁止中斷,這種上鎖保證了對CPU的獨占訪問。在互斥期間,即使外部事件產生而引發相應的中斷,系統也不會切換到相應的中斷服務程序(ISR),也能保證任務之間的互斥。因此在上鎖期間,它可能造成系統對外部事件反應遲鈍。這對于大多數實時系統而言,系統的實時性也就得不到保證,因而不適合作為一種通用的互斥方法。然而當涉及到任務和中斷服務程序共享數據時,中斷上鎖又是唯一的方法。但是在任何情況下,應該使中斷上鎖時間盡量短,這也是所有實時系統的基本要求。
從本質上講,信號量機制比禁止中斷或禁止搶占提供更精確的互斥粒度,但是在使用時需要注意優先級繼承,刪除安全性和遞歸使用等問題,并且當一個任務需要同時獲取多個信號量時更要注意避免系統的死鎖問題。因此,一般實時嵌入式操作系統都會為用戶提供多種互斥手段,以適應不同的使用場合。但對于禁止搶占(禁止調度)這種方法而言,有些系統支持,如UC/OS--II,VxWorks等,而有些系統未必支持,如PSOS等,為了達到禁止搶占這種效果,并且不受具體系統的約束,本文提出了一種新的禁止搶占方法——最高優先級法。
2 禁止搶占的基本原理
如果任務不與中斷服務子程序共享變量或數據結構,可以使用先禁止然后允許任務切換的手段。此時雖然任務切換禁止了,但中斷還是開著的。如果這時中斷來了,中斷服務子程序會在這一臨界區內立即執行。中斷服務子程序結束時,即使有更高優先級的任務已經進入就緒態,內核還是返回到原先被中斷了的任務,直到執行完給任務切換開鎖函數,內核再看看有沒有優先級更高的任務被中斷服務子程序激活而進入就緒態,如果有則做任務切換。此種機制比中斷上鎖要弱一些。
3 最高優先級法
3.1最高優先級法的基本原理
在實時嵌入式系統中,各個應用任務依據實時性.重要性被賦予了不同的優先級,內核嚴格按照優先級的高低來調度任務,高優先級的任務能搶占低優先級的任務以滿足實時性的要求。另外,實時嵌入式操作系統一般都提供了動態更改任務優先級的系統調用。我們可以將系統中的最高優先級(HiPriority)預留下來,當某任務需要禁止搶占時,將該任務的優先級提升到最高優先級(PriorityProtect),在該任務將其自身的優先級設回原優先級(UnPriorityProtect)之前,系統中的其它任務不會搶占該任務,該任務可以放心地完成臨界區的操作。使用這種方法也可以選擇基于優先級的時間片輪轉調度。輪轉調度可以使優先級相同處于就緒態的任務公平地分享使用CPU。按照優先級調度的原理,考察我們的最高優先級法:當任務處于最高優先級時即使時間片到期任務也不會切換出去,因為就緒隊列里優先級最高(為HiPriority)的任務只有一個,不管它是否用完時間片,其它低優先級的任務都無法搶占它。
3.2最高優先級法的普通實現
我們先來分析下面的一種在實際開發工作中會遇到的情況,任務A中有一段臨界區代碼,并且假設該臨界區代碼不包含因等待資源而不得不讓出CPU的可能:
PriorityProtect();
……//代碼1
Func();//函數調用
……//代碼2
UnPriorityProtect();
而函數Func()如下定義:
Func()
{
……//代碼3
PriorityProtect();
……//代碼4
UnPriorityProtect();
……//代碼5
}
當Func()執行完時,任務A的優先級被設回了原先的優先級,代碼2將受不到保護!因此,我們需要對函數PriorityProtect()和UnPriorityProtect()進行改造:函數PriorityProtect()需返回設置最高優先級之前任務的優先級,而函數UnPriorityProtect()卻不一定總是設置任務原先的優先級,它設置的是與之配對的PriorityProtect()返回的優先級。這兩個函數都是成對使用的,不管嵌套使用了多少次都不會出錯,舉例如下:
OldPriority=PriorityProtect();
……//代碼1
Func();//函數調用
……//代碼2
UnPriorityProtect(OldPriority);
而函數Func()如下定義:
Func()
{
int OldPriority;
……//代碼3
OldPriority =PriorityProtect();
……//代碼4
UnPriorityProtect(OldPriority);
……//代碼5
}
這樣在函數Func()執行完后任務仍處于最高優先級狀態,代碼2仍受到保護,符合程序員的意圖,程序員不必擔心會發生意外。但是這種方法的效率有點低下,執行Func()時已經是最高優先級狀態了,卻還要再設置,浪費了CPU時間,在嵌套次數較多時效率問題將更加嚴重。另一方面我們不能輕易將函數Func()中的互斥手段去掉,因為這要考慮在其它沒提供互斥手段的情況下調用Func()。基于上述原因,我們引入一種高效的方法——嵌套計數法。
3.3最高優先級法的嵌套計數實現
先來定義操作規則:COUNT為系統中的全局變量,初始化為0。
PriorityProtect()
{
if(!COUNT) ……(1)
利用具體的系統調用把當前任務設成最高優先級 ……(2)
COUNT++; ……(3)
}
UnPriorityProtect()
{
COUNT--;……(4)
if(!COUNT) ……(5)
利用具體的系統調用把當前任務設成原先的優先級……(6)
}
再來分析一下這種方法的安全性。COUNT是全局變量,對它的訪問要防止競爭條件。但巧妙的是一旦我們把任務用操作系統原語調用設成最高優先級后,對COUNT的訪問將是獨占的;一個任務首次調用PriorityProtect()時在語句(1)處有競爭條件,但一次只能有一個任務通過(2)處的原語成為最高優先級,只要我們正確地配對使用這兩個函數,當這個任務最終退出最高優先級狀態時COUNT必為0,并不影響其它任務在(1)處的判斷。這種方法的高效性也是明顯的,在嵌套使用的里層僅僅進行嵌套計數的計算,并不用進行優先級的設置。
4 結論
盡管不同的實時嵌入式操作系統給用戶提供的系統調用接口不盡相同,但是它們所準遵循的基本原理都是相同的。上述關于禁止任務搶占的普通實現以及嵌套計數實現提供的都是實現思想,在實際應用中很容易結合所使用的具體的操作系統寫出PriorityProtect()和UnPriorityProtect()的實現代碼。
[1] Labrosse Jean J,uc/OS-II-源碼公開的實時嵌入式操作系統,邵貝貝譯,北京:中國電力出版社,2001.
[2] 孔祥營等,嵌入式實時操作系統VxWorks及其開發環境Tornado,北京:中國電力出版社,2001.