《操作系統概念》第六章 6.5
信號量S十個整數變量,除了初始化外,它只能通過兩個標准原子操作:wait()和signal()來訪問。
Wait()的定義可表示為:
wait(S) { while(S <= 0) ;// no-op S--; }
signal的定義可表示為
signal(S) { S++; }
在wait()和signal()操作中,對信號量整型值的修改必須不可分地執行,即當一個進程修改信號量值時,不能有其他進程同時修改同一信號量的值。對於wait(S),對S的整型值的測試(S<=0)和對其可能的修改(S--),也必須不被中斷地執行。
操作系統區分計數信號量和二進制信號量。計數信號量的值域不受限制。而二進制信號量的值只能為0或1.。有的系統把二進制信號量稱為互斥鎖。
計數信號量可以用來控制訪問具有若干實例的某種資源。該信號量初始化為可用資源的數量。當每個進程需要使用資源時,需要對該信號量執行wait()操作(減少信號量的計數)。當進程釋放資源時,需要對該信號量執行signal()操作(增加信號量的計數)。
這里所定義的信號量的主要缺點是都要求忙等待(busy waiting)。當一個進程位於其臨界值內時,任何其他試圖進入其臨界區的進程都必須在其進入代碼中連續地循環。這種類型的信號量也稱為自旋鎖,這是因為進程在其等待時還在運行(自旋鎖有其優點,進程在等待鎖時不進行上下文切換,而上下文切換可能需要花費相當長的時間。自旋鎖常用於多處理器系統,這樣一個進程在一個處理自旋鎖時,另一線程可在另一處理器上在其臨界區內執行。)
克服忙等:修改信號量的wait()和signal()的定義,當一個進程執行wait()時,發現信號量的值不為正,則它不是忙等而是阻塞自己。阻塞操作將一個進程放入到與信號量相關的等待隊列中,並將該進程的狀態切換成等待狀態。可以在其它進程執行signal()操作之后被重新執行,該進程的重新執行是通過wakeup()來進行的,將進程從等待狀態切換到就緒狀態。
typedef struct {
int value; struct process *list; //進程鏈表。當一個進程必須等待信號量時,就加入到進程鏈表上。 } semaphore; wait(semaphore *S) { S->value--; If (S->value < 0) { add this process to S->list; block(); } } signali(semaphore *S) { S->value++; if (S->value <= 0) { Remove a process P from S->list; wakeup(P); } }
等待進程的鏈表可以利用進程控制塊PCB中的一個連接域來實現。
必須確保沒有連個進程能同時對同一信號量執行操作wait()和signal()。有兩種方法:1.在單處理器環境下,可以在執行wait()和signal()操作時簡單地禁止中斷。2.在多處理器環境下,必須禁止每個處理器的中斷,但是這個很難,還會嚴重影響性能。
這里對wait()和signal()操作的定義,並沒有完全取消忙等,而是取消了應用程序進入臨界區的忙等。而且,將忙等限制在操作wait()和signal()的臨界區內。
死鎖與飢餓:
具有等待隊列的信號量的實現可能導致這樣的情況:兩個或多個進程無限地等待一個事件,而這個事件只能由這些等待進程之一來產生。這里主要關心的事件是資源獲取和釋放。當出現這樣的狀態時,這些進程就稱為死鎖。
與死鎖相關的另一個問題是無限期阻塞(indefinite blocking)或飢餓(starvation),即進程在信號量內無限期等待。如果對與信號量相關的鏈表按LIFO順序來增加和移動進程,那么可能會發生無限期阻塞。