操作系統-PV操作的原理和幾種常見問題


信號量是一種變量類型,用一個記錄型數據結構表示,有兩個分量:信號量的值和信號量隊列指針
除了賦初值外,信號量僅能通過同步原語PV對其進行操作
s.value為正時,此值為封鎖進程前對s信號量可施行的P操作數,即s代表實際可用的物理資源
s.value為負時,其絕對值為對信號量s實施P操作而被封鎖並進入信號量s等待隊列的進程數,即登記排列在s信號量隊列之中等待的進程個數
s.value為0時,無資源且無進程等待
信號量按其取值可分為二值信號量和一般信號量(計數信號量),記錄型信號量和PV操作定義為如下數據結構和不可中斷過程:
typedef struct semaphore { 
    int value;
    struct pcb *list; //信號量隊列指針
}
void P(semaphore s){
    s.value--;
    if(s.value<0) sleep(s.list); //若信號量值小於0,執行P操作的進程調用sleep(s.list)阻塞自己,被置成等待信號量s狀態並移入s信號量隊列,轉向進程調度程序
}
void V(semaphore s){
    s.value++;
    if(s.value<=0) wakeup(s.list); //若信號量小於等於0,則調用wakeup(s.list)從信號量s隊列中釋放一個等待信號量s的進程並轉換成就緒態,進程則繼續運行
}
二值信號量雖然僅能取值0、1,但它和其它記錄型信號量有一樣的表達能力
  • 機票問題
注意:(1)P操作與V操作在執行路徑上必須一一對應,有一個P操作就有一個V操作;(2)輸出一張票的操作不應放在臨界區內
int A[m];
Semaphore s = 1;
cobegin
process Pi {
    int Xi;
    Li:按旅客定票要求找到A[j];
    P(s);
    Xi = A[j];
    If (Xi>=1) { Xi=Xi-1; A[j]=Xi;V(s); 輸出一張票;} 
    else {V(s); 輸出票已售完;}   
    goto Li;
    }
coend;
只有相同航班的票數才是相關的臨界資源,所以用一個信號量s處理全部機票會影響進程並發度
可以讓每一個航班都有自己的臨界區,把信號量改為s[m]
int A[m];
Semaphore s[m];    //每一個航班都有自己的臨界區
For (int j=0;j<m;i++) s[j] = 1;
cobegin
process Pi {
    int Xi;
    L1:按旅客定票要求找到A[j];
    P(s[j]);
    Xi = A[j];
    If (Xi>=1) { Xi=Xi-1; A[j]=Xi;V(s[j]); 輸出一張票;
    } else {V(s[j]); 輸出票已售完;}
    goto L1;
    }
coend;
  • 生產者消費者問題
