linux進程管理的一些問題

linux進程管理的一些問題

一.進程創建
一、進程
LINUX中,進程既是一個獨立擁有資源的基本單位,又是一個獨立調度的基本單位。一個進程實體由若干個區(段)組成,包括程序區、數據區、棧區、共享存儲區等。每個區又分爲若干頁,每個進程配置有唯一的進程控制塊PCB,用於控制和管理進程。
PCB的數據結構如下:
1、進程表項(Process Table Entry)。包括一些最常用的核心數據:
進程標識符PID、用戶標識符UID、進程狀態、事件描述符、進程和U區在內存或外存的地址、軟中斷信號、計時域、進程的大小、偏置值nice、指向就緒隊列中下一個PCB的指針P_Link、指向U區進程正文、數據及棧在內存區域的指針。
2、U區(U Area)。用於存放進程表項的一些擴充信息。
每一個進程都有一個私用的U區,其中含有:進程表項指針、真正用戶標識符u-ruid(read user ID)、有效用戶標識符u-euid(effective user ID)、用戶文件描述符表、計時器、內部I/O參數、限制字段、差錯字段、返回值、信號處理數組。
由於LINUX系統採用段頁式存儲管理,爲了把段的起始虛地址變換爲段在系統中的物理地址,便於實現區的共享,所以還有:
3、系統區表項。以存放各個段在物理存儲器中的位置等信息。
系統把一個進程的虛地址空間劃分爲若干個連續的邏輯區,有正文區、數據區、棧區等。這些區是可被共享和保護的獨立實體,多個進程可共享一個區。爲了對區進行管理,核心中設置一個系統區表,各表項中記錄了以下有關描述活動區的信息:
區的類型和大小、區的狀態、區在物理存儲器中的位置、引用計數、指向文件索引結點的指針。
4、進程區表
系統爲每個進程配置了一張進程區表。表中,每一項記錄一個區的起始虛地址及指向系統區表中對應的區表項。核心通過查找進程區表和系統區表,便可將區的邏輯地址變換爲物理地址。
二、進程映像
LINUX系統中,進程是進程映像的執行過程,也就是正在執行的進程實體。它由三部分組成:
1、用戶級上、下文。主要成分是用戶程序;
2、寄存器上、下文。由CPU中的一些寄存器的內容組成,如PC,PSW,SP及通用寄存器等;
3、系統級上、下文。包括OS爲管理進程所用的信息,有靜態和動態之分。
三、所涉及的系統調用
1、fork( ) 
創建一個新進程。 
系統調用格式: 
pid=fork( )
參數定義:
int fork( )
fork( )返回值意義如下:
0:在子進程中,pid變量保存的fork( )返回值爲0,表示當前進程是子進程。
>0:在父進程中,pid變量保存的fork( )返回值爲子進程的id值(進程唯一標識符)。
-1:創建失敗。
如果fork( )調用成功,它向父進程返回子進程的PID,並向子進程返回0,即fork( )被調用了一次,但返回了兩次。此時OS在內存中建立一個新進程,所建的新進程是調用fork( )父進程(parent process)的副本,稱爲子進程(child process)。子進程繼承了父進程的許多特性,並具有與父進程完全相同的用戶級上下文。父進程與子進程併發執行。
核心爲fork( )完成以下操作:
(1)爲新進程分配一進程表項和進程標識符
進入fork( )後,核心檢查系統是否有足夠的資源來建立一個新進程。若資源不足,則fork( )系統調用失敗;否則,核心爲新進程分配一進程表項和唯一的進程標識符。
(2)檢查同時運行的進程數目
超過預先規定的最大數目時,fork( )系統調用失敗。
(3)拷貝進程表項中的數據
將父進程的當前目錄和所有已打開的數據拷貝到子進程表項中,並置進程的狀態爲“創建”狀態。
(4)子進程繼承父進程的所有文件
對父進程當前目錄和所有已打開的文件表項中的引用計數加1。
(5)爲子進程創建進程上、下文
進程創建結束,設子進程狀態爲“內存中就緒”並返回子進程的標識符。
(6)子進程執行
雖然父進程與子進程程序完全相同,但每個進程都有自己的程序計數器PC(注意子進程的PC開始位置爲fork()調用後的語句),然後根據pid變量保存的fork( )返回值的不同,執行了不同的分支語句。



