首先理解管道其實是一個二進制字節流,它是內核為維持兩個或多個進程互相通信的一種手段(一種IPC)。如下圖所示:
管道是一個字節流,這意味著讀可以從管道中讀取一部分字節流,然後剩下一部分等待下一次讀取,這是允許的。如下測試:
#include<stdio.h> #include <sys/wait.h> #include <string.h> #include <unistd.h> #include <stdlib.h> #define BUF_SIZ 10 int main(int argc,char *argv[]) { int field[2]; pipe(field); char buf[BUF_SIZ]; switch(fork()) { case -1: exit(1); case 0: close(field[1]); for(;;) { int numread=read(field[0],buf,BUF_SIZ); if(numread==-1) exit(1); if(numread==0) break; if(write(1,buf,numread)!=numread) exit(1); sleep(3); } write(1,"\n",1); close(field[0]); default: close(field[0]); write(field[1],argv[1],strlen(argv[1])); close(field[1]); wait(NULL); exit(0); } }此時我將每次讀取的內容設置為10個字節,一次寫入管道中的字節超過10個時,可以在終端看到,10個字節後,一部分內容是在延遲3s才在終端顯示的。值得一提的是,當讀取管道時,如果管道中沒有數據,那麼read會阻塞,直到管道中有新的字節流。如果管道的寫入端關閉了,那麼在read完管道中剩下的字節流之後,將會看到文件結尾(read會返回0),當write進管道時,一次寫入太大的字節流(其實管道可以看做一個內核緩存,緩存的大小是固定的,不同的系統大小不一樣,linux是465536),管道容納不了一次寫這麼多,那麼write也會阻塞。
創建和使用管道:
int pipe(int field[2])即可創建管道,其中field[0]是管道讀取端,field[1]是管道寫入端。一般在一個進程中創建管道也沒有什麼意義。一般管道是用於父進程與其子進程之類的進行通訊。
盡管管道支持多個進程寫入和讀取,但是一般不那麼做。一般設定為父進程寫入,子進程讀取或者子進程寫入,父進程讀取,使得它們之間開始通訊。
注意在創建管道過程中,及時關閉不使用的文件描述符是很有必要的。
從管道中讀取數據的進程(如上圖b中所示的子進程)應該關閉其寫入端的文件描述符(圖44-3 b)中子進程的field[1].因為如果在別的進程都已經寫入數據完成,並且已經關閉了寫入文件描述符,按照原本的思想,該次通訊已經完成,此時已經沒有數據從父進程傳給子進程了。但是由於子進程的寫入管道端文件描述符未關閉,讓子進程讀取端誤認為至少還有一個進程沒有完成寫入,那麼子進程的讀取端會一直阻塞地read下去。而寫入端關閉讀取文件操作符是處於不同的原因。當一個進程試圖向管道中寫入數據但沒有任何進程擁有該管道打開的讀描述符,內核會向寫入進程發送一個SIGPIPE信號。默認下,該信號會殺死一個進程。當然,進程也可以捕捉或者忽略該信號。收到SIGPIPE信號對於表示管道的狀態很有用。
關閉未使用文件描述符的最後一個原因是當所有進程的所有引用一個管道的文件描述符被關閉後,才會銷毀該管道,釋放管道資源。
還有另外一種管道,它叫FIFO管道,但是它在文件系統中有一個名稱,打開它和打開一個普通文件是一樣的。不同的是管道只能用於有“血緣”關系的進程間通訊,FIFO可以對在任意進程之間通訊(如客戶端和服務器)。
文章參考Linux/Unix系統編程手冊一書。