1. 程式人生 > >訊號量與PV操作

訊號量與PV操作

在計算機作業系統中,PV操作是程序管理中的難點。
首先應弄清PV操作的含義:PV操作由P操作原語和V操作原語組成(原語是不可中斷的過程),對訊號量進行操作,具體定義如下:
    P(S):①將訊號量S的值減1,即S=S-1;
           ②如果S³0,則該程序繼續執行;否則該程序置為等待狀態,排入等待佇列。
    V(S):①將訊號量S的值加1,即S=S+1;
           ②如果S>0,則該程序繼續執行;否則釋放佇列中第一個等待訊號量的程序。
PV操作的意義

:我們用訊號量及PV操作來實現程序的同步和互斥。PV操作屬於程序的低階通訊。

什麼是訊號量?訊號量(semaphore)的資料結構為一個值和一個指標,指標指向等待該訊號量的下一個程序。訊號量的值與相應資源的使用情況有關。當它的值大於0時,表示當前可用資源的數量;當它的值小於0時,其絕對值表示等待使用該資源的程序個數。注意,訊號量的值僅能由PV操作來改變。
     一般來說,訊號量S³0時,S表示可用資源的數量。執行一次P操作意味著請求分配一個單位資源,因此S的值減1;當S<0時,表示已經沒有可用資源,請求者必須等待別的程序釋放該類資源,它才能執行下去。而執行一個V操作意味著釋放一個單位資源,因此S的值加1;若S£0,表示有某些程序正在等待該資源,因此要喚醒一個等待狀態的程序,使之執行下去。

    利用訊號量和PV操作實現程序互斥的一般模型是:
程序P1              程序P2           ……          程序Pn
……                  ……                           ……
P(S);              P(S);                         P(S);
臨界區;             臨界區;                        臨界區;
V(S);              V(S);                        V(S);
……                  ……            ……           ……

    其中訊號量S用於互斥,初值為1。
    使用PV操作實現程序互斥時應該注意的是:
    (1)每個程式中使用者實現互斥的P、V操作必須成對出現,先做P操作,進臨界區,後做V操作,出臨界區。若有多個分支,要認真檢查其成對性。
    (2)P、V操作應分別緊靠臨界區的頭尾部,臨界區的程式碼應儘可能短,不能有死迴圈。
   (3)互斥訊號量的初值一般為1。

    利用訊號量和PV操作實現程序同步
PV操作是典型的同步機制之一。用一個訊號量與一個訊息聯絡起來,當訊號量的值為0時,表示期望的訊息尚未產生;當訊號量的值非0時,表示期望的訊息已經存在。用PV操作實現程序同步時,呼叫P操作測試訊息是否到達,呼叫V操作傳送訊息。
    使用PV操作實現程序同步時應該注意的是:

    (1)分析程序間的制約關係,確定訊號量種類。在保持程序間有正確的同步關係情況下,哪個程序先執行,哪些程序後執行,彼此間通過什麼資源(訊號量)進行協調,從而明確要設定哪些訊號量。
    (2)訊號量的初值與相應資源的數量有關,也與P、V操作在程式程式碼中出現的位置有關。
    (3)同一訊號量的P、V操作要成對出現,但它們分別在不同的程序程式碼中。

【例1】生產者-消費者問題
在多道程式環境下,程序同步是一個十分重要又令人感興趣的問題,而生產者-消費者問題是其中一個有代表性的程序同步問題。下面我們給出了各種情況下的生產者-消費者問題,深入地分析和透徹地理解這個例子,對於全面解決作業系統內的同步、互斥問題將有很大幫助。

(1)一個生產者,一個消費者,公用一個緩衝區。
定義兩個同步訊號量:
empty——表示緩衝區是否為空,初值為1。
   full——表示緩衝區中是否為滿,初值為0。
生產者程序
while(TRUE){
生產一個產品;
     P(empty);
     產品送往Buffer;
     V(full);
}
消費者程序
while(True){
P(full);
   從Buffer取出一個產品;
   V(empty);
   消費該產品;
   }
(2)一個生產者,一個消費者,公用n個環形緩衝區。
定義兩個同步訊號量:
empty——表示緩衝區是否為空,初值為n。
full——表示緩衝區中是否為滿,初值為0。

    設緩衝區的編號為1~n-1,定義兩個指標in和out,分別是生產者程序和消費者程序使用的指
,指向下一個可用的緩衝區。
生產者程序
while(TRUE){
     生產一個產品;
     P(empty);
     產品送往buffer(in);
     in=(in+1)mod n;
     V(full);
}

消費者程序
while(TRUE){
 P(full);
   從buffer(out)中取出產品;
   out=(out+1)mod n;
   V(empty);
   消費該產品;
   }