例:
…..
pid=fork( );
if (! pid)
printf("I’m the child process!\n");
else if (pid>0)
printf("I’m the parent process! \n"); 
else
printf("Fork fail!\n") fork( )調用前
fork( )調用後
…..
pid=fork( );
if (! pid)
printf("I’m the child process!\n");
else if (pid>0)
printf("I’m the parent process!\n "); 
else
printf("Fork fail!\n");
…… …..
pid=fork( );
if (! pid)
printf("I’m the child process!\n");
else if (pid>0)
printf("I’m the parent process!\n ");
else
printf("Fork fail!\n");
……
四、參考程序
1、
#include <stdio.h>
main( )
{
int p1,p2;
while((p1=fork( ))= = -1); /*創建子進程p1*/
if (p1= =0) putchar(‘b’); 
else 

while((p2=fork( ))= = -1); /*創建子進程p2*/
if(p2= =0) putchar(‘c’); 
else putchar(‘a’); 
}
}
2、
#include <stdio.h>
main( )
{
int p1,p2,i;
while((p1=fork( ))= = -1); /*創建子進程p1*/
if (p1= =0)
for(i=0;i<10;i++)
printf("daughter %d\n",i);
else
{
while((p2=fork( ))= = -1); /*創建子進程p2*/
if(p2= =0)
for(i=0;i<10;i++)
printf("son %d\n",i);
else
for(i=0;i<10;i++)
printf("parent %d\n",i);
}
}
五、運行結果
1、bca,bac, abc ,……都有可能。
2、parent…
son…
daughter..
daughter..
或 parent…
son…
parent…
daughter…等
六、分析原因
除strace 外,也可用ltrace -f -i -S ./executable-file-name查看以上程序執行過程。
1、從進程併發執行來看,各種情況都有可能。上面的三個進程沒有同步措施,所以父進程與子進程的輸出內容會疊加在一起。輸出次序帶有隨機性
2、由於函數printf( )在輸出字符串時不會被中斷,因此,字符串內部字符順序輸出不變。但由於進程併發執行的調度順序和父子進程搶佔處理機問題,輸出字符串的順序和先後隨着執行的不同而發生變化。這與打印單字符的結果相同。
補充:進程樹
在LINUX系統中,只有0進程是在系統引導時被創建的,在系統初啓時由0進程創建1進程,以後0進程變成對換進程,1進程成爲系統中的始祖進程。LINUX利用fork( )爲每個終端創建一個子進程爲用戶服務,如等待用戶登錄、執行SHELL命令解釋程序等,每個終端進程又可利用fork( )來創建其子進程,從而形成一棵進程樹。可以說,系統中除0進程外的所有進程都是用fork( )創建的。

  

二.進程管理
lockf(files,function,size)
用作鎖定文件的某些段或者整個文件。
本函數的頭文件爲
#include "unistd.h"
參數定義:
int lockf(files,function,size)
int files,function;
long size;
其中:files是文件描述符;function是鎖定和解鎖:1表示鎖定,0表示解鎖。size是鎖定或解鎖的字節數,爲0,表示從文件的當前位置到文件尾。
二、參考程序
#include <stdio.h>
#include <unistd.h>
main( )
{
int p1,p2,i;
while((p1=fork( ))= = -1); /*創建子進程p1*/
if (p1= =0)
{
lockf(1,1,0); /*加鎖,這裏第一個參數爲stdout(標準輸出設備的描述符)*/
for(i=0;i<10;i++)
printf("daughter %d\n",i);
lockf(1,0,0); /*解鎖*/
}
else
{
while((p2=fork( ))= =-1); /*創建子進程p2*/
if (p2= =0)
{
lockf(1,1,0); /*加鎖*/
for(i=0;i<10;i++)
printf("son %d\n",i);
lockf(1,0,0); /*解鎖*/
}
else
{
lockf(1,1,0); /*加鎖*/
for(i=0;i<10;i++)
printf(" parent %d\n",i);
lockf(1,0,0); /*解鎖*/
}
}
}
三、運行結果
parent…
son…
daughter..
daughter..
或parent…
son…
parent…
daughter…
大致與未上鎖的輸出結果相同,也是隨着執行時間不同,輸出結果的順序有所不同。
四、分析原因
上述程序執行時,不同進程之間不存在共享臨界資源(其中打印機的互斥性已由操作系統保證)問題,所以加鎖與不加鎖效果相同。
五、分析以下程序的輸出結果:
#include<stdio.h>
#include<unistd.h>
main()
{
int p1,p2,i;
int *fp;
fp = fopen("to_be_locked.txt"
,"w+");
if(fp==NULL)
{
printf("Fail to create file");
exit(-1);
}
while((p1=fork( ))== -1); /*創建子進程p1*/
if (p1==0)
{
lockf(*fp,1,0); /*加鎖*/
for(i=0;i<10;i++)
fprintf(fp,"daughter %d\n",i);
lockf(*fp,0,0); /*解鎖*/
}
else
{
while((p2=fork( ))==-1); /*創建子進程p2*/
if (p2==0)
{
lockf(*fp,1,0); /*加鎖*/
for(i=0;i<10;i++)
fprintf(fp,"son %d\n",i);
lockf(*fp,0,0); /*解鎖*/
}
else
{
wait(NULL);
lockf(*fp,1,0); /*加鎖*/
for(i=0;i<10;i++)
fprintf(fp,"parent %d\n",i);
lockf(*fp,0,0); /*解鎖*/
}
}
fclose(fp);
}
cat to_be_locked.txt 查看輸出結果

  

