進程管理(十)-進程通信
什么是進程通信
通信(communication)意味着在進程間傳送數據。
低級通信VS高級通信
- 控制信息的傳送。進程間控制信息的交換稱為低級通信。
- 大批量數據傳送。進程間大批量數據的交換稱為高級通信。
進程的通信的四種方式
主從式
特點:
1.主進程可自由地使用從進程的資源或數據
2.從進程的動作受主進程的控制
3.主進程和從進程的關系是固定的
例子:主從式通信系統的典型例子是終端控制進程和終端進程
會話式
會話方式中,通信進程雙方可分別稱為使用進程和服務進程。其中,使用進程調用服務進程提供的服務,具有如下特點
1.使用進程在使用服務進程所提供的服務之前,必須得到服務進程的許可;
2.服務進程根據使用進程的要求提供服務,但對所提供服務的控制由服務進程自身完成。
3.使用進程和服務進程在通信時有固定連接關系。
消息或郵箱機制
特點:消息或郵箱機制則無論接收進程是否已准備好接收消息,發送進程都將把所要發送的消息送入緩沖區或郵箱。
組成:消息的一般形式為4個部分組成:發送進程名、接收進程名、數據和有關數據的操作。
主要特點:
1.只要存在空緩沖區或郵箱,發送進程就可以發送消息。
2.與會話系統不同,發送進程和接收進程之間無直接連接關系,接收進程可能在收到某個發送進程發來的消息之后,又轉去接收另一個發送進程發來的消息。
3. 發送進程和接收進程之間存在緩沖區或郵箱用來存放被傳送消息。
共享存儲區方式
特點:
1.共享存儲區方式不要求數據移動
2.兩個需要互相交換信息的進程通過對同一共享數據區(shared memory)的操作來達到互相通信的目的
3.共享數據區是每個互相通信進程的一個組成部分
消息緩沖機制
什么是消息緩沖機制
簡而言之,就是利用消息緩沖區進行數據傳送
發送進程:發送進程在發送消息前,先在自己的內存空間設置一個發送區,把欲發送的消息填入其中,然后再用發送過程將其發送出去。
接受進程:接收進程則在接收消息之前,在自己的內存空間內設置相應的接收區,然后用接收過程接收消息
需要滿足的條件
1.在發送進程把消息寫入緩沖區和把緩沖區掛入消息隊列時,應禁止其他進程對該緩沖區消息隊列的訪問。否則,將引起消息隊列的混亂。同理,當接收進程正從消息隊列中取消息緩沖時,也應禁止其他進程對該隊列的訪問。
2.當緩沖區中無消息存在時,接收進程不能接收到任何消息。至於發送進程是否可以發送消息,則
由發送進程是否申請到緩沖區決定。
消息緩沖機制的實現
設公用信號量mutex 為控制對緩沖區訪問的互斥信號量,其初值為1 。
設SM為接收進程的私用信號量,表示等待接收的消息個數,其初值為0 。
設發送進程調用過程send(m)將消息m 送往緩沖區,接收進程調用過程Receive(m)將消息m從緩沖區讀往自己的數據區,則Send(m)和Receive(n)可分別描述為:
Send(m):
begin
向系統申請一個消息緩沖區;
P(mutex);
將發送區消息m送入新申請的消息緩沖區;
把消息緩沖區掛入接收進程的消息隊列;
V(mutex);
V(SM);
end
Receive(n):
begin
P(SM);
P(mutex);
摘下消息隊列中的消息n ;
將消息n從緩沖區復制到接收區;
釋放緩沖區;
V(mutex);
end
郵箱通信
什么是郵箱通信
郵箱通信就是由發送進程申請建立一與接收進程鏈接的郵箱。
發送進程把消息送往郵箱,接收進程從郵箱中取出消息,從而完成進程間信息交換。
郵箱通信的好處
設置郵箱的最大好處就是發送進程和接收進程之間沒有處理時間上的限制。一個郵箱可考慮成發送進程與接收進程之間的大小固定的私有數據結構,它不像緩沖區那樣被系統內所有進程共享。
郵箱通信的實現
郵箱由郵箱頭和郵箱體組成。其中郵箱頭描述郵箱名稱、郵箱大小、郵箱方向以及擁有該郵箱的進程名等。
只有一發送進程和一接收進程使用的郵箱,進程間通信應滿足如下條件:
1.發送進程發送消息時,郵箱中至少要有一個空格能存放該消息。
2.接收進程接收消息時,郵箱中至少要有一個消息存在。
設發送進程調用過程 deposit(m)將消息發送到郵箱,接收進程調用過程remove(m)將消息m 從郵箱中取出。
為記錄郵箱中空格個數和消息個數,信號量fromnum 為發送進程的私用信號量,信號量mesnum為接收進程的私用信號量。fromnum 的初值為信箱的空格數 n,mesnum 的初值為 0。
進程通信實例
和控制台的通信
通用計算機中,除了用戶終端之外,還有一台由系統操作員控制的控制台終端。
各用戶進程可將消息送到控制台進程,操作員在讀到這些消息后做出相應的處理。設控制台終端由鍵盤和顯示器組成,終端和主機之間按全雙工模式發送和接收數據,即鍵盤和數據顯示彼此獨立。
設鍵盤控制進程和顯示控制進程分別為KCP 和DCP,用戶進程和控制台終端的通信由會話控制進程CCP 控制完成
KCP和DCP的動作
首先,當操作員打鍵時,KCP將對應的數據從鍵盤送入輸入緩沖inbuf,同時也將鍵入數據送echobuf在顯示器上顯示。顯然,KCP和CCP等之間的通信滿足消息機制的條件。另外,除了KCP和CCP的通信之外,KCP實際上還在和鍵盤發生通信。因此,在描述KCP和CCP時,還必須考慮KCP和鍵盤的通信。
鍵盤控制進程KCP
設T-Ready 和T-Busy分別為鍵盤KP和鍵盤控制進程KCP 的私用信號量,其初值為0和1。
初始化{清除所有inbuf 和echobuf}
begin
local x;
P(T-Ready);
從鍵盤數據傳輸緩沖x中取出字符m記為x.m;
Send(x.m);
將x.m送入echonuf;
V(T-Busy);
end
鍵盤動作KP
repeat
local x;
P(T-Busy);
把鍵入字符放入數據傳輸緩沖x;
V(T-Ready);
until 終端關閉
顯示器控制進程DCP
設D-Ready 和D-Busy分別為DP和DCP的私用信號量且初值為0和1 。
初始化 {清除輸出緩沖outbuf,echo模式置false}
begin
if outbuf滿
then
receive(k) /* CCP k */
P(D-Busy);
把k送入顯示器數據緩沖區;
V(D-Ready);
else
echo模式置true;
echobuf 中字符置入顯示器數據緩沖區;
fi
顯示器動作DP
假定一個消息的長度總是小於outbuf的長度。
repeat
if echo模式
then
打印顯示器數據緩沖區中字符
else
P (D-Ready)
打印顯示器數據緩沖區中消息
V (D-Busy)
Until 顯示器關機
CCP和KCP及DCP的接口
假定1個消息的長度小於outbuf和inbuf的長度。
設過程Read(x)把inbuf 中的所有字符讀到用戶進程數據區x處,過程Write(y)把用戶進程y 處的消息寫到outbuf中。
CCP 與用戶進程的接口
用戶進程向CCP發出的提問消息組成隊列RQ, 設相應的互斥信號量rq,初值為1。
CCP所發出的回答消息組成消息接收隊列Sqi,互斥信號量sqi,其初值為1。
CCP設置私用信號量question計算用戶進程提出問題的數目,信號量question初值為0。
CCP設置私用信號量answeri計算SQi中的消息個數,信號量answeri初值為0。
CCP 的動作
管道pipe
什么是管道
UNIX系統從System Ⅴ開始,提供有名管道和無名管道兩種數據通信方式,這里介紹無名管道。
無名管道為建立管道的進程及其子孫提供一條以比特流方式傳送消息的通信管道。該管道在邏輯上被看作管道文件,在物理上則由文件系統的高速緩沖區構成,很少啟動外設
管道的工作
發送進程利用系統調用write(fd[1], buf, size),把buf中的長度為size字符的消息送入管道入口fd[1]。
接收進程則使用系統調用read(fd[0], buf, size)從管道出口fd[0] 讀出size字符的消息置入buf 中。
管道按FIFO(先進先出)方式傳送消息,且只能單向傳送消息。
利用UNIX提供的系統調用pipe,可建立一條同步通信管道。其格式為: pipe(fd) int fd[2];
其中,fd[1]為寫入端,fd[0]為讀出端
管道的實例
1.用C語言編寫一個程序,建立一個pipe,同時父進程生成一個子進程,子進程向pipe中寫入一字符串,父進程從pipe中讀出該字符串。
#include 〈stdio.h〉
main()
{
int x, fd[2];
char buf[30], s[30];
pipe(fd);/*創建管道*/
while((x=fork())==-1);/*創建子進程失敗時,循環*/
if(x==0)
{
sprintf(buf,″This is an example\n″);
write(fd[1],buf,30);/*把buf中字符寫入管道*/
exit(0);
}
else/*父進程返回*/
{
wait(0);
read(fd[0],s,30);/*父進程讀管道中字符*/
printf(″%s″,s);
}
}
2.編寫一程序,建立一個管道。同時,父進程生成子進程P1,P2,這兩個子進程分別向管道中寫入各自的字符串,父進程讀出它們
#include 〈stdio.h〉
main()
{
int i,r,p1,p2,fd[2];
char buf[50],s[50];
pipe(fd);/*父進程建立管道*/
while((p1=fork())==-1);/*創建子進程P1,失敗時循環*/
if(p1==0) /*由子進程P1返回,執行子進程P1*/
{
lockf(fd[1],1,0);/*加鎖鎖定寫入端*/
sprintf(buf,″child process P1 is sending messages!\n″);
printf(″child processP1!\n″);
write(fd[1],buf,50);/*把buf中的50個字符寫入管道*/
sleep(5);/*睡眠5秒,讓父進程讀*/
lockf(fd[1],0,0);/*釋放管道寫入端*/
exit(0);/*關閉P1*/
}
else/*從父進程返回,執行父進程*/
{
while((p2=fork())==-1);/*創建子進程P2,失敗時循環*/
if(p2==0)/*從子進程P2返回,執行P2*/
{
lockf(fd[1],1,0);/*鎖定寫入端*/
sprintf(buf,″child process P2 is sending messages\n″);
printf(″child process P2 ! \n″);
write(fd[1],buf,50);/*把buf中字符寫入管道*/
sleep(5);/*睡眠等待*/
lockf(fd[1],0,0);/*釋放管道寫入端*/
exit(0);/*關閉P2*/
}
wait(0);
if(r=read(fd[0],s,50)==-1)
printf(″can′t read pipe\n″);
else printf(″%s\n″,s);
wait(0);
if(r=read(fd[0],s,50)==-1)
printf(″can′t read pipe\n″);
else printf(″%s\n″,s);
exit(0);
}
}