(3)一組生產者,一組消費者,公用n個環形緩衝區
    在這個問題中,不僅生產者與消費者之間要同步,而且各個生產者之間、各個消費者之間還必須互斥地訪問緩衝區。
定義四個訊號量:
empty——表示緩衝區是否為空,初值為n。
full——表示緩衝區中是否為滿,初值為0。
mutex1——生產者之間的互斥訊號量,初值為1。
mutex2——消費者之間的互斥訊號量,初值為1。

    設緩衝區的編號為1~n-1,定義兩個指標in和out,分別是生產者程序和消費者程序使用的指標,指向下一個可用的緩衝區。
生產者程序
while(TRUE){
     生產一個產品;
     P(empty);
     P(mutex1);
     產品送往buffer(in);
     in=(in+1)mod n;
     V(mutex1);
     V(full);
}
消費者程序
while(TRUE){
 P(full)
   P(mutex2);
   從buffer(out)中取出產品;
   out=(out+1)mod n;
   V(mutex2);
   V(empty);
   消費該產品;
   }
  需要注意的是無論在生產者程序中還是在消費者程序中,兩個P操作的次序不能顛倒。應先執行同步訊號量的P操作,然後再執行互斥訊號量的P操作,否則可能造成程序死鎖。

【例2】桌上有一空盤,允許存放一隻水果。爸爸可向盤中放蘋果,也可向盤中放桔子,兒子專等吃盤中的桔子,女兒專等吃盤中的蘋果。規定當盤空時一次只能放一隻水果供吃者取用,請用P、V原語實現爸爸、兒子、女兒三個併發程序的同步。

分析 在本題中,爸爸、兒子、女兒共用一個盤子,盤中一次只能放一個水果。當盤子為空時,爸爸可將一個水果放入果盤中。若放入果盤中的是桔子,則允許兒子吃,女兒必須等待;若放入果盤中的是蘋果,則允許女兒吃,兒子必須等待。本題實際上是生產者-消費者問題的一種變形。這裡,生產者放入緩衝區的產品有兩類,消費者也有兩類,每類消費者只消費其中固定的一類產品。

    :在本題中,應設定三個訊號量S、So、Sa,訊號量S表示盤子是否為空,其初值為l;訊號量So表示盤中是否有桔子,其初值為0;訊號量Sa表示盤中是否有蘋果,其初值為0。同步描述如下:
int S=1;
int Sa=0;
int So=0;
      main()
      {
        cobegin
            father();      /*父親程序*/
            son();        /*兒子程序*/
            daughter();    /*女兒程序*/
        coend
    }
    father()
    {
        while(1)
          {
            P(S);
            將水果放入盤中;
            if(放入的是桔子)V(So);
            else  V(Sa);
           }
     }
    son()
    {
        while(1)
          {
             P(So);
             從盤中取出桔子;
             V(S);
             吃桔子;
            }
    }
    daughter()
    {
         while(1)
            {
              P(Sa);
              從盤中取出蘋果;
              V(S);
              吃蘋果;
            }

 
思考題:

四個程序A、B、C、D都要讀一個共享檔案F,系統允許多個程序同時讀檔案F。但限制是程序A和程序C不能同時讀檔案F,程序B和程序D也不能同時讀檔案F。為了使這四個程序併發執行時能按系統要求使用檔案,現用PV操作進行管理,請回答下面的問題:
    (1)應定義的訊號量及初值:                    。
    (2)在下列的程式中填上適當的P、V操作,以保證它們能正確併發工作:
     A()                B()                  C()                 D()
      {                 {                    {                  {
      [1];                [3];                  [5];                 [7];
      read F;             read F;                read F;              read F;
     [2];                [4];                  [6];                 [8];
      }                  }                    }                  } 

    思考題解答:
(1)定義二個訊號量S1、S2,初值均為1,即:S1=1,S2=1。其中程序A和C使用訊號量S1,程序B和D使用訊號量S2。
(2)從[1]到[8]分別為:P(S1) V(S1) P(S2) V(S2) P(S1) V(S1) P(S2) V(S2)

 

具體PV原語對訊號量的操作可以分為三種情況:

1)              把訊號量視為一個加鎖標誌位,實現對一個共享變數的互斥訪問。

實現過程:

P(mutex);           // mutex的初始值為1

訪問該共享資料;

V(mutex);

非臨界區

2)              把訊號量視為是某種型別的共享資源的剩餘個數,實現對一類共享資源的訪問。

實現過程:

P(resource);          // resource的初始值為該資源的個數N

使用該資源;

V(resource);

非臨界區

3)              把訊號量作為程序間的同步工具

實現過程:

臨界區C1;    P(S);

V(S);           臨界區C2;

 

下面用幾個例子來具體說明:

例1:某超市門口為顧客準備了100輛手推車,每位顧客在進去買東西時取一輛推車,在買完東西結完帳以後再把推車還回去。試用P、V操作正確實現顧客程序的同步互斥關係。

