進程間通訊:IPC概念html
IPC:Interprocess Communication,經過內核提供的緩衝區進行數據交換的機制。c++
IPC通訊的方式:shell
通訊種類:bash
pipe通訊是單雙工的。微信
#include <unistd.h> int pipe(int pipefd[2]);
例子:socket
#include <unistd.h> #include <sys/types.h> #include <stdio.h> int main(){ int fds[2]; pipe(fds); pid_t pid = fork(); if(pid == 0){ write(fds[1], "hello\n", 6); char buf[10] = {0}; int ret = read(fds[0], buf, sizeof buf); if(ret > 0){ printf("%s", buf); } } if(pid > 0){ char buf[10] = {0}; int ret = read(fds[0], buf, sizeof buf); if(ret > 0){ printf("%s", buf); } write(fds[1], "world\n", 6); sleep(1); } }
例子1:子進程寫,父進程讀。函數
#include <unistd.h> #include <sys/types.h> #include <stdio.h> int main(){ int fds[2]; pipe(fds); pid_t pid = fork(); if(pid == 0){ write(fds[1], "hello\n", 6); /* char buf[10] = {0}; int ret = read(fds[0], buf, sizeof buf); if(ret > 0){ printf("%s", buf); } */ } if(pid > 0){ char buf[10] = {0}; int ret = read(fds[0], buf, sizeof buf); if(ret > 0){ printf("%s", buf); } //write(fds[1], "world\n", 6); //sleep(1); } }
例子2:用管道實現【ps aux | grep bash】命令。學習
實現辦法,用dup2函數把標準輸出,重定向到寫端;再把標準輸入重定向到讀端。code
#include <unistd.h> #include <sys/types.h> #include <stdio.h> int main(){ int fds[2]; pipe(fds); pid_t pid = fork(); int stdoutfd = dup(STDOUT_FILENO); if(pid == 0){ //close(fds[0]);//------------① dup2(fds[1], STDOUT_FILENO); execlp("ps", "ps", "aux", NULL); } if(pid > 0){ //close(fds[1]);//----------② dup2(fds[0], STDIN_FILENO); execlp("grep", "grep", "bash", NULL); dup2(stdoutfd, STDOUT_FILENO); } }
運行結果:發現程序沒有結束,阻塞住了,必須按ctol-c才能結束。htm
ys@ys:~/test$ ./pi2 ys 1551 0.0 0.2 29692 5548 pts/0 Ss 10:05 0:00 bash ys 2316 0.0 0.2 29560 5328 pts/1 Ss+ 11:33 0:00 bash ys 2486 0.0 0.0 21536 1060 pts/0 S+ 11:56 0:00 grep bash
用【ps aux】調查一下,發現,因爲父進程【grep bash】沒有結束尚未回收子進程,致使【ps】變成了殭屍進程。
ys 2437 0.0 0.0 21536 1088 pts/0 S+ 11:50 0:00 grep bash ys 2438 0.1 0.0 0 0 pts/0 Z+ 11:50 0:00 [ps] <defunct> ys 2439 0.0 0.1 44472 3800 pts/1 R+ 11:50 0:00 ps aux
爲何父進程【grep bash】沒有結束呢?確實在子進程裏給父進程【ps aux】的輸出結果了啊!
這是grep命令自己的緣故,在終端執行【grep bash】的話,就變成了阻塞狀態,grep在等待標準輸入,若是輸入了【bash】grep就會給出結果,可是仍是在繼續等待標準輸入,因此這就是父進程沒有結束,阻塞在【grep bash】那裏的緣由。
解決辦法:告訴【grep】,管道的寫端不會再寫入數據了後,grep就不會再繼續等待,因此grep就會結束。grep的結束了,父進程也就結束了,因此殭屍進程也就自動消失了。
須要改代碼的地方是②處,加上【close(fds[1]);】,就告訴了grep,已經沒有寫入了,因此grep就不會阻塞,父進程就可以結束掉。
注意:其實應該在子進程裏也應該加上【close(fds[1]);】,才能達到寫端所有關閉了,爲何沒寫也沒錯誤呢,由於子進程先執行結束了,進程結束後,系統會自動把進程中打開的文件描述符所有關閉,因此沒在子進程裏寫關閉寫端的代碼,也沒出問題。
管道有以下的規則:
例子1:寫端所有關閉:read函數返回0。
在①和②兩處必須都關閉寫端,read函數才能返回0.
#include <unistd.h> #include <sys/types.h> #include <stdio.h> #include <sys/wait.h> int main(){ int fds[2]; pipe(fds); pid_t pid = fork(); if(pid == 0){ char buf[10] = {0}; int ret = read(fds[0], buf, sizeof buf); if(ret > 0){ printf("%s", buf); } close(fds[1]);//----① sleep(1); if(read(fds[0],buf, sizeof buf) == 0){ printf("all closed\n"); } } if(pid > 0){ int ret = write(fds[1], "hello\n", 6); close(fds[1]);//------② wait(NULL); } }
例子2:讀端所有關閉:write函數會產生SIGPIPE信號,程序異常結束。
在①和②兩處必須都關閉讀端,write函數會產生SIGPIPE信號。
#include <unistd.h> #include <sys/types.h> #include <stdio.h> #include <sys/wait.h> int main(){ int fds[2]; pipe(fds); pid_t pid = fork(); if(pid == 0){ close(fds[0]);//------② int ret = write(fds[1], "hello\n", 6); } if(pid > 0){ close(fds[0]);//----① //close(fds[1]); int status; wait(&status); if(WIFSIGNALED(status)){ printf("killed by %d\n", WTERMSIG(status)); } } }
執行結果:【killed by 13】。13是SIGPIPE
查看系統默認的管道緩衝區的大小:ulimit -a
pipe size (512 bytes, -p) 8
查看系統默認的管道緩衝區的大小的函數:fpathconf
#include <unistd.h> long fpathconf(int fd, int name);
例子:
#include <unistd.h> #include <stdio.h> int main(){ int fds[2]; pipe(fds); long ret = fpathconf(fds[0], _PC_PIPE_BUF); printf("size:%ld\n", ret); }
執行結果:size:4096
上面的【例子:用管道實現【ps aux | grep bash】命令】有個問題,父進程直接調用了exec函數,致使沒法在父進程中回收子進程的資源。下面的例子就去解決這個問題,方法是,不在父進程裏調用exec函數,在2個兄弟子進程裏分別調用exec函數,而後在父進程裏回收資源。
#include <unistd.h> #include <stdio.h> #include <sys/types.h> #include <sys/wait.h> int main(){ int fds[2]; pipe(fds); pid_t pid = fork(); if(pid == 0){ pid_t pid1 = fork(); if(pid1 == 0){ dup2(fds[1], STDOUT_FILENO); execlp("ps", "ps", "aux", NULL); } else if(pid1 > 0){ close(fds[1]);//----① dup2(fds[0], STDIN_FILENO); execlp("grep", "grep", "bash", NULL); //dup2(stdoutfd, STDOUT_FILENO); } } else if(pid > 0){ close(fds[1]);//----② wait(NULL); } }
注意在①和②處的關閉代碼。
到此爲止,能夠看出來管道的
建立FIFO僞文件的命令:【mkfifo】
prw-r--r-- 1 ys ys 0 4月 29 15:59 myfifo
文件類型爲P,大小爲0。
也能夠用函數:mkfifo建立
#include <sys/types.h> #include <sys/stat.h> int mkfifo(const char *pathname, mode_t mode);
FIFO通訊原理:內核對fifo文件開闢一個緩衝區,操做fifo僞文件,就至關於操做緩衝區,實現裏進程間的通訊。實際上就是文件讀寫。
FIFO例子:傳進一個事先用mkfifo 建立好的FIFO文件。能夠同時打開多個讀端和寫端。
寫端:
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <unistd.h> int main(int argc, char* argv[]){ printf("begin write\n"); int fd = open(argv[1], O_WRONLY); printf("end write\n"); int num = 0; char buf[20] = {0}; while(1){ sprintf(buf, "num=%04d\n", num++); write(fd, buf, strlen(buf)); sleep(1); } close(fd); }
讀端:
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <unistd.h> #include <string.h> int main(int argc, char* argv[]){ printf("begin read\n"); int fd = open(argv[1], O_RDONLY); printf("end read\n"); int num = 0; char buf[20] = {0}; while(1){ memset(buf, 0x00, sizeof buf); int ret = read(fd, buf, sizeof buf); if(ret > 0){ printf("%s\n", buf); } else if(ret == 0){ break; } sleep(1); } close(fd); }
例子裏有兩個注意點:
open的時候是阻塞的,只有當讀端和寫端都打開後,open函數纔會返回。非FIFO文件的open函數不是阻塞的。
FIFOs Opening the read or write end of a FIFO blocks until the other end is also opened (by another process or thread). See fifo(7) for further details.
很是重要的一點:從fifo裏讀出數據後,這個被讀出來的數據在fifo裏就消失了。後面講的mmap進程間通訊就不同,讀完了,再讀還有,由於是映射到內存了。
A進程發送一個mp3文件,B進程接收這個mp3文件,並存儲到磁盤上,代碼以下:
發送端:先取得mp3文件的大小,把文件的大小先發給接收端,而後在把文件的內容發過去。
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <string.h> int main(int argc, char* argv[]){ struct stat sbuf; int ret = stat("02.mp3", &sbuf); if(ret < 0){ perror("stat"); return -1; } //get file size int sz = sbuf.st_size; printf("size:%d\n", sz); char buf[20] = {0}; sprintf(buf, "%d", sz); //open fifo file int fd = open(argv[1], O_RDWR); //send file size write(fd, buf, sizeof(buf)); //open src mp3 file int src = open("02.mp3", O_RDONLY); char srcBuf[1024] = {0}; //send file content to dec file int sent = 0; while((sent = read(src, srcBuf, sizeof(srcBuf))) > 0){ write(fd, srcBuf, sent); memset(srcBuf, 0x00, sizeof(srcBuf)); } close(fd); close(src); }
接收端:先從發送端取得要發過來的MP3文件的大小,而後根據這個大小,先建立一個空的文件,而後再向這個空的文件裏寫內容。
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdlib.h> #include <string.h> int main(int argc, char* argv[]){ //open fifo file int fd = open(argv[1], O_RDONLY); //send file size char buf[20] = {0}; //get file size read(fd, buf, sizeof(buf)); int sz = atoi(buf); printf("sz:%d\n", sz); int dsc = open("des.mp3", O_RDWR|O_CREAT|O_TRUNC, 0666); int ret = ftruncate(dsc, sz); if(ret < 0){ perror("ftruncate"); return -1; } char srcBuf[1024] = {0}; //recv file content from src file int sent = 0; while((sent = read(fd, srcBuf, sizeof(srcBuf))) > 0){ write(dsc, srcBuf, sent); memset(srcBuf, 0x00, sizeof(srcBuf)); } close(fd); close(dsc); }