三.進程的通信
一、信號
1、信號的基本概念
每個信號都對應一個正整數常量(稱爲signal number,即信號編號。定義在系統頭文件<signal.h>中),代表同一用戶的諸進程之間傳送事先約定的信息的類型,用於通知某進程發生了某異常事件。每個進程在運行時,都要通過信號機制來檢查是否有信號到達。若有,便中斷正在執行的程序,轉向與該信號相對應的處理程序,以完成對該事件的處理;處理結束後再返回到原來的斷點繼續執行。實質上,信號機制是對中斷機制的一種模擬,故在早期的LINUX版本中又把它稱爲軟中斷
信號與中斷的相似點:
(1)採用了相同的異步通信方式;
(2)當檢測出有信號或中斷請求時,都暫停正在執行的程序而轉去執行相應的處理程序;
(3)都在處理完畢後返回到原來的斷點;
(4)對信號或中斷都可進行屏蔽。
信號與中斷的區別:
(1)中斷有優先級,而信號沒有優先級,所有的信號都是平等的;
(2)信號處理程序是在用戶態下運行的,而中斷處理程序是在覈心態下運行;
(3)中斷響應是及時的,而信號響應通常都有較大的時間延遲。
信號機制具有以下三方面的功能:
(1)發送信號。發送信號的程序用系統調用kill( )實現;
(2)預置對信號的處理方式。接收信號的程序用signal( )來實現對處理方式的預置;
(3)收受信號的進程按事先的規定完成對相應事件的處理。
2、信號的發送
信號的發送,是指由發送進程把信號送到指定進程的信號域的某一位上。如果目標進程正在一個可被中斷的優先級上睡眠,核心便將它喚醒,發送進程就此結束。一個進程可能在其信號域中有多個位被置位,代表有多種類型的信號到達,但對於一類信號,進程卻只能記住其中的某一個。
進程用kill( )向一個進程或一組進程發送一個信號。
3、對信號的處理
當一個進程要進入或退出一個低優先級睡眠狀態時,或一個進程即將從核心態返回用戶態時,核心都要檢查該進程是否已收到軟中斷。當進程處於核心態時,即使收到軟中斷也不予理睬;只有當它返回到用戶態後,才處理軟中斷信號。對軟中斷信號的處理分三種情況進行:
(1)如果進程收到的軟中斷是一個已決定要忽略的信號(function=1),進程不做任何處理便立即返回;
(2)進程收到軟中斷後便退出(function=0);
(3)執行用戶設置的軟中斷處理程序。
二、所涉及的中斷調用
1、kill( )
系統調用格式
int kill(pid,sig)
參數定義
int pid,sig;
其中,pid是一個或一組進程的標識符,參數sig是要發送的軟中斷信號。
(1)pid>0時,核心將信號發送給進程pid。
(2)pid=0時,核心將信號發送給與發送進程同組的所有進程。
(3)pid=-1時,核心將信號發送給所有用戶標識符真正等於發送進程的有效用戶標識號的進程。
2、signal( )
預置對信號的處理方式,允許調用進程控制軟中斷信號。
系統調用格式
signal(sig,function)
頭文件爲
  #include <signal.h>
參數定義
signal(sig,function)
int sig;
void (*func) ( )
其中sig用於指定信號的類型,sig爲0則表示沒有收到任何信號,餘者如下表:

值 名 字 說 明
01 SIGHUP 掛起(hangup)
02 SIGINT 中斷,當用戶從鍵盤按^c鍵或^break鍵時
03 SIGQUIT 退出,當用戶從鍵盤按quit鍵時
04 SIGILL 非法指令
05 SIGTRAP 跟蹤陷阱(trace trap),啓動進程,跟蹤代碼的執行
06 SIGIOT IOT指令
07 SIGEMT EMT指令
08 SIGFPE 浮點運算溢出
09 SIGKILL 殺死、終止進程 
10 SIGBUS 總線錯誤
11 SIGSEGV 段違例(segmentation violation),進程試圖去訪問其虛地址空間以外的位置
12 SIGSYS 系統調用中參數錯,如系統調用號非法
13 SIGPIPE 向某個非讀管道中寫入數據
14 SIGALRM 鬧鐘。當某進程希望在某時間後接收信號時發此信號
15 SIGTERM 軟件終止(software termination)
16 SIGUSR1 用戶自定義信號1
17 SIGUSR2 用戶自定義信號2
18 SIGCLD 某個子進程死
19 SIGPWR 電源故障

