UNIX多進程編程
寫在前面的話
本文主要根據本人在UNIX系統上的編程實踐經驗總結而成, 既做為自己在一個時期內編程實踐的部分總結, 又可成為文章發表. 對UNIX程序員初學者來說是一個小小的經驗, 僅供參考; 對UNIX老手來說則不值一哂, 請各位多多指教.
一.多進程程序的特點
由於UNIX系統是分時多用戶系統, CPU按時間片分配給各個用戶使用, 而在實質上應該說CPU按時間片分配給各個進程使用, 每個進程都有自己的運行環境以使得在CPU做進程切換時不會"忘記"該進程已計算了一半的"半成品". 以DOS的概念來說, 進程的切換都是一次"DOS中斷"處理過程, 包括三個層次:
(1)用戶數據的保存: 包括正文段(TEXT), 數據段(DATA,BSS), 棧段(STACK), 共享內存段(SHARED MEMORY)的保存.
(2)寄存器數據的保存: 包括PC(program counter,指向下一條要執行的指 令的地址), PSW(processor status word,處理機狀態字), SP(stack pointer,棧指針), PCBP(pointer of process control block,進程控制塊指針), FP(frame pointer,指向棧中一個函數的local變量的首地址), AP(augument pointer,指向棧中函數調用的實參位置), ISP(interrupt stack pointer,中斷棧指針), 以及其他的通用寄存器等.
(3)系統層次的保存: 包括proc,u,虛擬存儲空間管理表格,中斷處理棧.以便於該進程再一次得到CPU時間片時能正常運行下去.既然系統已經處理好所有這些中斷處理的過程, 我們做程序還有什麼要擔心的呢? 我們盡可以使用系統提供的多進程的特點, 讓幾個程序精誠合作, 簡單而又高效地把結果給它搞出來.
另外,UNIX系統本身也是用C語言寫的多進程程序,多進程編程是UNIX的特點,當我們熟悉了多進程編程後,將會對UNIX系統機制有一個較深的認識.首先我介紹一下多進程程序的一些突出的特點:
1.並行化
一件復雜的事件是可以分解成若干個簡單事件來解決的, 這在程序員的大腦中早就形成了這種概念, 首先將問題分解成一個個小問題, 將小問題再細分, 最後在一個合適的規模上做成一個函數. 在軟件工程中也是這麼說的. 如果我們以圖的方式來思考, 一些小問題的計算是可以互不干擾的, 可以同時處理, 而在關鍵點則需要統一在一個地方來處理, 這樣程序的運行就是並行的, 至少從人的時間觀念上來說是這樣的. 而每個小問題的計算又是較簡單的.
2.簡單有序
這樣的程序對程序員來說不亞於管理一班人, 程序員為每個進程設計好相應的功能, 並通過一定的通訊機制將它們有機地結合在一起, 對每個進程的設計是簡單的, 只在總控部分小心應付(其實也是蠻簡單的), 就可完成整個程序的施工.
3.互不干擾
這個特點是操作系統的特點, 各個進程是獨立的, 不會串位.
4.事務化
比如在一個數據電話查詢系統中, 將程序設計成一個進程只處理一次查詢即可, 即完成一個事務. 當電話查詢開始時, 產生這樣一個進程對付這次查詢; 另一個電話進來時, 主控程序又產生一個這樣的進程對付, 每個進程完成查詢任務後消失. 這樣的編程多簡單, 只要做一次查詢的程序就可以了.
二.常用的多進程編程的系統調用
1.fork()
功能:創建一個新的進程.
語法:#include
#include
pid_t fork();
說明:本系統調用產生一個新的進程, 叫子進程, 是調用進程的一個復制品. 調用進程叫父進程, 子進程繼承了父進程的幾乎所有的屬性:
. 實際UID,GID和有效UID,GID.
. 環境變量.
. 附加GID.
. 調用exec()時的關閉標志.
. UID設置模式比特位.
. GID設置模式比特位.
. 進程組號.
. 會話ID.
. 控制終端.
. 當前工作目錄.
. 根目錄.
. 文件創建掩碼UMASK.
. 文件長度限制ULIMIT.
. 預定值, 如優先級和任何其他的進程預定參數, 根據種類不同決定是否可以繼承.
. 還有一些其它屬性.
但子進程也有與父進程不同的屬性:
. 進程號, 子進程號不同與任何一個活動的進程組號.
. 父進程號.
. 子進程繼承父進程的文件描述符或流時, 具有自己的一個拷貝並且與父進程和其它子進程共享該資源.
. 子進程的用戶時間和系統時間被初始化為0.
. 子進程的超時時鐘設置為0.
. 子進程的信號處理函數指針組置為空.
. 子進程不繼承父進程的記錄鎖.
返回值: 調用成功則對子進程返回0, 對父進程返回子進程號, 這也是最方便的區分父子進程的方法. 若調用失敗則返回-1給父進程,子進程不生成.
例子:pid_t pid;
if ((pid=fork())>0) {
}
else if (pid==0) {
exit(0); /*注意子進程必須用exit()退出運行*/
}
else {
printf("fork error\n");
exit(0);
}
2.system()
功能:產生一個新的進程, 子進程執行指定的命令.
語法:#include
#include
int system(string)
char *string;
說明:本調用將參數string傳遞給一個命令解釋器(一般為sh)執行, 即string被解釋為一條命令, 由sh執行該命令.若參數string為一個空指針則為檢查命令解釋器是否存在.
該命令可以同命令行命令相同形式, 但由於命令做為一個參數放在系統調用中, 應注意編譯時對特殊意義字符的處理. 命令的查找是按PATH環境變量的定義的. 命令所生成的後果一般不會對父進程造成影響.
返回值:當參數為空指針時, 只有當命令解釋器有效時返回值為非零.
若參數不為空指針, 返回值為該命令的返回狀態(同waitpid())的返回值. 命令無效或語法錯誤則返回非零值,所執行的命令被終止. 其他情況則返回-1.
例子:char command[81];
int i;
for (i=1;i<8;i++) {
sprintf(command,"ps -t tty%02i",i);
system(command);
}
3.exec()
功能:執行一個文件
語法:#include
int execl(path,arg0,...,argn,(char*)0)
char *path,*arg0,...,*argn;
int execv(path,argv)
char *path,*argv[];
int execle(path,arg0,...,argn,(char*)0,envp)
char *path,*arg0,...,*argn,*envp[];
int execve(path,argv,envp)
char *path,*argv[],*envp[];
int execvp(file,argv)
char *file,*argv[];
說明:這是一個系統調用族, 用於將一個新的程序調入本進程所占的內存, 並覆蓋之, 產生新的內存進程映象. 新的程序可以是可執行文件或SHELL批命令.
當C程序被執行時,是如下調用的:
main(int argc,char *argv[],char *envp[]);
argc是參數個數,是各個參數字符串指針數組,envp是新進程的環境變量字符串的指針數組.argc至少為1,argv[0]為程序文件名,所以,在上面的exec系統調用族中,path為新進程文件的路徑名,file為新進程文件名,若file不是全路徑名,系統調用會按PATH環境變量自動找對應的可執行文件運行.若新進程文件不是一個可執行的目標文件(如批處理文件),則execlp()和execvp()會將該文件內容作為一個命令解釋器的標准輸入形成system().arg0,...等指針指向'\0'結束的字符串,組成新進程的有效參數,且該參數列表以一個空指針結束.反過來,arg0至少必須存在並指向新進程文件名或路徑名.同樣,argv是字符串指針數組,argv[0]指向新進程文件名或路徑名,並以一空指針結束.
envp是一個字符串指針數組,以空指針結束,這些字符串組成新進程的環境.
在調用這些系統調用前打開的文件指針對新進程來說也是打開的,除非它已定義了close-on-exec標志.打開的文件指針在新進程中保持不變,所有相關的文件鎖也被保留.
調用進程設置並正被捕俘的信號在新進程中被恢復為缺省設置,其它的則保持不變.
新進程啟動時按文件的SUID和SGID設置定義文件的UID和GID為有效UID和GID.
新進程還繼承了如下屬性:
. 附加GID.
. 進程號.
. 父進程號.
. 進程組號.
. 會話號.
. 控制終端.
. alarm時鐘信號剩下的時間.
. 當前工作目錄.
. 根目錄.
. 文件創建掩碼.
. 資源限制.
. 用戶時間,系統時間,子進程用戶時間,子進程系統時間.
. 記錄鎖.
. 進程信號掩碼.
. 信號屏蔽.
. 優先級.
. 預定值.
調用成功後,系統調用修改新進程文件的最新訪問時間.
返回值:該系統調用一般不會有成功返回值, 因為原來的進程已蕩然無存.
例子:printf("now this process will be ps command\n");
execl("/bin/ps","ps","-ef",NULL);
4.popen()
功能:初始化從/到一個進程的管道.
語法:#include
FILE *popen(command,type)
char *command,type;
說明:本系統調用在調用進程和被執行命令間創建一個管道.參數command做為被執行的命令行.type做為I/O模式,"r"為從被執行命令讀,"w"為向被執行命令寫.返回一個標准流指針,做為管道描述符,向被執行命令讀或寫數據(做為被執行命令的STDIN或STDOUT)該系統調用可以用來在程序中調用系統命令,並取得命令的輸出信息或者向命令輸入信息.
返回值:不成功則返回NULL,成功則返回管道的文件指針.
5.pclose()
功能:關閉到一個進程的管道.
語法:#include
int pclose(strm)
FILE *strm;
說明:本系統調用用於關閉由popen()打開的管道,並會等待由popen()激活的命令執行結束後,關閉管道後讀取命令返回碼.
返回值:若關閉的文件描述符不是由popen()打開的,則返回-1.
例子:printf("now this process will call popen system call\n");
FILE * fd;
if ((fd=popen("ps -ef","r"))==NULL) {
printf("call popen failed\n");
return;
}
else {
char str[80];
while (fgets(str,80,fd)!=NULL)
printf("%s\n",str);
}
pclose(fd);
6.wait()
功能:等待一個子進程返回並修改狀態
語法:#include
#include
pid_t wait(stat_loc)
int *stat_loc;
說明:允許調用進程取得子進程的狀態信息.調用進程將會掛起直到其一個子進程終止.
返回值:等待到一個子進程返回時,返回值為該子進程號,否則返回值為-1.同時stat_loc返回子進程的返回值.
例子:/*父進程*/
if (fork()>0) {
wait((int *)0);
}
else {
exit(0);
}
7.waitpid()
功能:等待指定進程號的子進程的返回並修改狀態
語法:#include
#include
pid_t waitpid(pid,stat_loc,options)
pid_t pid;
int *stat_loc,options;
說明:當pid等於-1,options等於0時,該系統調用等同於wait().否則該系統調用的行為由參數pid和options決定.
pid指定了一組父進程要求知道其狀態的子進程:
-1:要求知道任何一個子進程的返回狀態.
>0:要求知道進程號為pid值的子進程的狀態.
<-1:要求知道進程組號為pid的絕對值的子進程的狀態.
options參數為以比特方式表示的標志以或運算組成的位圖,每個標志以字節中某個比特置1表示:
WUNTRACED:報告任何未知而又已停止運行的指定進程號的子進程的狀態.該子進程的狀態自停止運行時起就沒有被報告過.
WCONTINUED:報告任何繼續運行的指定進程號的子進程的狀態,該子進程的狀態自繼續運行起就沒有被報告過.
WHOHANG:若調用本系統調用時,指定進程號的子進程的狀態目前並不是立即有效的(即可被立即讀取的),調用進程並被暫停執行.
WNOWAIT:保持將其狀態設置在stat_loc的進程在可等待狀態.
該進程將等待直到下次被要求其返回狀態值.
返回值:等待到一個子進程返回時,返回值為該子進程號,否則返回值為-1.同時stat_loc返回子進程的返回值.
例子:pid_t pid;
int stat_loc;
if ((pid=fork())>0) {
waitpid(pid,&stat_loc,0);
}
else {
exit(1);
}
printf("stat_loc is [%d]\n",stat_loc);
功能:設置進程組號和會話號.
語法:#include
pid_t setpgrp()
說明:若調用進程不是會話首進程.將進程組號和會話號都設置為與它的進程號相等.並釋放調用進程的控制終端.
返回值:調用成功後,返回新的進程組號.
例子:/*父進程處理*/
if (fork()>0) {
}
else {
setpgrp();
exit(0);
}
9.exit()
功能:終止進程.
語法:#include
void exit(status)
int status;
說明:調用進程被該系統調用終止.引起附加的處理在進程被終止前全部結束.
返回值:無
10.signal()
功能:信號管理功能
語法:#include
void (*signal(sig,disp))(int)
int sig;
void (*disp)(int);
void (*sigset(sig,disp))(int)
int sig;
void (*disp)(int);
int sighold(sig)
int sig;
int sigrelse(sig)
int sig;
int sigignore(sig)
int sig;
int sigpause(sig)
int sig;
說明:這些系統調用提供了應用程序對指定信號的簡單的信號處理.
signal()和sigset()用於修改信號定位.參數sig指定信號(除了SIGKILL和SIGSTOP,這兩種信號由系統處理,用戶程序不能捕捉到).
disp指定新的信號定位,即新的信號處理函數指針.可以為SIG_IGN,SIG_DFL或信號句柄地址.
若使用signal(),disp是信號句柄地址,sig不能為SIGILL,SIGTRAP或SIGPWR,收到該信號時,系統首先將重置sig的信號句柄為SIG_DFL,然後執行信號句柄.
若使用sigset(),disp是信號句柄地址,該信號時,系統首先將該信號加入調用進程的信號掩碼中,然後執行信號句柄.當信號句柄運行結束後,系統將恢復調用進程的信號掩碼為信號收到前的狀態.另外,使用sigset()時,disp為SIG_HOLD,則該信號將會加入調用進程的信號掩碼中而信號的定位不變.
sighold()將信號加入調用進程的信號掩碼中.
sigrelse()將信號從調用進程的信號掩碼中刪除.
sigignore()將信號的定位設置為SIG_IGN.
sigpause()將信號從調用進程的信號掩碼中刪除,同時掛起調用進程直到收到信號.
若信號SIGCHLD的信號定位為SIG_IGN,則調用進程的子進程在終止時不會變成僵死進程.調用進程也不用等待子進程返回並做相應處理.
返回值:調用成功則signal()返回最近調用signal()設置的disp的值.否則返回SIG_ERR.
例子一:設置用戶自己的信號中斷處理函數,以SIGINT信號為例:
int flag=0;
void myself()
{
flag=1;
printf("get signal SIGINT\n");
*下步驟*/
void (*a)();
a=myself;
signal(SIGINT,a);
flag=2;
}
main()
{
while (1) {
sleep(2000); /*等待中斷信號*/
if (flag==1) {
printf("skip system call sleep\n");
exit(0);
}
if (flag==2) {
printf("skip system call sleep\n");
printf("waiting for next signal\n");
}
}
}
11.kill()
功能:向一個或一組進程發送一個信號.
語法:#include
#include
int kill(pid,sig);
pid_t pid;
int sig;
說明:本系統調用向一個或一組進程發送一個信號,該信號由參數sig指定,為系統給出的信號表中的一個.若為0(空信號)則檢查錯誤但實際上並沒有發送信號,用於檢查pid的有效性.
pid指定將要被發送信號的進程或進程組.pid若大於0,則信號將被發送到進程號等於pid的進程;若pid等於0則信號將被發送到所有的與發送信號進程同在一個進程組的進程(系統的特殊進程除外);若pid小於-1,則信號將被發送到所有進程組號與pid絕對值相同的進程;若pid等於-1,則信號將被發送到所有的進程(特殊系統進程除外).
信號要發送到指定的進程,首先調用進程必須有對該進程發送信號的權限.若調用進程有合適的優先級則具備有權限.若調用進程的實際或有效的UID等於接收信號的進程的實際UID或用setuid()系統調用設置的UID,或sig等於SIGCONT同時收發雙方進程的會話號相同,則調用進程也有發送信號的權限.
若進程有發送信號到pid指定的任何一個進程的權限則調用成功,否則調用失敗,沒有信號發出.
返回值:調用成功則返回0,否則返回-1.
例子:假設前一個例子進程號為324,現向它發一個SIGINT信號,讓它做信號處理:
kill((pid_t)324,SIGINT);
12.alarm()
功能:設置一個進程的超時時鐘.
語法:#include
unsigned int alarm(sec)
unsigned int sec;
說明:指示調用進程的超時時鐘在指定的時間後向調用進程發送一個SIGALRM信號.設置超時時鐘時時間值不會被放入堆棧中,後一次設置會把前一次(還未到超時時間)沖掉.
若sec為0,則取消任何以前設置的超時時鐘.
fork()會將新進程的超時時鐘初始化為0.而當一個進程用exec()族系統調用新的執行文件時,調用前設置的超時時鐘在調用後仍有效.
返回值:返回上次設置超時時鐘後到調用時還剩余的時間秒數.
例子:int flag=0;
void myself()
{
flag=1;
printf("get signal SIGALRM\n");
*以下步驟*/
void (*a)();
a=myself;
signal(SIGALRM,a);
flag=2;
}
main()
{
alarm(100); /*100秒後發超時中斷信號*/
while (1) {
sleep(2000); /*等待中斷信號*/
if (flag==1) {
printf("skip system call sleep\n");
exit(0);
}
if (flag==2) {
printf("skip system call sleep\n");
printf("waiting for next signal\n");
}
}
}
13.msgsnd()
功能:發送消息到指定的消息隊列中.
語法:#include
#include
#include
int msgsnd(msqid,msgp,msgsz,msgflg)
int msqid;
void *msgp;
size_t msgsz;
int msgflg;
說明:發送一個消息到由msqid指定消息隊列標識號的消息隊列.
參數msgp指向一個用戶定義的緩沖區,並且緩沖區的第一個域應為長整型,指定消息類型,其他數據放在緩沖區的消息中其他正文區內.下面是消息元素定義:
long mtype;
char mtext[];
mtype是一個整數,用於接收進程選擇消息類型.
mtext是一個長度為msgsz字節的任何正文,參數msgsz可從0到系統允許的最大值間變化.
msgflg指定操作行為:
. 若(msgflg&IPC_NOWAIT)是真的,消息並不是被立即發送而調用進程會立即返回.
. 若(msgflg&IPC_NOWAIT)不是真的,則調用進程會被掛起直到下面情況之一發生:
* 消息被發送出去.
* 消息隊列標志被系統刪除.系統調用返回-1.
* 調用進程接收到一個未被忽略的中斷信號,調用進程繼續執行或被終止.
調用成功後,對應指定的消息隊列的相關結構做如下動作:
. 消息數(msg_qnum)加1.
. 消息隊列最近發送進程號(msg_lspid)改為調用進程號.
. 消息隊列發送時間(msg_stime)改為當前系統時間.
以上信息可用命令ipcs -a看到.
返回值:成功則返回0,否則返回-1.
14.msgrcv()
功能:從消息隊列中取得指定類型的消息.
語法:#include
#include
#include
int msgrcv(msqid,msgp,msgsz,msgtyp,msgflg)
int msqid;
void *msgp;
int msgsz;
long msgtyp;
int msgflg;
說明:本系統調用從由msqid指定的消息隊列中讀取一個由msgtyp指定類型的消息到由msgp指向的緩沖區中,同樣的,該緩沖區的結構如前所述,包括消息類型和消息正文.msgsz為可接收的消息正文的字節數.若接收到的消息正文的長度大於msgsz,則會被截短到msgsz字節為止(當消息標志msgflg&MSG_NOERROR為真時),截掉的部份將被丟失,而且不通知消息發送進程.
msgtyp指定消息類型:
. 為0則接收消息隊列中第一個消息.
. 大於0則接收消息隊列中第一個類型為msgtyp的消息.
. 小於0則接收消息隊列中第一個類型值不小於msgtyp絕對值且類型值又最小的消息.
msgflg指定操作行為:
. 若(msgflg&IPC_NOWAIT)是真的,調用進程會立即返回,若沒有接收到消息則返回值為-1,errno設置為ENOMSG.
. 若(msgflg&IPC_NOWAIT)不是真的,則調用進程會被掛起直到下面情況之一發生:
* 隊列中的消息的類型是有效的.
* 消息隊列標志被系統刪除.系統調用返回-1.
* 調用進程接收到一個未被忽略的中斷信號,調用進程繼續執行或被終止.
調用成功後,對應指定的消息隊列的相關結構做如下動作:
. 消息數(msg_qnum)減1.
. 消息隊列最近接收進程號(msg_lrpid)改為調用進程號.
. 消息隊列接收時間(msg_rtime)改為當前系統時間.
以上信息可用命令ipcs -a看到.
返回值:調用成功則返回值等於接收到實際消息正文的字節數.
不成功則返回-1.
15.msgctl()
功能:消息控制操作
語法:#include
#include
#include
int msgctl(msqid,cmd,buf)
int msqid,cmd;
struct msqid_ds *buf;
說明:本系統調用提供一系列消息控制操作,操作動作由cmd定義,以下cmd定義值表明了各操作動作的定義.
. IPC_STAT:將msqid相關的數據結構中各個元素的當前值放入由buf指向的結構中.
. IPC_SET:將msqid相關的數據結構中的下列元素設置為由buf指向的結構中的對應值.
msg_perm.uid
msg_perm.gid
msg_perm.mode
msg_qbytes
本命令只能由有效UID等於msg_perm.cuid或msg_perm.uid的進程或有效UID有合適權限的進程操作.只有具有合適權限的用戶才能增加msg_qbytes的值.
. IPC_RMID:刪除由msqid指示的消息隊列.將它從系統中刪除並破壞相關的數據結構.
本命令只能由有效UID等於msg_perm.cuid或msg_perm.uid的進程或有效UID有合適權限的進程操作.
返回值:調用成功則返回值為0,否則為-1.
16.msgget()
功能:取得一個消息隊列.
語法:#include
#include
#include
int msgget(key,msgflg)
key_t key;
int msgflg;
說明:本系統調用返回與參數key相關的消息隊列的標識符.
若以下事實成立,則與消息隊列相關的標識符和數據結構將被創建出來:
. 若參數key等於IPC_PRIVATE.
. 若參數key沒有一個已存在的消息隊列標識符與之相關,同時值(msgflg&IPC_CREAT)為真.
創建消息隊列時,與新的消息隊列標識符相關的數據結構將被初始化為如下:
. msg_perm.cuid和msg_perm.uid設置為調用進程的有效UID.
. msg_perm.cgid和msg_perm.gid設置為調用進程的有效GID.
. msg_perm.mode訪問權限比特位設置為msgflg訪問權限比特位.
. msg_qnum,msg_lspid,msg_lrpid,msg_stime,msg_rtime設置為0.
. msg_ctime設置為當前系統時間.
. msg_qbytes設置為系統允許的最大值.
返回值:調用成功則返回一非0值,稱為消息隊列標識符;否則返回值為-1.
例子:本例將包括上述所有消息隊列操作的系統調用:
#define RKEY 0x9001L /*讀消息隊列的KEY值*/
#define WKEY 0x9002L /*寫消息隊列的KEY值*/
#define MSGFLG 0666 /*消息隊列訪問權限*/
#define IPC_WAIT 0 /*等待方式在include文件中未定義*/
int rmsqid; /*讀消息隊列標識符*/
int wmsqid; /*寫消息隊列標識符*/
struct msgbuf {
long mtype;
char mtext[200];
} buf;
if ((rmsqid=msgget(RKEY,MSGFLG|IPC_CREAT))<0) {
printf("get read message queue failed\n");
exit(1);
}
if ((wmsqid=msgget(WKEY,
MSGFLG|IPC_CREAT|IPC_TRUNC))<0) {
printf("get write message queue failed\n");
exit(2);
}
if (msgrcv(rmsqid,&buf,sizeof(struct msgbuf)-sizeof(long),
0L,IPC_WAIT)>0)
printf("get %ld type message from queue:%s\n",
buf.mtype,buf.mtext);
else {
printf("get message failed\n");
exit(3);
}
buf.mtype=3L
if (msgsnd(wmsqid,&buf,sizeof(struct msgbuf)-sizeof(long),
IPC_NOWAIT)>0)
printf("send message OK\n");
else {
printf("send message failed\n");
exit(4);
}
msgctl(wmsqid,IPC_RMID,(struct msqid *)NULL);
17.shmat()
功能:聯接共享內存的操作.
語法:#include
#include
#include
void *shmat(shmid,shmaddr,shmflg)
int shmid;
void *shmaddr;
int shmid;
說明:將由shmid指示的共享內存聯接到調用進程的數據段中.被聯接的段放在地址,該地址由以下准則指定:
. 若shmaddr等於(void *)0,則段聯接到由系統選擇的第一個可用的地址上.
. 若shmaddr不等於(void *)0同時(shmflg&SHM_RND)值為真,則段聯接到由(shmaddr-(shmaddr%SHMLBA))給出的地址上.
. 若shmaddr不等於(void *)0同時(shmflg&SHM_RND)值為假,則段聯接到由shmaddr指定的地址上.
若(shmflg&sSHM_RDONLY)為真並且調用進程有讀允許,則被聯接的段為只讀;否則,若值不為真且調用進程有讀寫權限,則被聯接的段為可讀寫的.
返回值:若調用成功則返回被聯接的共享內存段在數據段上的啟始地址.
否則返回值為-1.
18.shmdt()
功能:斷開共享內存聯接的操作.
語法:#include
#include
#include
void *shmdt(shmaddr)
void *shmaddr;
說明:本系統調用將由shmaddr指定的共享內存段從調用進程的數據段脫離出去.
返回值:若調用成功則返回值為0,否則返回值為-1.
19.shmget()
功能:取得共享內存段
語法:#include
#include
#include
int shmget(key,size,shmflg)
key_t key;
int size,shmflg;
說明:本系統調用返回key相關的共享內存標識符.
共享內存標識符和相關數據結構及至少size字節的共享內存段能正常創建,要求以下事實成立:
. 參數key等於IPC_PRIVATE.
. 參數key沒有相關的共享內存標識符,同時(shmflg&IPC_CREAT)值為真.
共享內存創建時,新生成的共享內存標識相關的數據結構被初始化如下:
. shm_perm.cuid和shm_perm.uid設置為調用進程的有效UID.
. shm_perm.cgid和shm_perm.gid設置為調用進程的有效GID.
. shm_perm.mode訪問權限比特位設置為shmflg訪問權限比特位.
. shm_lpid,shm_nattch,shm_atime,shm_dtime設置為0.
. shm_ctime設置為當前系統時間.
. shm_segsz設置為0.
返回值:若調用成功則返回一個非0值,稱為共享內存標識符,否則返回值為-1.
20.shmctl()
功能:共享內存控制操作.
語法:#include
#include
#include
int shmctl(shmid,cmd,buf)
int shmid,cmd;
struct shmid_ds *buf;
說明:本系統調用提供一系列共享內存控制操作.操作行為由cmd指定.
以下為cmd的有效值:
. IPC_STAT:將shmid相關的數據結構中各個元素的當前值放入由buf指向的結構中.
. IPC_SET:將shmid相關的數據結構中的下列元素設置為由buf指向的結構中的對應值.
shm_perm.uid
shm_perm.gid
shm_perm.mode
本命令只能由有效UID等於shm_perm.cuid或shm_perm.uid的進程或有效UID有合適權限的進程操作.
. IPC_RMID:刪除由shmid指示的共享內存.將它從系統中刪除並破壞相關的數據結構.
本命令只能由有效UID等於shm_perm.cuid或shm_perm.uid的進程或有效UID有合適權限的進程操作.
返回值:若調用成功則返回0,否則返回-1.
例子:本例包括上述所有共享內存操作系統調用:
#include
#include
#include
#define SHMKEY 74
#define K 1024
int shmid;
cleanup()
{
shmctl(shmid,IPC_RMID,0);
exit(0);
}
main()
{
int *pint;
char *addr1,*addr2;
extern char *shmat();
extern cleanup();
for (i=0;i<20;i++)
signal(i,cleanup);
shmid=shmget(SHMKEY,128*K,0777|IPC_CREAT);
addr1=shmat(shmid,0,0);
addr2=shmat(shmid,0,0);
printf("addr1 0x%x addr2 0x%x\n",addr1,addr2);
pint=(int*)addr1;
for (i=0;i<256;i++)
*pint++=i;
pint=(int*)addr1;
*pint=256;
pint=(int*)addr2;
for (i=0;i<256;i++)
printf("index %d\tvalue%d\n",i,*pint++);
shmdt(addr1);
shmdt(addr2);
pause();
}
功能:信號量控制操作.
語法:#include
#include
#include
int semctl(semid,memnum,cmd,arg)
int semid,semnum,cmd;
union semun {
int val;
struct semid_ds *buf;
ushort *array;
}arg;
說明:本系統調用提供了一個信號量控制操作,操作行為由cmd定義,這些命令是對由semid和semnum指定的信號量做操作的.每個命令都要求有相應的權限級別:
. GETVAL:返回semval的值,要求有讀權限.
. SETVAL:設置semval的值到arg.val上.此命令成功執行後,semadj的值對應的所有進程的信號量全部被清除,要求有修改權限.
. GETPID:返回sempid的值,要求有讀權限.
. GETNCNT:返回semncnt的值,要求有讀權限.
. GETZCNT:返回semzcnt的值,要求有讀權限.
以下命令在一組信號量中的各個semval上操作:
. GETALL:返回每個semval的值,同時將各個值放入由arg.array指向的數組中.當此命令成功執行後,semadj的值對應的所有進程的信號量全部被清除,要求有修改權限.
. SETALL:根據由arg.array指向的數組設置各個semval值.當此命令成功執行後,semadj的值對應的所有進程的信號量全部被清除,要求有修改權限.
以下命令在任何情況下都是有效的:
. IPC_STAT:將與semid相關的數據結構的各個成員的值放入由arg.buf指向的結構中.要求有讀權限.
. IPC_SET:設置semid相關數據結構的如下成員,設置數據從arg.buf指向的結構中讀取:
sem_perm.uid
sem_perm.gid
sem_perm.mode
本命令只能由有效UID等於sem_perm.cuid或sem_perm.uid的進程或有效UID有合適權限的進程操作.
. IPC_RMID:刪除由semid指定的信號量標識符和相關的一組信號量及數據結構.本命令只能由有效UID等於sem_perm.cuid或sem_perm.uid的進程或有效UID有合適權限的進程操作.
返回值:若調用成功,則根據cmd返回以下值:
GETVAL:semval的值.
GETPID:sempid的值.
GETNCNT:semncnt的值.
GETZCNT:semzcnt的值.
其他:0.
若調用失敗則返回-1.
功能:取得一組信號量.
語法:#include
#include
#include
int semget(key,nsems,semflg)
key_t key;
int nsems,semflg;
說明:返回和key相關的信號量標識符.
若以下事實成立,則與信號量標識符,與之相關的semid_ds數據結構及一組nsems信號量將被創建:
. key等於IPC_PRIVATE.
. 系統內還沒有與key相關的信號量,同時(semflg&IPC_CREAT)為真.
創建時新的信號量相關的semid_ds數據結構被初始化如下:
. 在操作權限結構,sem_perm.cuid和sem_perm.uid設置等於調用進程的有效UID.
. 在操作權限結構,sem_perm.cgid和sem_perm.gid設置等於調用進程的有效GID.
. 訪問權限比特位sem_perm.mode設置等於semflg的訪問權限比特位.
. sem_otime設置等於0,sem_ctime設置等於當前系統時間.
返回值:若調用成功,則返回一非0值,稱為信號量標識符;否則返回-1.
功能:信號量操作.
語法:#include
#include
#include
int semop(semid,sops,nsops)
int semid;
struct sembuf *sops;
unsigned nsops;
說明:本系統調用用於執行用戶定義的在一組信號量上操作的行為集合.
該組信號量與semid相關.
參數sops為一個用戶定義的信號量操作結構數組指針.
參數nsops為該數組的元素個數.
數組的每個元素結構包括如下成員:
sem_num; /* 信號量數 */
sem_op; /* 信號量操作 */
sem_flg; /* 操作標志 */
由本系統調用定義的每個信號量操作是針對由semid和sem_num指定的信號量的.變量sem_op指定三種信號量操作的一種:
. 若sem_op為一負數並且調用進程具有修改權限,則下列情況之一將會發生:
* 若semval不小於sem_op的絕對值,則sem_op的絕對值被減去semval的值.若(semflg&SEM_UNDO)為真則sem_op的絕對值加上調用進程指定的信號量的semadj值.
* 若semval小於sem_op的絕對值同時(semflg&IPC_NOWAIT)為真,則本調用立即返回.
* 若semval小於sem_op的絕對值同時(semflg&IPC_NOWAIT)為假,則本系統調用將增加指定信號量相關的semncnt值(加一),將調用進程掛起直到下列條件之一被滿足:
(2).調用進程等待的semid已被系統刪除.
(3).調用進程捕俘到信號,此時,指定信號量的semncnt值減一,調用進程執行中斷服務程序.
. 若sem_op為一正值,同時調用進程具有修改權限,sem_op的值加上semval的值,若(semflg&SEM_UNDO)為真,則sem_op減去調用進程指定信號量的semadj值.
. 若sem_op為0,同時調用進程具有讀權限,下列情況之一將會發生:
* 若semval為0,本系統調用立即返回.
* 若semval不等於0且(semflg&IPC_NOWAIT)為真,本系統調用立即返回.
* 若semval不等於0且(semflg&IPC_NOWAIT)為假,本系統調用將把指定信號量的semzcnt值加一,將調用進程掛起直到下列情況之一發生:
(2).調用進程等待的semid已被系統刪除.
(3).調用進程捕俘到信號,此時,指定信號量的semncnt值減一,調用進程執行中斷服務程序.
返回值:調用成功則返回0,否則返回-1.
例子:本例將包括上述信號量操作的所有系統調用:
#include
#include
#include
#define SEMKEY 75
int semid;
unsigned int count;
* struct sembuf {
* unsigned short sem_num;
* short sem_op;
* short sem_flg;
* }*/
struct sembuf psembuf,vsembuf; /*P和V操作*/
cleanup()
{
semctl(semid,2,IPC_RMID,0);
exit(0);
}
main(argc,argv)
int argc;
char *argv[];
{
int i,first,second;
short initarray[2],outarray[2];
extern cleanup();
if (argc==1) {
for (i=0;i<20;i++)
signal(i,clearup);
semid=semget(SEMKEY,2,0777|IPC_CREAT);
initarray[0]=initarray[1]=1;
semctl(semid,2,SETALL,initarray);
semctl(semid,2,GETALL,outarray);
printf("sem init vals %d%d \n",
outarray[0],outarray[1]);
pause(); /*睡眠到被一軟件中斷信號喚醒*/
}
else if (argv[1][0]=='a') {
first=0;
second=1;
}
else {
first=1;
second=0;
}
semid=semget(SEMKEY,2,0777);
for (count=0;;xcount++) {
semop(semid,&psembuf,1);
semop(semid,&psembuf,1);
printf("proc %d count %d\n",getpid(),count);
semop(semid,&vsembuf,1);
semop(semid,&vsembuf,1);
}
}
24.sdenter()
功能:共享數據段同步訪問,加鎖.
語法:#include
int sdenter(addr,flags)
char *addr;
int flags;
說明:用於指示調用進程即將可以訪問共享數據段中的內容.
參數addr為將一個sdget()調用的有效返回碼.
所執行的動作取決於flags的值:
. SD_NOWAIT:若另一個進程已對指定的段調用本系統調用且還沒有調用sdleave(),並且該段並非用SD_UNLOCK標志創建,則調用進程不是等待該段空閒而是立即返回錯誤碼.
. SD_WRITE:指示調用進程希望向共享數據段寫數據.此時,另一個進程用SD_RDONLY標志聯接該共享數據段則不被允許.
返回值:調用成功則返回0,否則返回-1.
25.sdleave()
功能:共享數據段同步訪問,解鎖.
語法:#include
int sdleave(addr,flags)
char *addr;
說明:用於指示調用進程已完成修改共享數據段中的內容.
返回值:調用成功則返回0,否則返回-1.
26.sdget()
功能:聯接共享數據段到調用進程的數據空間中.
語法:#include
char *sdget(path,flags,size.mode)
char *path;
int flags;
long size;
int mode;
說明:本系統調用將共享數據段聯接到調用進程的數據段中,具體動作由flags的值定義:
. SD_RDONLY:聯接的段為只讀的.
. SD_WRITE:聯接的段為可讀寫的.
. SD_CREAT:若由path命名的段存在且不在使用中,本標志的作用同早先創建一個段相同,否則,該段根據size和mode的值進程創建.對段的讀寫訪問權限的授予基於mode給的權限,功能與一般文件的相同.段被初始化為全0.
. SD_UNLOCK:若用此標志創建該段,則允許有多個進程同時訪問(在讀寫中)該段.
返回值:若調用成功則返回聯接的段地址.否則返回-1.
27.sdfree()
功能:將共享數據段從調用進程的數據空間中斷開聯接.
語法:#include
int sdfree(addr)
char *addr;
說明:本系統調用將共享數據段從調用進程的數據段的指定地址中分離.
若調用進程已完成sdenter()的調用,還未調用sdleave()就調用本系統調用,則sdleave()被自動調用,然後才做本調用的工作.
返回值:若調用成功則返回聯接的段地址.否則返回-1.
28.sdgetv()
功能:同步共享數據訪問.
語法:#include
int sdgetv(addr)
char *addr;
說明:用於同步協調正在使用共享數據段的進程.返回值為共享數據段的版本號.當有進程對該段做sdleave()操作時,版本號會被修改.
返回值:若調用成功,則返回指定共享數據段的版本號,否則返回-1.
29.sdwaitv()
功能:同步共享數據訪問.
語法:#include
int sdwaitv(addr,vnum)
char *addr;
int vnum;
說明:用於同步協調正在使用共享數據段的進程.返回值為共享數據段的版本號.調用進程會睡眠直到指定段的版本號不再等於vnum;
返回值:若調用成功,則返回指定共享數據段的版本號,否則返回-1.
30.sbrk()
功能:修改數據段空間分配.
語法:char *sbrk(incr)
int incr;
說明:用於動態修改調用進程數據段的空間分配.進程將重置進程的分段值並分配一個合適大小的空間.分段值為數據段外第一次分配的地址.要分配的空間的增加量等於分段值的增加量.新分配的空間設置為0.若相同的內存空間重新分配給同一個進程,則空間的內容不確定.
返回值:若成功調用則返回值為0,否則返回-1.
例子:本例將包括上述共享數據空間操作的所有系統調用:
char * area1;
char buf[21];
int v;
area1=sdget("/tmp/area1",SD_WRITE|SD_CREAT,640,0777);
if ((int)area1==-1) {
printf("get share data segment area1 failed\n");
exit(1);
}
v=sdgetv(area1);
*起,否則進入訪問並將該數據段加寫鎖*/
sdenter(area1,SD_WRITE);
strcpy(area1,"aaaaaaaaaa");
sdleave(area1);
sdwaitv(area1,v);
sdenter(area1,SD_WRITE);
memcpy(buf,area1,20);
sdleave(area1);
printf("the data now in area1 is [%s]\n",buf);
31.getenv()
功能:取得指定環境變量值.
語法:#include
#include
char *getenv(name)
char *name;
說明:本系統調用檢查環境字符串(格式如name=value),並在找到有指定名字的環境值後,返回指向value字符串的指針.否則返回空指針.
返回值:如前述.
例子:char * value;
value=getenv("HOME");
printf("HOME = [%s]\n",value);
32.putenv()
功能:修改或增加環境值.
語法:#include
int putenv(string)
char *string;
說明:參數string指向一個字符串,格式如下:
name=value
本系統調用將環境變量name等於值value,修改或增加一個環境變量,字符串string成為環境的一部分.
返回值:若putenv()不能取得合適的內存空間則返回非0值,否則返回0.
例子:/*父進程處理*/
putenv("HOME=/home/abcdef");
putenv("PATH=/bin");
if (fork()>0)
exit(0); /*父進程退出運行*/
setpgrp();
char * value1;
value1=getenv("HOME");
value2=getenv("PATH");
printf("HOME=[%s],PATH=[%s]\n",value1,value2);
三.多進程編程技巧
1.主要程序結構
(1)事件主控方式
若是應用程序屬於事務處理方式,則在主函數中設計為監控事件發生,當事件發生時,可以生成一個新的進程來處理該事務,事務處理完成後就可以讓子進程退出系統.這種處理方式一般不要消息傳遞.
(2)信息協調方式
若是應用程序需要由多個進程協調處理完成,則可以生成這些進程,通過消息在進程間的傳遞,使各個進程能相互協調,共同完成事務.這種處理方式一般是用fork()生成幾個進程後,用exec()調用其它程序文件,使得不同的程序同時在系統內運行.然後通過IPC機制傳送消息,使各個程序能協調運行.
2.選擇主體分叉點
(1)事件初始產生
對應於事件主控方式的程序結構.關鍵點在於以何種方式選擇事件的初始發生點,如網絡程序給出的建鏈信息.主控程序在收到該消息後就認為是一個事件開始,則可以產生一個子進程處理後面的事務:接收交易信息,事務處理,發送返回交易信息,關閉鏈接等,完成後將子進程退出系統.
(2)主程序自主產生
對應於信息協調方式的程序結構.主控程序只負責生成幾個子進程,各個子進程分別調用exec()將不同的執行文件調入內存運行,主控程序在生成所有的子進程後即可退出系統,將子進程留在內存中運行.
3.進程間關系處理
(1)父子進程關系
. 進程組處理
進程組的概念是這樣的,當系統啟動時,第一個進程是init,其進程組號等於進程號,由它產生的所有子進程的進程組號也相同,子進程的子進程也繼承該進程組號,這樣,由init所生成的所有子進程都屬於同一個進程組.但是,同一個進程組的父子進程可能在信號上有相互通訊,若父進程先於子進程退出系統,則子進程會成為一個孤兒進程,可能變成僵死進程.從而使該子進程在其不"願意"的情況下退出運行.為解決這個問題,子進程可以自己組成一個新的進程組,即調用setpgrp()與原進程組脫離關系,產生一個新的進程組,進程組號與它的進程號相同.這樣,父進程退出運行後就不會影響子進程的當前運行.
. 子進程信號處理
但是,單做上述處理還不能解決另一個困難,即子進程在退出運行時,找不到其父進程(父進程已退出,子進程的父進程號改為1).發送子進程退出信號後沒有父進程做出響應處理,該子進程就不可能完全退出運行,可能進入僵死狀態.所以父進程在產生子進程前最好屏蔽子進程返回信號的處理,生成子進程,在父進程退出運行後,子進程返回則其進程返回信號的處理會由系統給出缺省處理,子進程就可以正常退出.
(2)兄弟進程關系
. 交換進程號
對於信息協調方式的程序來說,各兄弟進程間十分需要相互了解進程號,以便於信號處理機制.比較合理的方法是父進程生成一個共享內存的空間,每個子進程都在啟動時在共享內存中設置自己的進程號.這樣,當一個子進程要向另一個子進程發送信號或是因為其他原因需要知道另一個子進程號時,就可以在共享內存中訪問得到所需要的進程號.
4.進程間通訊處理
(1)共享內存需要鎖機制
由於共享內存在設計時沒有處理鎖機制,故當有多個進程在訪問共享內存時就會產生問題.如:一個進程修改一個共享內存單元,另一個進程在讀該共享內存單元時可能有第三個進程立即修改該單元,從而會影響程序的正確性.同時還有分時系統對各進程是分時間片處理的,可能會引起不同的正確性問題.按操作系統的運作方式,則有讀鎖和寫鎖來保證數據的一致性.所以沒有鎖機制的共享內存,必須和信號量一起使用,才能保證共享內存的正確操作.
(2)消息隊列需要關鍵值
消息隊列的操作
在進程取得消息隊列的訪問權限後就必須通過關鍵值來讀消息隊列中的相同關鍵值的消息,寫消息時帶入消息關鍵值.這樣可以通過不同的關鍵值區分不同的交易,使得在同一個消息隊列可以供多種消息同時使用而不沖突.若讀消息隊列使用關鍵值0則讀取消息隊列中第一個消息,不論其關鍵值如何.
(3)信號需要信號處理函數設置和再設置
在用戶進程需要對某個中斷做自己定義的處理時,可以自己定義中斷處理函數,並設置中斷處理函數與該中斷相關聯.這樣,用戶進程在收到該中斷後,即調用用戶定義的函數,處理完成後用戶進程從被中斷處繼續運行(若用戶定義的中斷函數沒有長跳函數或退出運行等會改變運行指令地址的系統調用).在中斷信號被處理後,該中斷的處理函數會恢復成上次缺省處理函數而不是保持用戶定義函數,故在用戶定義的中斷處理函數中一般都再定義該中斷和函數自己的關聯.
(4)IPC的權限設置
在消息隊列,共享內存和信號量的訪問時有用戶訪問權限設置,類同於文件的訪問權限的設置如(777表示rwxrwxrwx),用命令ipcs即可看到在系統中生成的消息隊列,共享內存和信號量的訪問權限.其意義也類似於文件訪問權限.只是執行位無效.
在有名管道和文件方式共享內存中以系統文件的方式定義了用戶的訪問權限.用命令ls -l可以看到它們以系統文件方式存在並具有訪問權限值,並可以看到有名管道的文件類型為p,文件方式共享內存的文件類型為s.
(5)信號中斷對系統調用一級有效
系統在設計系統調用時就考慮了中斷處理問題.當進程運行到一個系統調用時發生了中斷,則進程進入該中斷處理,處理完成後,進程會跳過該系統調用而進入下一條程序指令.
應該注意的是中斷發生在系統調用一級而不是子程序或函數一級.比如一個程序在一個子程序被調用前設置了超時中斷,並在子程序中收到超時中斷,系統在處理完超時中斷後接著處理該子程序被中斷的系統調用之後的指令,而不是從調用該子程序名指令的後一條指令繼續處理.
(6)各種IPC方式的特點
. 消息隊列:
通過消息隊列key值定義和生成消息隊列.
任何進程只要有訪問權限並知道key即可訪問消息隊列.
消息隊列為內存塊方式數據段.
消息隊列中的消息元素長度可為系統參數限制內的任何長度.
消息元素由消息類型分類,其訪問方式為按類型訪問.
在一次讀寫操作前都必須取得消息隊列標識符,即訪問權.訪問後即脫離訪問關系.
消息隊列中的某條消息被讀後即從隊列中刪除.
消息隊列的訪問具備鎖機制處理,即一個進程在訪問時另一個進程不能訪問.
操作時要注意系統資源和效率.
在權限允許時,消息隊列的信息傳遞是雙向的.
. 共享內存
通過共享內存key值定義和生成共享內存.
任何進程只要有訪問權限並知道key即可訪問共享內存.
共享內存為內存塊方式的數據段.
共享內存中的數據長度可為系統參數限制內的任何長度.
共享內存的訪問同數組的訪問方式相同.
在取得共享內存標識符將共享內存與進程數據段聯接後即可開始對之進行讀寫操作,在所有操作完成之後再做共享內存和進程數據段脫離操作,才完成全部共享內存訪問過程.
共享內存中的數據不會因數據被進程讀取後消失.
共享內存的訪問不具備鎖機制處理,即多個進程可能同時訪問同一個共享內存的同一個數據單元.
共享內存的使用最好和信號量一起操作,以具備鎖機制,保證數據的一致.
在權限允許時,共享內存的信息傳遞是雙向的.
. 信號量
用於生成鎖機制,避免發生數據不一致.
沒有其他的數據信息.
不需要有父子關系或兄弟關系.
. 信號
信號由系統進行定義.
信號的發送只要有權限即可進行.
信號是一個事件發生的信息標志,不帶有其它信息.
信號不具備數據塊.
信號的處理可由用戶自己定義.
信號可能由用戶進程,操作系統(軟件或硬件原因)等發出.
有一些信號是不可被屏蔽的.
信號中斷的是系統調用級的函數.
信號的信息傳遞是單向的.
. 管道
做為系統的特殊設備文件,可以是內存方式的,也可以是外存方式的.
管道的傳輸一般是單向的,即一個管道一向,若兩個進程要做雙向傳輸則需要2個管道.管道生成時即有兩端,一端為讀,一端為寫,兩個進程要協調好,一個進程從讀方讀,另一個進程向寫方寫.
管道的讀寫使用流設備的讀寫函數,即:read(),write.
管道的傳輸方式為FIFO,流方式的.不象消息隊列可以按類型讀取.
* 有名管道
一般為系統特殊文件方式,使用的進程之間不一定要有父子關系或兄弟關系.
* 無名管道
一般為內存方式,使用的進程之間一定要有父子關系或兄弟關系.
. 文件
文件是最簡單的進程間通訊方式,使用外部存貯器為中介.
操作麻煩,定位困難.
保密程度低.
容易出現數據不一致問題.
占用硬盤空間.
只要有權限並知道文件名,任何進程都可對之操作.
* 特殊處理
為避免出現保密問題,在打開文件,取得文件描述符後,調用unlink()將硬盤上的文件路徑名刪除,則硬盤上就沒有文件拷貝了.但在進程中該文件描述符是打開的,由該進程生成的子進程中該文件描述符也是打開的,就可以利用系統提供的文件緩沖區做進程間通訊,代價是進程間必須有父子關系或兄弟關系.
. 環境變量
信息的傳送一般是單向的,即由父進程向子進程傳送.
保密性較好.
雙方必須約定環境變量名.
只占用本進程和子進程的環境變量區.
. 共享數據段
操作比較復雜.
占用硬盤空間,生成系統特殊文件.
其他性質與共享內存相類似.
. 流
文件描述符的操作方式.
進程間不一定要有父子關系或兄弟關系.
雙向傳送信息.
進程各自生成socket,用bind()聯接.
其他性質與管道相類似.
流編程為TCP/IP網絡編程范圍,在本文中暫不闡述.
. 傳遞參數
信息的傳送一般是單向的, 即由父進程向子進程傳送.
保密性較差,用進程列表即可顯示出來.
雙方必須約定參數位置.
只占用子進程的參數區.