在計算機操做系統中,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操做要成對出現,但它們分別在不一樣的進程代碼中。spa
【例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)定義二個信號量S一、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問題如:生產者-消費者,讀者-寫者,哲學家就餐,睡着的理髮師等可參考相關教材。
1、兩個進程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);
}
}
2、《操做系統》課程的期末考試即將舉行,假設把學生和監考老師都看做進程,學生有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);
}
3、某商店有兩種食品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);
}