function:在該進程中的一個函數地址,在覈心返回用戶態時,它以軟中斷信號的序號作爲參數調用該函數,對除了信號SIGKILL,SIGTRAP和SIGPWR以外的信號,核心自動地重新設置軟中斷信號處理程序的值爲SIG_DFL,一個進程不能捕獲SIGKILL信號。
function 的解釋如下:
(1)function=1時,進程對sig類信號不予理睬,亦即屏蔽了該類信號;
(2)function=0時,缺省值,進程在收到sig信號後應終止自己;
(3)function爲非0,非1類整數時,function的值即作爲信號處理程序的指針。
三、參考程序
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void waiting( ),stop( );
int wait_mark;
main( )
{
int p1,p2,stdout;
while((p1=fork( ))= =-1); /*創建子進程p1*/
if (p1>0)
{
while((p2=fork( ))= =-1); /*創建子進程p2*/
if(p2>0)
{
wait_mark=1;
signal(SIGINT,stop); /*接收到^c信號,轉stop*/
waiting( );
kill(p1,16); /*向p1發軟中斷信號16*/
kill(p2,17); /*向p2發軟中斷信號17*/
wait(0); /*同步*/
wait(0);
printf("Parent process is killed!\n");
exit(0);
}
else
{
wait_mark=1;
signal(17,stop); /*接收到軟中斷信號17,轉stop*/
waiting( );
lockf(stdout,1,0);
printf("Child process 2 is killed by parent!\n");
lockf(stdout,0,0);
exit(0);
}
}
else
{
wait_mark=1;
signal(16,stop); /*接收到軟中斷信號16,轉stop*/
waiting( );
lockf(stdout,1,0);
printf("Child process 1 is killed by parent!\n");
lockf(stdout,0,0);
exit(0);
}
}

void waiting( )
{
while(wait_mark!=0);
}

void stop( )
{
wait_mark=0;
}
四、運行結果
屏幕上無反應,按下^C後,顯示 Parent process is killed!
五、分析原因
上述程序中,signal( )都放在一段程序的前面部位,而不是在其他接收信號處。這是因爲signal( )的執行只是爲進程指定信號值16或17的作用,以及分配相應的與stop( )過程鏈接的指針。因而,signal( )函數必須在程序前面部分執行。
本方法通信效率低,當通信數據量較大時一般不用此法。

 

/******************************************************************************************/  

三.進程通信(管道通信機制)
一、什麼是管道
LINUX系統在OS的發展上,最重要的貢獻之一便是該系統首創了管道(pipe)。這也是LINUX系統的一大特色。
所謂管道,是指能夠連接一個寫進程和一個讀進程的、並允許它們以生產者—消費者方式進行通信的一個共享文件,又稱爲pipe文件。由寫進程從管道的寫入端(句柄1)將數據寫入管道,而讀進程則從管道的讀出端(句柄0)讀出數據。

句柄fd[0]
句柄fd[1] 
讀出端
寫入端
二、管道的類型:
1、有名管道
一個可以在文件系統中長期存在的、具有路徑名的文件。用系統調用mknod( )建立。它克服無名管道使用上的侷限性,可讓更多的進程也能利用管道進行通信。因而其它進程可以知道它的存在,並能利用路徑名來訪問該文件。對有名管道的訪問方式與訪問其他文件一樣,需先用open( )打開。
2、無名管道
一個臨時文件。利用pipe( )建立起來的無名文件(無路徑名)。只用該系統調用所返回的文件描述符來標識該文件,故只有調用pipe( )的進程及其子孫進程才能識別此文件描述符,才能利用該文件(管道)進行通信。當這些進程不再使用此管道時,核心收回其索引結點。
二種管道的讀寫方式是相同的,本文只講無名管道。
3、pipe文件的建立
分配磁盤和內存索引結點、爲讀進程分配文件表項、爲寫進程分配文件表項、分配用戶文件描述符
4、讀/寫進程互斥
內核爲地址設置一個讀指針和一個寫指針,按先進先出順序讀、寫。
爲使讀、寫進程互斥地訪問pipe文件,需使各進程互斥地訪問pipe文件索引結點中的直接地址項。因此,每次進程在訪問pipe文件前,都需檢查該索引文件是否已被上鎖。若是,進程便睡眠等待,否則,將其上鎖,進行讀/寫。操作結束後解鎖,並喚醒因該索引結點上鎖而睡眠的進程。
三、所涉及的系統調用 
1、pipe( )
建立一無名管道。
系統調用格式
pipe(filedes)
參數定義
int pipe(filedes);
int filedes[2];
其中,filedes[1]是寫入端,filedes[0]是讀出端。
該函數使用頭文件如下:
#include <unistd.h>
#inlcude <signal.h>
#include <stdio.h>
2、read( )
系統調用格式
read(fd,buf,nbyte)
功能:從fd所指示的文件中讀出nbyte個字節的數據,並將它們送至由指針buf所指示的緩衝區中。如該文件被加鎖,等待,直到鎖打開爲止。
參數定義
int read(fd,buf,nbyte);
int fd;
char *buf;
unsigned nbyte;
3、write( )
系統調用格式
read(fd,buf,nbyte)
功能:把nbyte 個字節的數據,從buf所指向的緩衝區寫到由fd所指向的文件中。如文件加鎖,暫停寫入,直至開鎖。
參數定義同read( )。
四、參考程序
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
int pid1,pid2;
main( )

