管道是一種最基本的IPC機制,做用於有血緣關係的進程之間,完成數據傳遞。調用pipe系統函數便可建立一個管道。有以下特質:函數
1. 其本質是一個僞文件(實爲內核緩衝區)spa
2. 由兩個文件描述符引用,一個表示讀端,一個表示寫端。code
3. 規定數據從管道的寫端流入管道,從讀端流出。blog
管道的原理: 管道實爲內核使用環形隊列機制,藉助內核緩衝區(4k)實現。隊列
管道的侷限性:進程
① 數據本身讀不能本身寫。ip
② 數據一旦被讀走,便不在管道中存在,不可反覆讀取。字符串
③ 因爲管道採用半雙工通訊方式。所以,數據只能在一個方向上流動。string
④ 只能在有公共祖先的進程間使用管道。it
常見的通訊方式有,單工通訊、半雙工通訊、全雙工通訊。
建立管道
int pipe(int pipefd[2]); 成功:0;失敗:-1,設置errno
函數調用成功返回r/w兩個文件描述符。無需open,但需手動close。規定:fd[0] → r; fd[1] → w,就像0對應標準輸入,1對應標準輸出同樣。向管道文件讀寫數據實際上是在讀寫內核緩衝區。
管道建立成功之後,建立該管道的進程(父進程)同時掌握着管道的讀端和寫端。如何實現父子進程間通訊呢?一般能夠採用以下步驟:
1. 父進程調用pipe函數建立管道,獲得兩個文件描述符fd[0]、fd[1]指向管道的讀端和寫端。
2. 父進程調用fork建立子進程,那麼子進程也有兩個文件描述符指向同一管道。
3. 父進程關閉管道讀端,子進程關閉管道寫端。父進程能夠向管道中寫入數據,子進程將管道中的數據讀出。因爲管道是利用環形隊列實現的,數據從寫端流入管道,從讀端流出,這樣就實現了進程間通訊。
練習:父子進程使用管道通訊,父寫入字符串,子進程讀出並,打印到屏幕。 【pipe.c】
思考:爲甚麼,程序中沒有使用sleep函數,但依然能保證子進程運行時必定會讀到數據呢?
#include <unistd.h> #include <string.h> #include <stdlib.h> #include <stdio.h> #include <sys/wait.h> void sys_err(const char *str) { perror(str); exit(1); } int main(void) { pid_t pid; char buf[1024]; int fd[2]; char *p = "test for pipe\n"; if (pipe(fd) == -1) sys_err("pipe"); pid = fork(); if (pid < 0) { sys_err("fork err"); } else if (pid == 0) { close(fd[1]); int len = read(fd[0], buf, sizeof(buf)); write(STDOUT_FILENO, buf, len); close(fd[0]); } else { close(fd[0]); write(fd[1], p, strlen(p)); wait(NULL); close(fd[1]); } return 0; }
使用管道須要注意如下4種特殊狀況(假設都是阻塞I/O操做,沒有設置O_NONBLOCK標誌):
1. 若是全部指向管道寫端的文件描述符都關閉了(管道寫端引用計數爲0),而仍然有進程從管道的讀端讀數據,那麼管道中剩餘的數據都被讀取後,再次read會返回0,就像讀到文件末尾同樣。
2. 若是有指向管道寫端的文件描述符沒關閉(管道寫端引用計數大於0),而持有管道寫端的進程也沒有向管道中寫數據,這時有進程從管道讀端讀數據,那麼管道中剩餘的數據都被讀取後,再次read會阻塞,直到管道中有數據可讀了纔讀取數據並返回。
3. 若是全部指向管道讀端的文件描述符都關閉了(管道讀端引用計數爲0),這時有進程向管道的寫端write,那麼該進程會收到信號SIGPIPE,一般會致使進程異常終止。固然也能夠對SIGPIPE信號實施捕捉,不終止進程。具體方法信號章節詳細介紹。
4. 若是有指向管道讀端的文件描述符沒關閉(管道讀端引用計數大於0),而持有管道讀端的進程也沒有從管道中讀數據,這時有進程向管道寫端寫數據,那麼在管道被寫滿時再次write會阻塞,直到管道中有空位置了才寫入數據並返回。
總結:
① 讀管道: 1. 管道中有數據,read返回實際讀到的字節數。
2. 管道中無數據:
(1) 管道寫端被所有關閉,read返回0 (好像讀到文件結尾)
(2) 寫端沒有所有被關閉,read阻塞等待(不久的未來可能有數據遞達,此時會讓出cpu)
② 寫管道: 1. 管道讀端所有被關閉, 進程異常終止(也可以使用捕捉SIGPIPE信號,使進程不終止)
2. 管道讀端沒有所有關閉:
(1) 管道已滿,write阻塞。
(2) 管道未滿,write將數據寫入,並返回實際寫入的字節數。
#include<stdio.h> #include<string.h> #include<stdlib.h> #include<unistd.h> #include<sys/types.h> #include<sys/wait.h> int main(int argc ,char *argv[]){ char buf[4]; int fd[2]; if(pipe(fd)==-1){ printf("pipe failed\n"); } int rc1=fork(); if(rc1==0){ close(fd[0]); write(fd[1],"1234",4); printf("rc1 write ok\n"); exit(1); }else{ int rc2=fork(); if(rc2==0){ close(fd[1]); read(fd[0],buf,4); int i=0; for(i;i<4;i++){ printf("rc2 : buf[%d]=%c\n",i,buf[i]); } exit(1); } } return 0;} [root@localhost codec5]# rc1 write ok rc2 : buf[0]=1 rc2 : buf[1]=2 rc2 : buf[2]=3 rc2 : buf[3]=4