分析:把手推車視為某種資源,每個顧客為一個要互斥訪問該資源的程序。因此這個例子為PV原語的第二種應用型別。

解:semaphore  S_CartNum;   // 空閒的手推車數量, 初值為100

void  consumer(void)           // 顧客程序
{
        P(S_CartNum);

        買東西;

        結帳;

        V(S_CartNum); 
}

例2:桌子上有一個水果盤,每一次可以往裡面放入一個水果。爸爸專向盤子中放蘋果,兒子專等吃盤子中的蘋果。把爸爸、兒子看作二個程序,試用P、V操作使這四個程序能正確地併發執行。

分析:爸爸和兒子兩個程序相互制約,爸爸程序執行完即往盤中放入蘋果後,兒子程序才能執行即吃蘋果。因此該問題為程序間的同步問題。

解:semaphore  S_PlateNum;  // 盤子容量,初值為1

semaphore  S_AppleNum;   // 蘋果數量,初值為0

void  father( )                // 父親程序
{

    while(1)

    {
        P(S_PlateNum);

        往盤子中放入一個蘋果;

        V(S_AppleNum);

    } 
}

void  son( )   // 兒子程序
{

    while(1)

    {
        P(S_AppleNum);

        從盤中取出蘋果;

        V(S_PlateNum);

        吃蘋果;

    } 
}

另附用PV原語解決程序同步與互斥問題的例子:

經典IPC問題如:生產者-消費者,讀者-寫者,哲學家就餐,睡著的理髮師等可參考相關教材。

一、兩個程序PA、PB通過兩個FIFO(先進先出)緩衝區佇列連線(如圖)

 

 

PA

PB

Q1

Q2

 

 

    PA從Q2取訊息,處理後往Q1發訊息,PB從Q1取訊息,處理後往Q2發訊息,每個緩衝區長度等於傳送訊息長度. Q1佇列長度為n,Q2佇列長度為m. 假設開始時Q1中裝滿了訊息,試用P、V操作解決上述程序間通訊問題。

解:// Q1隊列當中的空閒緩衝區個數,初值為0
semaphore  S_BuffNum_Q1;  

// Q2隊列當中的空閒緩衝區個數,初值為m 
semaphore  S_BuffNum_Q2;    

// Q1隊列當中的訊息數量,初值為n 
semaphore  S_MessageNum_Q1;

// Q2隊列當中的訊息數量,初值為0 
semaphore  S_MessageNum_Q2;

void  PA( )
{

        while(1)

        {
                P(S_MessageNum_Q2);

                從Q2當中取出一條訊息;

                V(S_BuffNum_Q2);

                處理訊息;

                生成新的訊息;

                P(S_BuffNum_Q1);

                把該訊息傳送到Q1當中;

                V(S_MessageNum_Q1);

        } 
}

void  PB( )
{

        while(1)

        {
                P(S_MessageNum_Q1);

                從Q1當中取出一條訊息;

                V(S_BuffNum_Q1);

                處理訊息;

                生成新的訊息;

                P(S_BuffNum_Q2);

                把該訊息傳送到Q2當中;

                V(S_MessageNum_Q2);

        } 
}

 

二、《作業系統》課程的期末考試即將舉行,假設把學生和監考老師都看作程序,學生有N人,教師1人。考場門口每次只能進出一個人,進考場的原則是先來先進。當N個學生都進入了考場後,教師才能髮捲子。學生交卷後即可離開考場,而教師要等收上來全部卷子並封裝卷子後才能離開考場。

(1)問共需設定幾個程序?

(2)請用P、V操作解決上述問題中的同步和互斥關係。

解:semaphore  S_Door;          // 能否進出門,初值1

semaphore  S_StudentReady;    // 學生是否到齊,初值為0

semaphore  S_ExamBegin;   // 開始考試,初值為0

semaphore  S_ExamOver;    // 考試結束,初值為0

int  nStudentNum = 0;          // 學生數目

semaphore  S_Mutex1         //互斥訊號量,初值為1

int  nPaperNum = 0;       // 已交的卷子數目

semaphore  S_Mutex2         //互斥訊號量,初值為1

void  student( )
{

        P(S_Door);

        進門;
        V(S_Door);

        P(S_Mutex1);

        nStudentNum ++;         // 增加學生的個數

        if(nStudentNum == N)  V(S_StudentReady);

        V(S_Mutex1);

        P(S_ExamBegin);         // 等老師宣佈考試開始

        考試中…

        交卷;

P(S_Mutex2);

        nPaperNum ++;      // 增加試卷的份數

        if(nPaperNum == N)  V(S_ExamOver);

        V(S_Mutex2);

        P(S_Door);

        出門;

        V(S_Door);

}