int fd[2];
char outpipe[100],inpipe[100];
pipe(fd); /*創建一個管道*/
while ((pid1=fork( ))= =-1);
if(pid1= =0)
{
lockf(fd[1],1,0);
sprintf(outpipe,"child 1 process is sending message!"); 
/*把串放入數組outpipe中*/
write(fd[1],outpipe,50); /*向管道寫長爲50字節的串*/
sleep(5); /*自我阻塞5秒*/
lockf(fd[1],0,0);
exit(0);
}
else
{
while((pid2=fork( ))= =-1);
if(pid2= =0)
{ lockf(fd[1],1,0); /*互斥*/
sprintf(outpipe,"child 2 process is sending message!");
write(fd[1],outpipe,50);
sleep(5);
lockf(fd[1],0,0);
exit(0);
}
else
{ wait(0); /*同步*/
read(fd[0],inpipe,50); /*從管道中讀長爲50字節的串*/
printf("%s\n",inpipe);
wait(0);
read(fd[0],inpipe,50);
printf("%s\n",inpipe);
exit(0);
}
}
}
五、運行結果
延遲5秒後顯示
child 1 process is sending message!
再延遲5秒
child 2 process is sending message!

  

進程通信(消息的發送與接受實驗)
一、什麼是消息
消息(message)是一個格式化的可變長的信息單元。消息機制允許由一個進程給其它任意的進程發送一個消息。當一個進程收到多個消息時,可將它們排成一個消息隊列。消息使用二種重要的數據結構:一是消息首部,其中記錄了一些與消息有關的信息,如消息數據的字節數;二個消息隊列頭表,其每一表項是作爲一個消息隊列的消息頭,記錄了消息隊列的有關信息。
1、消息機制的數據結構
(1)消息首部
記錄一些與消息有關的信息,如消息的類型、大小、指向消息數據區的指針、消息隊列的鏈接指針等。
(2)消息隊列頭表
其每一項作爲一個消息隊列的消息頭,記錄了消息隊列的有關信息如指向消息隊列中第一個消息和指向最後一個消息的指針、隊列中消息的數目、隊列中消息數據的總字節數、隊列所允許消息數據的最大字節總數,還有最近一次執行發送操作的進程標識符和時間、最近一次執行接收操作的進程標識符和時間等。
2、消息隊列的描述符
LINUX中,每一個消息隊列都有一個稱爲關鍵字(key)的名字,是由用戶指定的;消息隊列有一消息隊列描述符,其作用與用戶文件描述符一樣,也是爲了方便用戶和系統對消息隊列的訪問。
二、涉及的系統調用
1. msgget( )
創建一個消息,獲得一個消息的描述符。核心將搜索消息隊列頭表,確定是否有指定名字的消息隊列。若無,核心將分配一新的消息隊列頭,並對它進行初始化,然後給用戶返回一個消息隊列描述符,否則它只是檢查消息隊列的許可權便返回。
系統調用格式:
msgqid=msgget(key,flag)
該函數使用頭文件如下:
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
參數定義
int msgget(key,flag)
key_t key;
int flag;
其中:
key是用戶指定的消息隊列的名字;flag是用戶設置的標誌和訪問方式。如 IPC_CREAT |0400 是否該隊列已被創建。無則創建,是則打開;
IPC_EXCL |0400 是否該隊列的創建應是互斥的。
msgqid 是該系統調用返回的描述符,失敗則返回-1。
2. msgsnd()
發送一消息。向指定的消息隊列發送一個消息,並將該消息鏈接到該消息隊列的尾部。
系統調用格式:
msgsnd(msgqid,msgp,size,flag)
該函數使用頭文件如下:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
參數定義:
int msgsnd(msgqid,msgp,size,flag)
I int msgqid,size,flag;
struct msgbuf * msgp;
其中msgqid是返回消息隊列的描述符;msgp是指向用戶消息緩衝區的一個結構體指針。緩衝區中包括消息類型和消息正文,即
{
long mtype; /*消息類型*/
char mtext[ ]; /*消息的文本*/
}
size指示由msgp指向的數據結構中字符數組的長度;即消息的長度。這個數組的最大值由MSG-MAX( )系統可調用參數來確定。flag規定當核心用盡內部緩衝空間時應執行的動作:進程是等待,還是立即返回。若在標誌flag中未設置IPC_NOWAIT位,則當該消息隊列中的字節數超過最大值時,或系統範圍的消息數超過某一最大值時,調用msgsnd進程睡眠。若是設置IPC_NOWAIT,則在此情況下,msgsnd立即返回。
對於msgsnd( ),核心須完成以下工作:
(1)對消息隊列的描述符和許可權及消息長度等進行檢查。若合法才繼續執行,否則返回;
(2)核心爲消息分配消息數據區。將用戶消息緩衝區中的消息正文,拷貝到消息數據區;
(3)分配消息首部,並將它鏈入消息隊列的末尾。在消息首部中須填寫消息類型、消息大小和指向消息數據區的指針等數據;
(4)修改消息隊列頭中的數據,如隊列中的消息數、字節總數等。最後,喚醒等待消息的進程。
3. msgrcv( )
接受一消息。從指定的消息隊列中接收指定類型的消息。
系統調用格式:
msgrcv(msgqid,msgp,size,type,flag)
本函數使用的頭文件如下:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
參數定義:
int msgrcv(msgqid,msgp,size,type,flag)
int msgqid,size,flag;
struct msgbuf *msgp;
long type;
其中,msgqid,msgp,size,flag與msgsnd中的對應參數相似,type是規定要讀的消息類型,flag規定倘若該隊列無消息,核心應做的操作。如此時設置了IPC_NOWAIT標誌,則立即返回,若在flag中設置了MS_NOERROR,且所接收的消息大於size,則核心截斷所接收的消息。
對於msgrcv系統調用,核心須完成下述工作:
(1)對消息隊列的描述符和許可權等進行檢查。若合法,就往下執行;否則返回;
(2)根據type的不同分成三種情況處理:
type=0,接收該隊列的第一個消息,並將它返回給調用者;
type爲正整數,接收類型type的第一個消息;
type爲負整數,接收小於等於type絕對值的最低類型的第一個消息。
(3)當所返回消息大小等於或小於用戶的請求時,核心便將消息正文拷貝到用戶區,並從消息隊列中刪除此消息,然後喚醒睡眠的發送進程。但如果消息長度比用戶要求的大時,則做出錯返回。
4. msgctl( )
消息隊列的操縱。讀取消息隊列的狀態信息並進行修改,如查詢消息隊列描述符、修改它的許可權及刪除該隊列等。
系統調用格式:
msgctl(msgqid,cmd,buf);
本函數使用的頭文件如下:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
參數定義:
int msgctl(msgqid,cmd,buf);
int msgqid,cmd;
struct msgqid_ds *buf;
其中,函數調用成功時返回0,不成功則返回-1。buf是用戶緩衝區地址,供用戶存放控制參數和查詢結果;cmd是規定的命令。命令可分三類:
(1)IPC_STAT。查詢有關消息隊列情況的命令。如查詢隊列中的消息數目、隊列中的最大字節數、最後一個發送消息的進程標識符、發送時間等;
(2)IPC_SET。按buf指向的結構中的值,設置和改變有關消息隊列屬性的命令。如改變消息隊列的用戶標識符、消息隊列的許可權等;
(3)IPC_RMID。消除消息隊列的標識符。
msgqid_ds 結構定義如下:
struct msgqid_ds
{ struct ipc_perm msg_perm; /*許可權結構*/
short pad1[7]; /*由系統使用*/
ushort msg_qnum; /*隊列上消息數*/
ushort msg_qbytes; /*隊列上最大字節數*/
ushort msg_lspid; /*最後發送消息的PID*/
ushort msg_lrpid; /*最後接收消息的PID*/
time_t msg_stime; /*最後發送消息的時間*/
time_t msg_rtime; /*最後接收消息的時間*/
time_t msg_ctime; /*最後更改時間*/
};
struct ipc_perm
{ ushort uid; /*當前用戶*/
ushort gid; /*當前進程組*/
ushort cuid; /*創建用戶*/
ushort cgid; /*創建進程組*/
ushort mode; /*存取許可權*/
{ short pid1; long pad2;} /*由系統使用*/ 
}
三、參考程序
1、client.c
#include <sys/types.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#define MSGKEY 75
struct msgform
{ long mtype;
char mtext[1000];
}msg;
int msgqid;

