知識概述
通過pipe在內核中創建一個文件,然后可以實現兩個進程通信
管道是一種最基本的IPC機制,由 pipe 函數創建:
1 #include <unistd.h> 2 int pipe(int filedes[2]);
調用 pipe 函數時在內核中開辟一塊緩沖區(稱為管道)用於通信,它有一個讀端一個寫端,然后通過 filedes 參數傳出給用戶程序兩個文件描述符,
filedes[0] 指向管道的讀端, filedes[1] 指向管道的寫端(很好記,就像0是標准輸入1是標准輸出一樣)。所以管道在用戶程序看起來就像一個打開的文件,
通過 read(filedes[0]); 或者 write(filedes[1]); 向這個文件讀寫數據其實是在讀寫內核緩沖區。 pipe 函數調用成功返回0,調用失敗返回-1。
開辟了管道之后如何實現兩個進程間的通信呢?比如可以按下面的步驟通信。
1. 父進程調用 pipe 開辟管道,得到兩個文件描述符指向管道的兩端。
2. 父進程調用 fork 創建子進程,那么子進程也有兩個文件描述符指向同一管道。
3. 父進程關閉管道讀端,子進程關閉管道寫端。父進程可以往管道里寫,子進程可以從管道里讀,
管道是用環形隊列實現的,數據從寫端流入從讀端流出,這樣就實現了進程間通信。
#include <stdlib.h> #include <unistd.h> #define MAXLINE 80 int main(void) { int n; int fd[2]; pid_t pid; char line[MAXLINE]; if (pipe(fd) < 0) { perror("pipe"); exit(1); } if ((pid = fork()) < 0) { perror("fork"); exit(1); } if (pid > 0) { /* parent */ close(fd[0]); write(fd[1], "hello world\n", 12); wait(NULL); } else { /* child */ close(fd[1]); n = read(fd[0], line, MAXLINE); write(STDOUT_FILENO, line, n); } return 0; }
問題
父進程只用到寫端,因而把讀端關閉,子進程只用到讀端,因而把寫端關閉,然后互相通信,不使用的讀端或寫端必須關閉,請讀者想一想如果不關閉會有什么問題。
思考
1. 如果所有指向管道寫端的文件描述符都關閉了(管道寫端的引用計數等於0),
而仍然有進程從管道的讀端讀數據,那么管道中剩余的數據都被讀取后,
再次 read 會返回0,就像讀到文件末尾一樣。
2. 如果有指向管道寫端的文件描述符沒關閉(管道寫端的引用計數大於0),
而持有管道寫端的進程也沒有向管道中寫數據,這時有進程從管道讀端讀數據,
那么管道中剩余的數據都被讀取后,再次 read 會阻塞,
直到管道中有數據可讀了才讀取數據並返回。
考慮到如下代碼
#include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<string.h> int main(void) { int n; char buff[128]; pid_t pid; int fd[2]; if(pipe(fd)<0) { perror("pipe"); exit(0); } if((pid=fork())<0) { perror("fork"); exit(0); } if(pid>0) { /* parent */ printf("+++++++++++++\n"); close(fd[0]); write(fd[1],"hello world",11); //sleep(5); //write(fd[1],"I am a Student",14); printf("+++++++++++++\n"); } else { printf("--------------\n"); //close(fd[1]); memset(buff,0,128); n = read(fd[0],buff,20); printf("buff=%s\n",buff); memset(buff,0,128); printf("read twice\n"); n = read(fd[0],buff,20); printf("buff=%s\n",buff); printf("--------------\n"); } return 0; }
父進程關閉了讀端口,通過寫端口向pipe中寫入了hello world。然后父進程結束。關閉相關文件(讀寫)描述符
子進程在關閉寫端口的時候,父進程結束時候,寫文件描述符引用計數為0。所以子進程再次讀取后返回0。子進程結束退出。
子進程在不關閉寫端口的時候,父進程結束時候,寫文件描述符引用計數為1(自己的沒關閉)。所以子進程再次讀取時候陷入阻塞狀態。
因為父進程是在SHELL下執行的。所以當父進程結束時候,Shell進程認為命令執行結束了,於是打印Shell提示符,而子進程等待讀取輸入。
父進程已經結束,不會給他輸入數據,而子進程本身只是為了讀取而不是向管道寫數據。所以子進程一直在后台運行,通過ps命令可以查看到子進程信息。
所以,子進程只用到讀端,因而把寫端關閉。防止造成子進程做無用功。。。