(1)1生產者1消費者1緩沖區問題
int B;
semaphore sput = 1/* 可以使用的空緩沖區數 */
semaphore sget = 0; /* 緩沖區內可以使用的產品數 */
process producer {
    L1:
    produce a product;
    P(sput);
    B = product;
    V(sget);
    goto L1;
}
process consumer {
    L2:
    P(sget);
    product = B;
    V(sput);
    consume a product;
    goto L2;
}
(2)1生產者1消費者N緩沖區問題
必須引入放入和取出產品的循環隊列指針putptr指針和getptr指針進行管理,因為只有1生產者1消費者所以它們不需要是共享變量
int B[k]; // 共享緩沖區隊列
semaphore sput = N;  //可以使用的空緩沖區數
semaphore sget = 0;  //緩沖區內可以使用的產品數
int putptr = getptr = 0; 
process producer {
    L1:produce a product;
    P(sput);
    B[putptr] = product;
    putptr = (putptr + 1) mod k;
    V(sget);
    goto L1;
}
process consumer {
    L2:P(sget);
    product = B[getptr];
    getptr = (getptr + 1) mod k;    
    V(sput);
    consume a product;
    goto L2;
}
⭐️(3)N生產者N消費者N緩沖區問題
必須引入新的信號量s1和s2對putptr指針和getptr指針進行管理
int B[k];
semaphore sput = N; /* 可以使用的空緩沖區數 */
semaphore sget = 0; /* 緩沖區內可以使用的產品數 */
int putptr = getptr = 0; 
semaphore s1 = s2 = 1/* 互斥使用putptr、getptr */
process producer_i {
    L1:produce a product;
   P(sput);
    P(s1);
    B[putptr] = product;
    putptr = ( putptr + 1 ) mod k;
   V(s1);
    V(sget);
    goto L1;
}
process consumer_j {
    L2:P(sget);
    P(s2);
    Product = B[getptr];
    getptr = ( getptr + 1 ) mod k;
    V(s2);
    V(sput);
    consume a product;
    goto L2;
}
若要求一個消費者取10次,其它消費者才能開始取,則需要再增加一個信號量mutex1並對消費者進程進行改造,循環10次后再V(mutex1)。
這里通過一個互斥信號量mutex2互斥訪問緩沖區,而不是像前面那樣通過兩個互斥信號量s1和s2分別互斥使用緩沖區存指針putptr、取指針getptr
process consumer_j {
    P(mutex1);
    for(int i=0; i<10; i++){
        P(full);
        P(mutex2);
        互斥訪問緩沖區;
        V(mutex2);
        V(empty);
    }
    V(mutex1);
}
(4)蘋果橘子問題
父親只放評估、母親只放橙子;兒子只吃橙子、女兒只吃蘋果
同步關系1:有蘋果
同步關系2:有橘子
同步關系3:有空位
Semaphore sp; /* 盤子里可以放幾個水果*/
Semaphore sg1; /* 盤子里有桔子*/
Semaphore sg2; /* 盤子里有蘋果*/
sp = 1; /* 盤子里允許放入一個水果*/
sg1 = 0; /* 盤子里沒有桔子*/
sg2 = 0; /* 盤子里沒有蘋果*/
process father {
L1: 削一個蘋果;
    P(sp);
    把蘋果放入plate;
    V(sg2);
    goto L1;
}
process mother {
    L2: 剝一個桔子;
    P(sp);
    把桔子放入plate;
    V(sg1);
    goto L2;
}
process son {
    L3: P(sg1);
    從plate中取桔子;
    V(sp);
    吃桔子;
    goto L3;
}
process daughter {
    L4: P(sg2);
    從plate中取蘋果;
    V(sp);
    吃蘋果;
    goto L4;
}
(5)吸煙者問題
三個消費者各有一種材料、缺另外兩種材料,供應者每次隨機供應兩種不同材料
想法:生產者每次供應兩種不同材料,其實可以理解為有三種不同的生產材料提供方式,分別供三種消費者消費
int random;
Semaphore offer1 = offer2 = offer3 = 0;
Semaphore finish = 1;
process producer(){
    while(1){
        random = 任意隨機整數;
        random = random % 3;
        P(finish);
        對應兩種材料放在桌子上;
        if(random==0) V(offer1);
        else if(random==1) V(offer2);
        else V(offer3);
    }
}
process consumer_1(){
    while(1){
        P(offer1);
        拿自己缺的那兩種材料;
        卷成煙抽掉;
        V(finish);
    }
}
(6)1生產者2消費者N緩沖區,生產者隨機生成正整數,2個消費者分別取奇數和偶數
思路:這個問題和抽煙者問題很類似,關鍵是要定義緩沖區里有奇數的互斥信號量odd、緩沖區里有偶數的互斥信號量even。每次生產者隨機生成一個數之后,判斷奇偶,分別V(odd)和V(even)
  • 哲學家就餐問題
Semaphore fork[5];
for (int i = 0; i < 5; i++)
    fork[i] = 1;
cobegin
process philsopher_i() {
    while (true) {
        think();
        P(fork[i]);
        P(fork[(i+1)%5]);
        eat();
        V(fork[i]);
        V(fork[(i+1)%5]);
    }
}
coend
如果5位哲學家同時拿起他們左手/右手的叉子,將出現死鎖,可以:
(1)至多允許四個哲學家同時拿叉子
(2)奇數號哲學家先取左邊叉子,再取右邊叉子;偶數號哲學家則相反
(3)每位哲學家取到手邊兩把叉子才開始吃,否則一把也不取:可以通過引入互斥信號量mutex每次只允許一個哲學家拿叉子
Semaphore fork[5];
for (int i = 0; i < 5; i++)
    fork[i] = 1;