void client()
{
int i;
msgqid=msgget(MSGKEY,0777); /*打開75#消息隊列*/
for(i=10;i>=1;i–)
{
msg.mtype=i;
printf(“(client)sent\n”);
msgsnd(msgqid,&msg,1024,0); /*發送消息*/
}
exit(0);
}

main( )

client( );
}

2、server.c
#include <sys/types.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#define MSGKEY 75
struct msgform
{ long mtype;
char mtext[1000];
}msg;
int msgqid;

void server( )
{
msgqid=msgget(MSGKEY,0777|IPC_CREAT); /*創建75#消息隊列*/
do 
{
msgrcv(msgqid,&msg,1030,0,0); /*接收消息*/
printf(“(server)received\n”);
}while(msg.mtype!=1);
msgctl(msgqid,IPC_RMID,0); /*刪除消息隊列,歸還資源*/
exit(0);
}

main( )

server( );
}
四、程序說明
1、爲了便於操作和觀察結果,編制二個程序client.c和server.c,分別用於消息的發送與接收。
2、server建立一個 Key 爲75的消息隊列,等待其它進程發來的消息。當遇到類型爲1的消息,則作爲結束信號,取消該隊列,並退出server。server每接收到一個消息後顯示一句“(server)received。”
3、client使用 key爲75的消息隊列,先後發送類型從10到1的消息,然後退出。最後一個消息,即是 server端需要的結束信號。client 每發送一條消息後顯示一句 “(client)sent”。
4、注意: 二個程序分別編輯、編譯爲client與server。執行:
./server&
ipcs -q
./client。
五、運行結果
從理想的結果來說,應當是每當client發送一個消息後,server接收該消息,client再發送下一條。也就是說“(client)sent”和 “(server)received”的字樣應該在屏幕上交替出現。實際的結果大多是,先由client發送了兩條消息,然後server接收一條消息。此後client 、server交替發送和接收消息。最後server一次接收兩條消息。client 和server 分別發送和接收了10條消息,與預期設想一致。

  