void  teacher( )
{

        P(S_Door);

        進門;
        V(S_Door);

        P(S_StudentReady);//等待最後一個學生來喚醒

        髮捲子;

        for(i = 1; i <= N; i++)    V(S_ExamBegin);

        P(S_ExamOver);         // 等待考試結束

        封裝試卷;

        P(S_Door);

        出門;
        V(S_Door);

}

 

三、某商店有兩種食品A和B,最大數量均為m個。 該商店將A、B兩種食品搭配出售,每次各取一個。為避免食品變質,遵循先到食品先出售的原則。有兩個食品公司分別不斷地供應A、B兩種食品(每次一個)。為保證正常銷售,當某種食品的數量比另一種的數量超過k(k<m)< font="">個時,暫停對數量大的食品進貨,補充數量少的食品。

(1) 問共需設定幾個程序?

(2) 用P、V操作解決上述問題中的同步互斥關係。

解:semaphore  S_BuffNum_A;  //A的緩衝區個數, 初值m

semaphore  S_Num_A;          // A的個數,初值為0

semaphore  S_BuffNum_B;  //B的緩衝區個數, 初值m

semaphore  S_Num_B;          // B的個數,初值為0

void  Shop( )
{

        while(1)

        {
                P(S_Num_A);

                P(S_Num_B);

                分別取出A、B食品各一個;

                V(S_BuffNum_A);

                V(S_BuffNum_A);

                搭配地銷售這一對食品;

        } 
}

// “A食品加1,而B食品不變”這種情形允許出現的次數(許可證的數量),其值等於//k-(A-B),初值為k

semaphore  S_A_B;

// “B食品加1,而A食品不變”這種情形允許出現的次數(許可證的數量),其值等於//k-(B-A),初值為k

semaphore  S_B_A;

void  Producer_A ( )
{

        while(1)

        {
                生產一個A食品;

                P(S_BuffNum_A);

                P(S_A_B);

                向商店提供一個A食品;

                V(S_Num_A);

                V(S_B_A);

        } 
}

void  Producer_B ( )
{

        while(1)

        {
                生產一個B食品;

                P(S_BuffNum_B);

                P(S_B_A);

                向商店提供一個B食品;

                V(S_Num_B);

                V(S_A_B);

        } 
}

四:在一棟學生公寓裡,只有一間浴室,而且這間浴室非常小,每一次只能容納一個人。公寓裡既住著男生也住著女生,他們不得不分享這間浴室。因此,樓長制定了以下的浴室使用規則:(1)每一次只能有一個人在使用;(2)女生的優先順序要高於男生,即如果同時有男生和女生在等待使用浴室,則女生優先;(3)對於相同性別的人來說,採用先來先使用的原則。

假設:

(1)當一個男生想要使用浴室時,他會去執行一個函式boy_wants_to_use_bathroom,當他離開浴室時,也會去執行另外一個函式boy_leaves_bathroom;

(2)當一個女生想要使用浴室時,會去執行函式girl_wants_to_use_bathroom,當她離開時, 也會執行函式girl_leaves_bathroom;

問題:請用訊號量和P、V操作來實現這四個函式(初始狀態:浴室是空的)。

解:訊號量的定義:

semaphore  S_mutex;     // 互斥訊號量,初值均為1

semaphore  S_boys; // 男生等待佇列,初值為0

semaphore  S_girls;   // 女生等待佇列,初值為0

普通變數的定義:

int  boys_waiting = 0;     // 正在等待的男生數;

int  girls_waiting = 0; // 正在等待的女生數;

int  using = 0;      // 當前是否有人在使用浴室;

void  boy_wants_to_use_bathroom ( )
{

        P(S_mutex);

        if((using == 0) && (girls_waiting == 0))

         {

                using  =  1;

                V(S_mutex);

         }

        else

         {

                boys_waiting ++;

                V(S_mutex);

                P(S_boys);

         }

}

void  boy_leaves_bathroom ( )
{

        P(S_mutex);

        if(girls_waiting  >  0)  // 優先喚醒女生

         {

                girls_waiting --;

                V(S_girls);

         }

        else  if(boys_waiting  >  0)

         {

                boys_waiting --;

                V(S_ boys);

         }

        else    using  =  0;         // 無人在等待

        V(S_mutex);

}

void  girl_wants_to_use_bathroom ( )
{

        P(S_mutex);

        if(using == 0)

         {

                using  =  1;

                V(S_mutex);

         }

        else

         {

                girls_waiting ++;

                V(S_mutex);

                P(S_girls);

         }

}

void  girl_leaves_bathroom ( )
{

        P(S_mutex);

        if(girls_waiting  >  0)  // 優先喚醒女生

         {

                girls_waiting --;

                V(S_girls);

         }

        else  if(boys_waiting  >  0)

         {

                boys_waiting --;

                V(S_ boys);

         }

        else    using  =  0;         // 無人在等待

        V(S_mutex);