臨界資源
互斥訪問
進程共享各種資源,然而有很多資源一次只能供一個進程使用。一次僅允許一個進程使用的資源稱為臨界資源,如輸入機、打印機、磁帶機等許多物理設備都屬於臨界資源。訪問這樣的臨界資源就需要通過互斥共享方式,在一段時間內只允許一個進程訪問該資源。
臨界區
每個進程中訪問臨界資源的代碼稱為臨界區(critical section),實現進程對臨界資源的互斥訪問可以轉變為進程互斥地進入臨界區。每個進程在進入臨界區之前,需要先對臨界資源進行檢查。如果此刻臨界資源未被訪問就允許臨界區的訪問,並設置它正被訪問的標志;如果此刻該臨界資源正被某進程訪問,則本進程不能進入臨界區;進程訪問完臨界資源后需要歸還,並且要去掉被訪問的標志。
因此需要在臨界區前面增加一段用於檢查的進入區(entry section)代碼,在后面也要加上一段退出區(exit section)*的代碼,其它部分的代碼稱為剩余區**。訪問臨界資源的循環進程可以用偽代碼描述:
while(TURE)
{
進入區
臨界區
退出區
剩余區
}
進程同步
需要同步的動機
OS 引入進程后可以讓程序並發執行,提高運行效率,但是這也增加了系統的復雜性。如果不能對進程進行管理,在多個進程對臨界資源進行爭奪時就會帶來很多問題。因此為了保證多個進程能有條不紊地運行,需要提供一些進程同步的方式。
進程同步機制是對多個相關進程在執行次序上進行協調,使並發執行的諸進程之間能按照一定的規則(或時序)共享系統資源。
進程的制約關系
進程之間可能存在着以下兩種形式的制約關系,由於進程的異步性,如果缺乏進程同步會產生對共享變量或數據結構等資源不正確的訪問次序,從而造成進程每次執行結果的不一致。
制約關系 | 說明 |
---|---|
間接制約關系 | 對於臨界資源,必須保證多個進程對其互斥地訪問,這些進程形成了間接相互制約關系 |
直接相互制約關系 | 某些應用程序建立了多個進程完成同一項任務,這些進程就構成了直接制約關系 |
同步的原則
實現進程互斥地進入自己的臨界區而采用的同步機制應遵循以下原則:
原則 | 說明 |
---|---|
空閑讓進 | 當無進程處於臨界區時,應允許一個請求進入臨界區的進程立即進入 |
忙則等待 | 當已有進程進入臨界區時,其它試圖進入臨界區的進程必須等待 |
有限等待 | 對要求訪問臨界資源的進程,應保證在有限時間內能進入自己的臨界區 |
讓權等待 | 當進程不能進入自己的臨界區時,應立即釋放處理機 |
實現同步的原理
在對臨界區進行管理時,可以將標志看做一個鎖。初始時鎖是打開的,每個要進入臨界區的進程必須先對鎖進行測試,當鎖未開時必須等待,直至鎖被打開。當鎖是打開的時候,應立即把其鎖上來阻止其它進程進入臨界區。為防止多個進程同時測試到鎖為打開的情況,測試和關鎖操作必須是連續的。
硬件同步機制
基於軟件的方法存在很大的局限性,同步也可以通過計算機提供的一些的硬件指令實現。但是當臨界資源忙碌時,其它訪問進程得不斷地調用硬件進行測試,處於一種“忙等”狀態不符合“讓權等待”的原則,造成處理機時間的浪費。
關中斷
在進入鎖測試之前關閉中斷,直到完成鎖測試並上鎖之后才能打開中斷。關中斷后進程在臨界區執行期間,計算機系統不響應中斷,從而不會引發進程調度,也就不會發生進程或線程切換。
關中斷的方法是一種非常簡單的方法,但是關中斷時間過長會影響系統效率。且關中斷方法也不適用於多 CPU
系統,因為一個 CPU 關中斷后其他 CPU 還是可以訪問臨界資源。
Test-and-Set 指令
可以使用 Test-and-Set(測試並建立)指令實現互斥,TS 指令的偽代碼如下:
boolean TS(boolean *lock){
{
Boolean old;
old = *lock;
*lock = TRUE;
return old;
}
可以為每個臨界資源設置一個布爾變量 lock 代表該資源的狀態,初值為 FALSE 表示該臨界資源空閑。進
程在進入臨界區之前先用 TS 指令測試 lock,如果其值為 FALSE 則可以進入,並將 TRUE 值賦予 1ock。此時 lock = TRUE 表示資源被占用,任何進程檢查 lock 后不能進入臨界區。利用 TS 指令實現進程同步的偽代碼如下:
do{
進入區
while TS(&lock);
臨界區
lock = FALSE;
剩余區
}while(TRUE);
Swap 指令
Swap (對換)指令用於交換兩個字的內容,對換指令的偽代碼如下:
void swap(boolean *a, boolean *b)
boolean temp;
temp = *a;
*a = *b;
*b = temp;
}
可以每個臨界資源設置一個初值為 false 的全局布爾變量 lock,在每個進程中使用一個局部布爾變量 key。Swap 指令實現進程互斥的偽代碼如下:
do{
key = TRUE;
do{
swap(&lock,&key);
}while(key!=FALSE);
臨界區
lock = FALSE;
剩余區
} while(TRUE);
信號量機制
信號量就是用一個變量來表示系統中某種資源的數量,可以利用這種機制來實現同步。
整型信號量
整型信號量定義為一個用於表示資源數目的整型量 S,S 除了初始化外僅能通過兩個標准的原子操作 wait(S) 和 signal(S)來訪問,這兩個操作也可以被稱為 P、V 操作。wait 操作的偽代碼如下:
wait(S){
while(S<=0);
S--;
}
signal 操作的偽代碼如下:
signal(S){
S++;
}
當一個進程在修改某信號量時,由於 P、v 操作是原子操作,因此沒有其它進程可同時對該信號量進行修改。
記錄型信號量
當整型信號量 s≤0 時就會不斷地測試直到它大於 0,因此該機制並未遵循“讓權等待”的准則。但是采取了“讓權等待”的策略后,又會出現多個進程等待訪問同一臨界資源的情況。因此除了需要一個用於代表資源數目的整型變量 value,還應增加一個進程鏈表指針 list 用於鏈接的所有等待進程,這類信號量稱之為記錄型信號量。
typedef struct{
int value;
struct process_control_block *list;
}semaphore;
將原本的整型變量修改為上述結構體定義的變量后,wait(S) 操作使系統中可供分配的該類資源數減少一個,表示進程請求一個單位的資源,偽代碼如下:
wait(semaphore *S){
S->value--;
if(S->value < 0)
block(S->list);
}
signal(S) 操作使可分配的資源增加一個,表示進程釋放一個單位的資源,偽代碼如下:
signal(semaphore *S) {
S->value++;
if(S->value <= 0)
wakeup(S->list);
}
AND 型信號量
如果一個進程需要多個臨界資源才能進行工作,在只獲得部分資源時會導致工作無法展開且占用着資源無法釋放的情況。AND 型信號量的基本思想是一次性分配進程在整個運行過程中需要的所有資源,待進程使用完后再一起釋放。只要尚有一個資源未能分配給進程,其它所有可能為之分配的資源也不分配給它。
wait 操作就需要加入 AND 的條件對多個資源進行判斷,偽代碼如下:
wait(S1,S2,...,Sn){
while(TRUE)
if(Si >= 1 &&...&& Sn >= 1){
for(int i = 1; i <= n; i++)
Si--;
break;
}
else {
進程加入等待隊列
}
}
}
相應的 signal 操作需要釋放進程占用的所有資源,偽代碼如下:
Ssignal(S1,S2,..., Sn) {
while(TRUE){
for(int i = 1; i <= n; i++){
Si++;
將等待該資源的進程移入就緒隊列
}
}
}
信號量集
前面的記錄型信號量機制中,wait(S) 或 signal(S) 操作僅能對某類臨界資源進行一個單位的申請或釋放。當一次需要 N 個單位時要進行 N 次操作,不僅效率低,還可能出現獲得部分資源導致工作無法展開且占用着資源無法釋放的情況。在有些情況下,當所申請的資源數量低於某一下限值時要進行管制,不分配該類資源來保證安全性。因此在分配臨界之前,都必須測試資源的數量,判斷是否大於可分配的下限值。
因此可以提出信號量集,也就是進程對信號量 S 的測試值可以從 1 改成資源的分配下限值 t,當 S ≥ t 時才能進行資源分配。進程對該資源的需求值為 d,分配資源時進行 S = S - d 一次分配多個資源。對應的 Swait 和 Ssignal 格式為:
Swait(S1, t1, d1,..., Sn, tn, dn);
Ssignal(S1, d1,..., Sn, dn);
信號量的應用
實現進程互斥
為該臨界資源設置一個初始值為 1 的互斥信號量 mutex,然后將各進程訪問該資源的臨界區置於 wait(mutex) 和 signal(mutex)。這樣每個欲訪問該臨界資源的進程在進入臨界區之前,都要先執行 wait 操作判斷資源是否可用,臨界區運行完畢后需要用 signal 操作釋放資源。
semaphore mutex=1;
PA(){
其他代碼
wait(mutex);
臨界區
signal(mutex);
剩余區
}
PB(){
其他代碼
wait(mutex);
臨界區
signal(mutex);
剩余區
}
實現前趨關系
設有兩個並發執行的進程 P1 和 P2 分別有語句 S1 和 S2,希望在 S1 執行后再執行 S2。如果進程之間必須保證執行的順序是一前一后的,可以設置一個初始值為 0 的同步信號量 S,然后在前操作之后執行 signal(s),在后操作之前執行 wait(s)。前操作執行完之后 s 的會加一,此時后操作調用 wait 滿足了條件即可開始執行。
semaphore s = 0;
PA(){
S1
signal(s);
}
PB(){
wait(s);
S2
}
管程
提出管程的動機
信號量機制需要每個要訪問臨界資源的進程都必須編寫 wait(S) 和 signal(S) 的代碼,會導致同步操作產生冗余,加大系統管理的復雜性。而系統中的各種資源和軟件資源可用數據結構來抽象地描述,因此可以用共享數據結構表示系統中的共享資源,並且將對該共享數據結構實施的特定操作定義為一組過程。這樣進程可以通過這些過程,對共享資源執行申請、釋放和其它操作。
管程的定義
代表共享資源的數據結構,以及由對該共享數據結構實施操作的一組過程,組成的資源管理程序共同構成了一個操作系統的資源管理模塊,稱之為管程。對於請求訪問共享資源的諸多並發進程,可以根據資源情況接受或阻塞,確保每次僅有一個進程進入管程。通過管程達成該進程對共享資源所有訪問的統一管理,有效地實現進程互斥。管程和面向對象編程的對象很相似,由上述的定義可知,它由四部分組成:
- 管程的名稱;
- 共享數據結構說明;
- 對該數據結構進行操作的一組過程;
- 對共享數據設置初始值的語句。
管程的語法描述如下:
Monitor monitor_name //管程名
{
share variable declarations; //共享變量說明
cond declarations; //條件變量說明
//對數據結構操作的過程
public:
void P1(……){
}
void(){
initialization code; //初始化代碼
}
}
管程的特點
- 模塊化:管程是一個基本程序單位,可以單獨編譯;
- 抽象數據類型:管程中不僅有數據,也有對數據的操作;
- 信息掩蔽:管程中的數據結構只能被管程中的過程訪問。
參考資料
《計算機操作系統(第四版)》,湯小丹 梁紅兵 哲鳳屏 湯子瀛 編著,西安電子科技大學出版社