進程通信(共享存儲區通信)
一、共享存儲區
1、共享存儲區機制的概念
共享存儲區(Share Memory)是LINUX系統中通信速度最高的一種通信機制。該機制可使若干進程共享主存中的某一個區域,且使該區域出現(映射)在多個進程的虛地址空間中。另一方面,一個進程的虛地址空間中又可連接多個共享存儲區,每個共享存儲區都有自己的名字。當進程間欲利用共享存儲區進行通信時,必須先在主存中建立一共享存儲區,然後將它附接到自己的虛地址空間上。此後,進程對該區的訪問操作,與對其虛地址空間的其它部分的操作完全相同。進程之間便可通過對共享存儲區中數據的讀、寫來進行直接通信。圖示列出二個進程通過共享一個共享存儲區來進行通信的例子。其中,進程A將建立的共享存儲區附接到自己的AA’區域,進程B將它附接到自己的BB’區域。

進程A的虛空間 內存空間 進程B的虛空間
A
A’
應當指出,共享存儲區機制只爲進程提供了用於實現通信的共享存儲區和對共享存儲區進行操作的手段,然而並未提供對該區進行互斥訪問及進程同步的措施。因而當用戶需要使用該機制時,必須自己設置同步和互斥措施才能保證實現正確的通信。
二、涉及的系統調用
1、shmget( )
創建、獲得一個共享存儲區。
系統調用格式:
shmid=shmget(key,size,flag)
該函數使用頭文件如下:
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
參數定義
int shmget(key,size,flag);
key_t key;
int size,flag;
其中,key是共享存儲區的名字;size是其大小(以字節計);flag是用戶設置的標誌,如IPC_CREAT。IPC_CREAT表示若系統中尚無指名的共享存儲區,則由核心建立一個共享存儲區;若系統中已有共享存儲區,便忽略IPC_CREAT。
附:
操作允許權 八進制數
用戶可讀 00400
用戶可寫 00200
小組可讀 00040
小組可寫 00020 
其它可讀 00004
其它可寫 00002