Semaphore mutex = 1;
cobegin
process philsopher_i() {
    while(true){
       P(mutex);
        P(fork[i]);
        P(fork[(i+1)%5]);
       V(mutex);
        eat();
        V(fork[i]);
        V(fork[(i+1)%5]);
    }
}
coend
  • 寫者問題
一次只允許一個寫者寫,但可以N個讀者同時讀。寫者完成寫操作前不允許其它寫者、讀者操作
引入表示是否允許寫的信號量writeblock,相當於任何進程在工作的時候都不允許寫。不過單純引入信號量不能解決此問題,還必須引入計數器readcount對讀進程進行計數,讀之前檢查計數器,如果為1(自己是唯一的讀進程)才需要P(writeblock),讀之后檢查計數器,如果為0(當前已無讀進程)則需要V(writeblock)。
mutex是用於對計數器readcount操作的互斥信號量
int readcount = 0;
Semaphore writeblock = 1 ;
Semaphore mutex = 1;
cobegin
process read_i() {
    P(mutex);
    readcount++;
    if(readcount==1) P(writeblock); //自己是唯一的讀進程,寫者在寫文件時自己不能開始讀,自己開始讀后不允許寫操作
    V(mutex);
    /*讀文件*/
    P(mutex);
    readcount-—; 
    if(readcount==0) V(writeblock); //自己是唯一的讀進程,讀文件完成后允許寫操作
    V(mutex);
}
process write_j() {
    P(writeblock);
    /*寫文件*/
    V(writeblock);
}
coend
此寫法讀者優先,當存在讀者時寫者將被延遲。且只要有一個讀者活躍,隨后而來的讀者都將被允許訪問文件,從而導致寫者長時間等待。
改進方法:增加信號量,確保當一個寫進程聲明想寫時,不允許后面的新讀者訪問共享文件。
  • 男女共浴問題、汽車過橋問題
這個問題的關鍵是設置一把性別鎖mutex,第一個到的男人要負責搶這把鎖,因此mutex的PV操作必須放在修改mancount的臨界區內。一旦男人搶到了這把鎖,試圖搶這把鎖的女人就會停留在womancount的臨界區內出不來,而每次又只允許一個女人進入womancount的臨界區。
最后一個走的男人要負責把這把鎖釋放掉。
Semaphore mutex;
Semaphore mutex_man = mutex_woman= 1;
int mancount = womancount =0;
Process man(){
    P(mutex_man)
    mancount++;
    if(mancount==1) P(mutex);
    V(mutex_man);
    洗澡;
    P(mutex_man);
    mancount—;
    if(mancount==0) V(mutex);
    V(mutex_man)
}
南大18年真題過橋問題:汽車只能單向過橋,最多12輛車同時過橋。
  • 理發師問題
引入一個計數器waiting記錄等待理發的顧客坐的椅子數,初值為0最大為N。mutex是用於對計數器waiting操作的互斥信號量
引入信號量customers記錄等候理發的顧客數,並用於阻塞理發師進程,初值為0
引入信號量barbers記錄正在等候顧客的理發師數,並用於阻塞顧客進程,初值為0
想法:其實和N生產者1消費者N緩沖區的生產者消費者問題有些類似,但是多了P(barbers)和V(barbers);此外,椅子數改用了int數waiting再用信號量mutex進行互斥。
int waiting = 0;
Semaphore customers = 0;
Semaphore barbers = 0;
Semaphore mutex = 1;
cobegin
process barbers() {
    while(true) {
       P(customers);//判斷是否有顧客,沒有的話理發師睡眠
       P(mutex);
        waiting--;
        V(barbers);//理發師准備為顧客理發
        V(mutex);
        cuthair();  //理發師理發,不應放在臨界區
    }
}
process customer_i() {
    P(mutex);
    if(waiting<N) {
        waiting++;
        V(customers);//喚醒理發師
        V(mutex);
        P(barbers);//如果理發師忙則等待
        get_haircut();
    }
    else V(mutex);//人滿了,顧客離開
}
coend

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM