linux下進程間通訊的幾種主要手段:html
本文講述進程間通訊方法——管道及命名管道linux
管道是Linux支持的最初Unix IPC形式之一,具備如下特色:網絡
#include <unistd.h> int pipe(int fd[2]) |
該函數建立的管道的兩端處於一個進程中間,在實際應用中沒有太大意義,所以,一個進程在由pipe()建立管道後,通常再fork一個子進程,而後經過管道實現父子進程間的通訊(所以也不難推出,只要兩個進程中存在親緣關係,這裏的親緣關係指的是具備共同的祖先,均可以採用管道方式來進行通訊)。測試
管道兩端可分別用描述字fd[0]以及fd[1]來描述,須要注意的是,管道的兩端是固定了任務的。即一端只能用於讀,由描述字fd[0]表示,稱其爲管道讀端;另外一端則只能用於寫,由描述字fd[1]來表示,稱其爲管道寫端。若是試圖從管道寫端讀取數據,或者向管道讀端寫入數據都將致使錯誤發生。通常文件的I/O函數均可以用於管道,如close、read、write等等。命令行
從管道中讀取數據:線程
關於管道的讀規則驗證:
/************** * readtest.c * **************/ #include <unistd.h> #include <sys/types.h> #include <errno.h> main() { int pipe_fd[2]; pid_t pid; char r_buf[100]; char w_buf[4]; char* p_wbuf; int r_num; int cmd; memset(r_buf,0,sizeof(r_buf)); memset(w_buf,0,sizeof(r_buf)); p_wbuf=w_buf; if(pipe(pipe_fd)<0) { printf("pipe create error\n"); return -1; } if((pid=fork())==0) { printf("\n"); close(pipe_fd[1]); sleep(3);//確保父進程關閉寫端 r_num=read(pipe_fd[0],r_buf,100); printf( "read num is %d the data read from the pipe is %d\n",r_num,atoi(r_buf)); close(pipe_fd[0]); exit(); } else if(pid>0) { close(pipe_fd[0]);//read strcpy(w_buf,"111"); if(write(pipe_fd[1],w_buf,4)!=-1) printf("parent write over\n"); close(pipe_fd[1]);//write printf("parent close fd[1] over\n"); sleep(10); } } /************************************************** * 程序輸出結果: * parent write over * parent close fd[1] over * read num is 4 the data read from the pipe is 111 * 附加結論: * 管道寫端關閉後,寫入的數據將一直存在,直到讀出爲止. ****************************************************/ |
向管道中寫入數據:
對管道的寫規則的驗證1:寫端對讀端存在的依賴性
#include <unistd.h> #include <sys/types.h> main() { int pipe_fd[2]; pid_t pid; char r_buf[4]; char* w_buf; int writenum; int cmd; memset(r_buf,0,sizeof(r_buf)); if(pipe(pipe_fd)<0) { printf("pipe create error\n"); return -1; } if((pid=fork())==0) { close(pipe_fd[0]); close(pipe_fd[1]); sleep(10); exit(); } else if(pid>0) { sleep(1); //等待子進程完成關閉讀端的操做 close(pipe_fd[0]);//write w_buf="111"; if((writenum=write(pipe_fd[1],w_buf,4))==-1) printf("write to pipe error\n"); else printf("the bytes write to pipe is %d \n", writenum); close(pipe_fd[1]); } } |
則輸出結果爲: Broken pipe,緣由就是該管道以及它的全部fork()產物的讀端都已經被關閉。若是在父進程中保留讀端,即在寫完pipe後,再關閉父進程的讀端,也會正常寫入pipe,讀者可本身驗證一下該結論。所以,在向管道寫入數據時,至少應該存在某一個進程,其中管道讀端沒有被關閉,不然就會出現上述錯誤(管道斷裂,進程收到了SIGPIPE信號,默認動做是進程終止)
對管道的寫規則的驗證2:linux不保證寫管道的原子性驗證
#include <unistd.h> #include <sys/types.h> #include <errno.h> main(int argc,char**argv) { int pipe_fd[2]; pid_t pid; char r_buf[4096]; char w_buf[4096*2]; int writenum; int rnum; memset(r_buf,0,sizeof(r_buf)); if(pipe(pipe_fd)<0) { printf("pipe create error\n"); return -1; } if((pid=fork())==0) { close(pipe_fd[1]); while(1) { sleep(1); rnum=read(pipe_fd[0],r_buf,1000); printf("child: readnum is %d\n",rnum); } close(pipe_fd[0]); exit(); } else if(pid>0) { close(pipe_fd[0]);//write memset(r_buf,0,sizeof(r_buf)); if((writenum=write(pipe_fd[1],w_buf,1024))==-1) printf("write to pipe error\n"); else printf("the bytes write to pipe is %d \n", writenum); writenum=write(pipe_fd[1],w_buf,4096); close(pipe_fd[1]); } } 輸出結果: the bytes write to pipe 1000 the bytes write to pipe 1000 //注意,此行輸出說明了寫入的非原子性 the bytes write to pipe 1000 the bytes write to pipe 1000 the bytes write to pipe 1000 the bytes write to pipe 120 //注意,此行輸出說明了寫入的非原子性 the bytes write to pipe 0 the bytes write to pipe 0 ...... |
結論:
寫入數目小於4096時寫入是非原子的!
若是把父進程中的兩次寫入字節數都改成5000,則很容易得出下面結論:
寫入管道的數據量大於4096字節時,緩衝區的空閒空間將被寫入數據(補齊),直到寫完全部數據爲止,若是沒有進程讀數據,則一直阻塞。
實例一:用於shell
管道可用於輸入輸出重定向,它將一個命令的輸出直接定向到另外一個命令的輸入。好比,當在某個shell程序(Bourne shell或C shell等)鍵入who│wc -l後,相應shell程序將建立who以及wc兩個進程和這兩個進程間的管道。考慮下面的命令行:
$kill -l 運行結果見 附一。
$kill -l | grep SIGRTMIN 運行結果以下:
30) SIGPWR 31) SIGSYS 32) SIGRTMIN 33) SIGRTMIN+1 34) SIGRTMIN+2 35) SIGRTMIN+3 36) SIGRTMIN+4 37) SIGRTMIN+5 38) SIGRTMIN+6 39) SIGRTMIN+7 40) SIGRTMIN+8 41) SIGRTMIN+9 42) SIGRTMIN+10 43) SIGRTMIN+11 44) SIGRTMIN+12 45) SIGRTMIN+13 46) SIGRTMIN+14 47) SIGRTMIN+15 48) SIGRTMAX-15 49) SIGRTMAX-14 |
實例二:用於具備親緣關係的進程間通訊
下面例子給出了管道的具體應用,父進程經過管道發送一些命令給子進程,子進程解析命令,並根據命令做相應處理。
#include <unistd.h> #include <sys/types.h> main() { int pipe_fd[2]; pid_t pid; char r_buf[4]; char** w_buf[256]; int childexit=0; int i; int cmd; memset(r_buf,0,sizeof(r_buf)); if(pipe(pipe_fd)<0) { printf("pipe create error\n"); return -1; } if((pid=fork())==0) //子進程:解析從管道中獲取的命令,並做相應的處理 { printf("\n"); close(pipe_fd[1]); sleep(2); while(!childexit) { read(pipe_fd[0],r_buf,4); cmd=atoi(r_buf); if(cmd==0) { printf("child: receive command from parent over\n now child process exit\n"); childexit=1; } else if(handle_cmd(cmd)!=0) return; sleep(1); } close(pipe_fd[0]); exit(); } else if(pid>0) //parent: send commands to child { close(pipe_fd[0]); w_buf[0]="003"; w_buf[1]="005"; w_buf[2]="777"; w_buf[3]="000"; for(i=0;i<4;i++) write(pipe_fd[1],w_buf[i],4); close(pipe_fd[1]); } } //下面是子進程的命令處理函數(特定於應用): int handle_cmd(int cmd) { if((cmd<0)||(cmd>256)) //suppose child only support 256 commands { printf("child: invalid command \n"); return -1; } printf("child: the cmd from parent is %d\n", cmd); return 0; } |
管道的主要侷限性正體如今它的特色上:
管道應用的一個重大限制是它沒有名字,所以,只能用於具備親緣關係的進程間通訊,在有名管道(named pipe或FIFO)提出後,該限制獲得了克服。FIFO不一樣於管道之處在於它提供一個路徑名與之關聯,以FIFO的文件形式存在於文件系統中。這樣,即便與FIFO的建立進程不存在親緣關係的進程,只要能夠訪問該路徑,就可以彼此經過FIFO相互通訊(可以訪問該路徑的進程以及FIFO的建立進程之間),所以,經過FIFO不相關的進程也能交換數據。值得注意的是,FIFO嚴格遵循先進先出(first in first out),對管道及FIFO的讀老是從開始處返回數據,對它們的寫則把數據添加到末尾。它們不支持諸如lseek()等文件定位操做。
#include <sys/types.h> #include <sys/stat.h> int mkfifo(const char * pathname, mode_t mode) |
該函數的第一個參數是一個普通的路徑名,也就是建立後FIFO的名字。第二個參數與打開普通文件的open()函數中的mode 參數相同。 若是mkfifo的第一個參數是一個已經存在的路徑名時,會返回EEXIST錯誤,因此通常典型的調用代碼首先會檢查是否返回該錯誤,若是確實返回該錯誤,那麼只要調用打開FIFO的函數就能夠了。通常文件的I/O函數均可以用於FIFO,如close、read、write等等。
有名管道比管道多了一個打開操做:open。
FIFO的打開規則:
若是當前打開操做是爲讀而打開FIFO時,若已經有相應進程爲寫而打開該FIFO,則當前打開操做將成功返回;不然,可能阻塞直到有相應進程爲寫而打開該FIFO(當前打開操做設置了阻塞標誌);或者,成功返回(當前打開操做沒有設置阻塞標誌)。
若是當前打開操做是爲寫而打開FIFO時,若是已經有相應進程爲讀而打開該FIFO,則當前打開操做將成功返回;不然,可能阻塞直到有相應進程爲讀而打開該FIFO(當前打開操做設置了阻塞標誌);或者,返回ENXIO錯誤(當前打開操做沒有設置阻塞標誌)。
對打開規則的驗證參見 附2。
從FIFO中讀取數據:
約定:若是一個進程爲了從FIFO中讀取數據而阻塞打開FIFO,那麼稱該進程內的讀操做爲設置了阻塞標誌的讀操做。
注:若是FIFO中有數據,則設置了阻塞標誌的讀操做不會由於FIFO中的字節數小於請求讀的字節數而阻塞,此時,讀操做會返回FIFO中現有的數據量。
向FIFO中寫入數據:
約定:若是一個進程爲了向FIFO中寫入數據而阻塞打開FIFO,那麼稱該進程內的寫操做爲設置了阻塞標誌的寫操做。
對於設置了阻塞標誌的寫操做:
對於沒有設置阻塞標誌的寫操做:
對FIFO讀寫規則的驗證:
下面提供了兩個對FIFO的讀寫程序,適當調節程序中的不多地方或者程序的命令行參數就能夠對各類FIFO讀寫規則進行驗證。
#include <sys/types.h> #include <sys/stat.h> #include <errno.h> #include <fcntl.h> #define FIFO_SERVER "/tmp/fifoserver" main(int argc,char** argv) //參數爲即將寫入的字節數 { int fd; char w_buf[4096*2]; int real_wnum; memset(w_buf,0,4096*2); if((mkfifo(FIFO_SERVER,O_CREAT|O_EXCL)<0)&&(errno!=EEXIST)) printf("cannot create fifoserver\n"); if(fd==-1) if(errno==ENXIO) printf("open error; no reading process\n"); fd=open(FIFO_SERVER,O_WRONLY|O_NONBLOCK,0); //設置非阻塞標誌 //fd=open(FIFO_SERVER,O_WRONLY,0); //設置阻塞標誌 real_wnum=write(fd,w_buf,2048); if(real_wnum==-1) { if(errno==EAGAIN) printf("write to fifo error; try later\n"); } else printf("real write num is %d\n",real_wnum); real_wnum=write(fd,w_buf,5000); //5000用於測試寫入字節大於4096時的非原子性 //real_wnum=write(fd,w_buf,4096); //4096用於測試寫入字節不大於4096時的原子性 if(real_wnum==-1) if(errno==EAGAIN) printf("try later\n"); } |
程序2:與程序1一塊兒測試寫FIFO的規則,第一個命令行參數是請求從FIFO讀出的字節數
#include <sys/types.h> #include <sys/stat.h> #include <errno.h> #include <fcntl.h> #define FIFO_SERVER "/tmp/fifoserver" main(int argc,char** argv) { char r_buf[4096*2]; int fd; int r_size; int ret_size; r_size=atoi(argv[1]); printf("requred real read bytes %d\n",r_size); memset(r_buf,0,sizeof(r_buf)); fd=open(FIFO_SERVER,O_RDONLY|O_NONBLOCK,0); //fd=open(FIFO_SERVER,O_RDONLY,0); //在此處能夠把讀程序編譯成兩個不一樣版本:阻塞版本及非阻塞版本 if(fd==-1) { printf("open %s for read error\n"); exit(); } while(1) { memset(r_buf,0,sizeof(r_buf)); ret_size=read(fd,r_buf,r_size); if(ret_size==-1) if(errno==EAGAIN) printf("no data avlaible\n"); printf("real read bytes %d\n",ret_size); sleep(1); } pause(); unlink(FIFO_SERVER); } |
程序應用說明:
把讀程序編譯成兩個不一樣版本:
把寫程序編譯成兩個四個版本:
下面將使用br、nbr、w代替相應程序中的阻塞讀、非阻塞讀
驗證阻塞寫操做:
驗證非阻塞寫操做:
無論寫打開的阻塞標誌是否設置,在請求寫入的字節數大於4096時,都不保證寫入的原子性。但兩者有本質區別:
對於阻塞寫來講,寫操做在寫滿FIFO的空閒區域後,會一直等待,直到寫完全部數據爲止,請求寫入的數據最終都會寫入FIFO;
而非阻塞寫則在寫滿FIFO的空閒區域後,就返回(實際寫入的字節數),因此有些數據最終不可以寫入。
對於讀操做的驗證則比較簡單,再也不討論。
在驗證了相應的讀寫規則後,應用實例彷佛就沒有必要了。
管道經常使用於兩個方面:(1)在shell中時常會用到管道(做爲輸入輸入的重定向),在這種應用方式下,管道的建立對於用戶來講是透明的;(2)用於具備親緣關係的進程間通訊,用戶本身建立管道,並完成讀寫操做。
FIFO能夠說是管道的推廣,克服了管道無名字的限制,使得無親緣關係的進程一樣能夠採用先進先出的通訊機制進行通訊。
管道和FIFO的數據是字節流,應用程序之間必須事先肯定特定的傳輸"協議",採用傳播具備特定意義的消息。
要靈活應用管道及FIFO,理解它們的讀寫規則是關鍵。
附1:kill -l 的運行結果,顯示了當前系統支持的全部信號:
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR 31) SIGSYS 32) SIGRTMIN 33) SIGRTMIN+1 34) SIGRTMIN+2 35) SIGRTMIN+3 36) SIGRTMIN+4 37) SIGRTMIN+5 38) SIGRTMIN+6 39) SIGRTMIN+7 40) SIGRTMIN+8 41) SIGRTMIN+9 42) SIGRTMIN+10 43) SIGRTMIN+11 44) SIGRTMIN+12 45) SIGRTMIN+13 46) SIGRTMIN+14 47) SIGRTMIN+15 48) SIGRTMAX-15 49) SIGRTMAX-14 50) SIGRTMAX-13 51) SIGRTMAX-12 52) SIGRTMAX-11 53) SIGRTMAX-10 54) SIGRTMAX-9 55) SIGRTMAX-8 56) SIGRTMAX-7 57) SIGRTMAX-6 58) SIGRTMAX-5 59) SIGRTMAX-4 60) SIGRTMAX-3 61) SIGRTMAX-2 62) SIGRTMAX-1 63) SIGRTMAX |
除了在此處用來講明管道應用外,接下來的專題還要對這些信號分類討論。
附2:對FIFO打開規則的驗證(主要驗證寫打開對讀打開的依賴性)
#include <sys/types.h> #include <sys/stat.h> #include <errno.h> #include <fcntl.h> #define FIFO_SERVER "/tmp/fifoserver" int handle_client(char*); main(int argc,char** argv) { int r_rd; int w_fd; pid_t pid; if((mkfifo(FIFO_SERVER,O_CREAT|O_EXCL)<0)&&(errno!=EEXIST)) printf("cannot create fifoserver\n"); handle_client(FIFO_SERVER); } int handle_client(char* arg) { int ret; ret=w_open(arg); switch(ret) { case 0: { printf("open %s error\n",arg); printf("no process has the fifo open for reading\n"); return -1; } case -1: { printf("something wrong with open the fifo except for ENXIO"); return -1; } case 1: { printf("open server ok\n"); return 1; } default: { printf("w_no_r return ----\n"); return 0; } } unlink(FIFO_SERVER); } int w_open(char*arg) //0 open error for no reading //-1 open error for other reasons //1 open ok { if(open(arg,O_WRONLY|O_NONBLOCK,0)==-1) { if(errno==ENXIO) { return 0; } else return -1; } return 1; } |