控制命令 值
IPC_CREAT 0001000
IPC_EXCL 0002000
例:shmid=shmget(key,size,(IPC_CREAT|0400))
創建一個關鍵字爲key,長度爲size的共享存儲區
2、shmat( )
共享存儲區的附接。從邏輯上將一個共享存儲區附接到進程的虛擬地址空間上。
系統調用格式:
virtaddr=shmat(shmid,addr,flag)
該函數使用頭文件如下:
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
參數定義
char *shmat(shmid,addr,flag);
int shmid,flag;
char * addr;
其中,shmid是共享存儲區的標識符;addr是用戶給定的,將共享存儲區附接到進程的虛地址空間;flag規定共享存儲區的讀、寫權限,以及系統是否應對用戶規定的地址做舍入操作。其值爲SHM_RDONLY時,表示只能讀;其值爲0時,表示可讀、可寫;其值爲SHM_RND(取整)時,表示操作系統在必要時捨去這個地址。該系統調用的返回值是共享存儲區所附接到的進程虛地址viraddr。
3、shmdt( )
把一個共享存儲區從指定進程的虛地址空間斷開。
系統調用格式:
shmdt(addr)
該函數使用頭文件如下:
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
參數定義
int shmdt(addr);
char addr;
其中,addr是要斷開連接的虛地址,亦即以前由連接的系統調用shmat( )所返回的虛地址。調用成功時,返回0值,調用不成功,返回-1。
4、shmctl( )
共享存儲區的控制,對其狀態信息進行讀取和修改。
系統調用格式:
shmctl(shmid,cmd,buf)
該函數使用頭文件如下:
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
參數定義
int shmctl(shmid,cmd,buf);
int shmid,cmd;
struct shmid_ds *buf;
其中,buf是用戶緩衝區地址,cmd是操作命令。命令可分爲多種類型:
(1)用於查詢有關共享存儲區的情況。如其長度、當前連接的進程數、共享區的創建者標識符等;
(2)用於設置或改變共享存儲區的屬性。如共享存儲區的許可權、當前連接的進程計數等;
(3)對共享存儲區的加鎖和解鎖命令;
(4)刪除共享存儲區標識符等。
上述的查詢是將shmid所指示的數據結構中的有關成員,放入所指示的緩衝區中;而設置是用由buf所指示的緩衝區內容來設置由shmid所指示的數據結構中的相應成員。
三、參考程序
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#define SHMKEY 75
int shmid,i; int *addr;

void client( )
{ int i;
shmid=shmget(SHMKEY,1024,0777); /*打開共享存儲區*/
addr=shmat(shmid,0,0); /*獲得共享存儲區首地址*/
for (i=9;i>=0;i–)
{ while (*addr!=-1);
printf("(client) sent\n");
*addr=i;
}
exit(0);
}

void server( )
{
shmid=shmget(SHMKEY,1024,0777|IPC_CREAT); /*創建共享存儲區*/
addr=shmat(shmid,0,0); /*獲取首地址*/
do 
{
*addr=-1;
while (*addr==-1);
printf("(server) received\n");
}while (*addr);
shmctl(shmid,IPC_RMID,0); /*撤消共享存儲區,歸還資源*/
exit(0);
}

main( )
{
while ((i=fork( ))= =-1);
if (!i) server( );
system(“ipcs -m”);
while ((i=fork( ))= =-1);
if (!i) client( );
wait(0);
wait(0);
}
四、程序說明
1、爲了便於操作和觀察結果,用一個程序作爲“引子“,先後fork()兩個子進程,server和client,進行通信。
2、server端建立一個key爲75的共享區,並將第一個字節置爲-1,作爲數據空的標誌。等待其他進程發來的消息。當該字節的值發生變化時,表示收到了信息,進行處理。然後再次把它的值設爲-1,如果遇到的值爲0,則視爲爲結束信號,取消該隊列,並退出server。server每接收到一次數據後顯示“(server)received”。
3、client端建立一個key爲75的共享區,當共享取得第一個字節爲-1時,server端空閒,可發送請求。client隨即填入9到0。期間等待 server 端的再次空閒。進行完這些操作後,client退出。client每發送一次數據後顯示“(client)sent”。
4、父進程在server和client均退出後結束。
五、運行結果
和預想的完全一樣。但在運行過程中,發現每當client發送一次數據後,server要等待大約0.1秒纔有響應。同樣,之後client又需要等待大約0.1秒才發送下一個數據。
六、程序分析
出現上述應答延遲的現象是程序設計的問題。當client端發送了數據後,並沒有任何措施通知server端數據已經發出,需要由client的查詢才能感知。此時,client端並沒有放棄系統的控制權,仍然佔用CPU的時間片。只有當系統進行調度時,切換到了server進程,再進行應答。這個問題,也同樣存在於server端到client的應答過程中。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章