信號量機構是一種功能較強的機制,可用來解決互斥與同步的問題,它只能被兩個標准的原語wait(S)和signal(S)來訪問,也可以記為“P操作”和“V操作”。
原語是指完成某種功能且不被分割不被中斷執行的操作序列,通常可由硬件來實現完成不被分割執行特性的功能。如前述的“Test-and-Set”和“Swap”指令,就是由硬件實現的原子操作。原語功能的不被中斷執行特性在單處理機時可由軟件通過屏蔽中斷方法實現。
原語之所以不能被中斷執行,是因為原語對變量的操作過程如果被打斷,可能會去運行另一個對同一變量的操作過程,從而出現臨界段問題。如果能夠找到一種解決臨界段問題的元方法,就可以實現對共享變量操作的原子性。
整型信號量
整型信號量被定義為一個用於表示資源數目的整型量S,wait和signal操作可描述為:
wait(S){ while (S<=0); S=S-1; } signal(S){ S=S+1; }
wait操作中,只要信號量S<=0,就會不斷地測試。因此,該機制並未遵循“讓權等待” 的准則,而是使進程處於“忙等”的狀態。
記錄型信號量
記錄型信號量是不存在“忙等”現象的進程同步機制。除了需要一個用於代表資源數目的整型變量value外,再增加一個進程鏈表L,用於鏈接所有等待該資源的進程,記錄型信號量是由於釆用了記錄型的數據結構得名。記錄型信號量可描述為:
typedef struct{ int value; struct process *L; } semaphor
相應的wait(S)和signal(S)的操作如下:
void wait (semaphore S) { //相當於申請資源 S.value--; if(S.value<0) { add this process to S.L; block(S.L); } }
wait操作,S.value--,表示進程請求一個該類資源,當S.value<0時,表示該類資源已分配完畢,因此進程應調用block原語,進行自我阻塞,放棄處理機,並插入到該類資源的等待隊列S.L中,可見該機制遵循了“讓權等待”的准則。
void signal (semaphore S) { //相當於釋放資源 S.value++; if(S.value<=0){ remove a process P from S.L; wakeup(P); } }
signal操作,表示進程釋放一個資源,使系統中可供分配的該類資源數增1,故S.value++。若加1后仍是S.value<=0,則表示在S.L中仍有等待該資源的進程被阻塞,故還應調用wakeup 原語,將S.L中的第一個等待進程喚醒。
利用信號量實現同步
信號量機構能用於解決進程間各種同步問題。設S為實現進程P1、P2同步的公共信號量,初值為0。進程P2中的語句y要使用進程P1中語句x的運行結果,所以只有當語句x執行完成之后語句y才可以執行。其實現進程同步的算法如下:
semaphore S = 0; //初始化信號量 P1 ( ) { // … x; //語句x V(S); //告訴進程P2,語句乂已經完成 } P2()){ // … P(S) ; //檢查語句x是否運行完成 y; // 檢查無誤,運行y語句 // … }
利用信號量實現進程互斥
信號量機構也能很方便地解決進程互斥問題。設S為實現進程Pl、P2互斥的信號量,由於每次只允許一個進程進入臨界區,所以S的初值應為1(即可用資源數為1)。只需把臨界區置於P(S)和V(S)之間,即可實現兩進程對臨界資源的互斥訪問。其算法如下:
semaphore S = 1; //初化信號量 P1 ( ) { // … P(S); // 准備開始訪問臨界資源,加鎖 // 進程P1的臨界區 V(S); // 訪問結束,解鎖 // … } P2() { // … P(S); //准備開始訪問臨界資源,加鎖 // 進程P2的臨界區; V(S); // 訪問結束,解鎖 // … }
互斥的實現是不同進程對同一信號量進行P、V操作,一個進程在成功地對信號量執行了 P操作后進入臨界區,並在退出臨界區后,由該進程本身對該信號量執行V操作,表示當前沒有進程進入臨界區,可以讓其他進程進入。
利用信號量實現前驅關系
信號量也可以用來描述程序之間或者語句之間的前驅關系。圖2-8給出了一個前驅圖,其中S1, S2, S3, …, S6是最簡單的程序段(只有一條語句)。為使各程序段能正確執行,應設置若干個初始值為“0”的信號量。例如,為保證S1 -> S2、 S1 -> S3的前驅關系,應分別設置信號量a1、a2。同樣,為了保證 S2 -> S4、S2 ->S5、S3 -> S6、S4 -> S6、S5 -> S6,應設置信號量bl、b2、c、d、e。

圖2-8 前驅圖舉例
實現算法如下:
semaphore al=a2=bl=b2=c=d=e=0; //初始化信號量 S1() { // … V(al); V(a2) ; //S1已經運行完成 } S2() { P(a1); //檢查S1是否運行完成 // … V(bl); V(b2); // S2已經運行完成 } S3() { P(a2); //檢查S1是否已經運行完成 // … V(c); //S3已經運行完成 } S4() { P(b1); //檢查S2是否已經運行完成 // … V(d); //S4已經運行完成 } S5() { P(b2); //檢查S2是否已經運行完成 // … V(e); // S5已經運行完成 } S6() { P(c); //檢查S3是否已經運行完成 P(d); //檢查S4是否已經運行完成 P(e); //檢查S5是否已經運行完成 // …; }
分析進程同步和互斥問題的方法步驟
1) 關系分析。找出問題中的進程數,並且分析它們之間的同步和互斥關系。同步、互斥、前驅關系直接按照上面例子中的經典范式改寫。
2) 整理思路。找出解決問題的關鍵點,並且根據做過的題目找出解決的思路。根據進程的操作流程確定P操作、V操作的大致順序。
3) 設置信號量。根據上面兩步,設置需要的信號量,確定初值,完善整理。