挑戰408——操作系統(10)——信號量與PV操作

處理同步和互斥的問題,除了用到之前的軟件和硬件的方法,用的最多的還是信號量機制
信號量機制是通過定義表示共享資源使用的特殊變量以及兩個標準的原語(P操作和V操作),來實現同步和互斥的。
根據信號量的數據類型不同,我們將信號量分爲整型信號量和記錄型信號量

整型信號量

整型信號量是一種被定義爲用來表示資源數的整型量S,其值只能被wait和signal操作。S的初始值被設置爲可訪問的資源數的數量。當用整型信號量用於互斥的時候,S被初始化爲1,表示臨界資源不能被多個進程同時訪問。wait,signal操作代表表示如下:

//整型信號量
int s;
wait(s){
	while(s < 0){
//若s的初值爲1,下面的語句就會使s = 0,表示可用此時沒有可用資源數
//所以此刻申請該資源進程就會阻塞,也就是不允許多個進程同時使用
//這就是爲什麼用於互斥的時候,互斥信號量要設置爲1
		s = s - 1;
	}
}//相當於p操作,進程申請一個資源

而對於signal操作:

signal(s){
	s = s + 1;
}//與wait操作相反,表明這個時候釋放一個資源

記錄型信號量

但是當多個進程需要訪問該資源的時候,如何解決呢?
記錄型信號量中,包括一個整型的數值S,代表可用的資源數和一個進程鏈表queue,用於鏈接所有等待該資源的進程,定義其數據結構如下:

struct semaphore{
	int value;//代表可用的資源數
	struct PCB *queue;//用於鏈接所有等待該資源的進程
}

信號量只能通過兩個原語操作(P,和V)來訪問它們,而PV對應的代碼操作如下:

/P操作的代碼表示
void wait(semaphore s){
	s.value = s.value - 1;//可用資源數減一
//若可用資源數小於0,阻塞該進程,並投入等待隊列(注意不是就緒隊列)
	if(s.value < 0) block(s.queue);
}


//V操作的代碼表示
void signal(semaphore s){
	s.value = s.value + 1;//可用資源數加一
	//若此時s.value <=0,表明在等待隊列中有等待該資源的進程被阻塞
	//故將其從等待隊列中喚醒,並投入到就緒隊列中
	if(s.value <= 0) wakeup(s.queue);
}

顯然我們可以得出這樣一個結論:P操作意味着釋放一個資源,V操作意味着釋放一個資源。當s.value <= 0時,其|s.value <= 0|的值代表的是等待隊列中等待該資源的進程數。如s.value = -8,那麼表示等待隊列中有8個進程正在等待這個臨界資源,s.value = 0,則表示資源剛好使用完畢。

利用信號量解決互斥問題

看一個程序段:

//程序P1
{
	R1 = COUNT;
	R1 = 1;
	COUNT = R1;
}

//程序P2
{
	R2 = COUNT;
	R2 = 1;
	COUNT = R2;
}

假設兩段程序共享一個變量COUNT,那麼如果程序併發執行,則可能出現兩種結果,COUNT = 6或者COUNT = 7;(就不分析了),但是結果肯定是唯一的。造成這個原因我們之前說過,就是因爲P1在訪問臨界資源count的時候,P2也在訪問,導致前後的值不一致。所以解決的方法就是當其中一個進程在訪問的時候,將這個變量保護起來。我們就用信號量機制來表示一下;
下面的代碼是我假設執行順序是P1 -> P2.,過程看我的註釋。反過來的分析是一樣的

semaphore mutex = 1;//設置初始互斥信號量爲1

//程序P1
{
	P(mutex);//申請一個資源,此時mutex -=1(即 = 0)
//下面的三句代碼是P1的臨界區	
	R1 = COUNT;
	R1 = 1;
	COUNT = R1;
	//釋放一個資源,此時mutex += 1,即恢復1
	v(mutex);
}

//程序P2
{
	//申請一個資源,此時如果P1在使用,那麼mutex = 0進程阻塞
	//下面的代碼都不執行。知道mutex的值大於0爲止()即P1釋放
	//相應資源的時候
	P(mutex);
	//P2的臨界區
	R2 = COUNT;
	R2 = 1;
	COUNT = R2;
	//釋放資源
	V(mutex);
}

這樣,無論按什麼樣的執行順序,COUNT的結果都是7,實現了可再現性。
注意,在解決互斥問題的時候,申請(P)的資源最後一定要釋放(V)出來,否則後序的進程不能執行

信號量解決同步問題

同步,是指進程間要按照一定的順序進行。有些程序並不滿足併發的條件(比如我們前面舉過的例子),因爲後面的進程推進必須依賴前面進程的結果。因此必要的時候要控制進程的先後執行順序。
我們通常用前驅圖來表示進程間的同步關係。如下圖:
在這裏插入圖片描述
顯然,我們知道P2,P3可以併發執行,但是P2和P4卻不可以。我們用PV操作來描述這一過程(我們一開始設置信號量S1 = S2 = S3 = 0)

分析:
對於P1,它是進程的起始,一定要先於P2,P3,P4執行,而P2,P3併發執行,都要用到P1所提供的資源。所以P1纔會先執行代碼,然後釋放兩個資源供P2,P4使用.這裏有個問題,就是爲什麼設置信號量爲0?因爲我們第一步要V(s1),初始值爲0,V一下就加1,這個時候纔有資源給P2去申請(P)。
對於P2,它先向P1申請到要的資源後,進入臨界區運行,然後釋放資源給P4繼續運行。
對於P3,P4,他們都順利申請到了想要的資源,但是不再釋放資源了,因爲已經沒有了後續需要使用資源的進程了。
所以,我們可以得出結論,PV操作是成對出現的,有對某個資源的P操作,就一定要對某個資源的V操作

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章