從概念上講,管道(pipe)是兩個進(jìn)程之間的連接,它使得一個進(jìn)程的標(biāo)準(zhǔn)輸出成為另一個進(jìn)程的標(biāo)準(zhǔn)輸入。在UNIX操作系統(tǒng)中,管道對于進(jìn)程之間的通信非常有用。
管道僅是單向通信,一個進(jìn)程向管道寫入,另一個進(jìn)程則從管道中讀取。這是一個被視為“虛擬文件”的主內(nèi)存區(qū)域。
創(chuàng)建的進(jìn)程及其所有子進(jìn)程都可以使用管道進(jìn)行讀寫。一個進(jìn)程可以寫入這個管道,而另一個相關(guān)的進(jìn)程可以從中讀取。
如果管道為空,而且管道的輸入口至少有一個進(jìn)程打開著,那么一個進(jìn)程試圖讀取管道的時將會被掛起。
pipe()系統(tǒng)調(diào)用在進(jìn)程的打開文件表中查找前兩個可用位置,并將它們分配給管道的讀寫端。

語法
// fd[0]是管道讀取端的fd(文件描述符)
// fd[1]是管道寫入端的fd
// 成功時返回:0
// 錯誤時為-1
int pipe(int fds[2]);
管道的行為類似于隊列數(shù)據(jù)結(jié)構(gòu),是FIFO(先進(jìn)先出),而且讀寫的大小在這里不必匹配。我們一次可以寫入512個字節(jié),而在管道另一端一次讀取1個字節(jié)。
例子1:
// 演示C語言中的pipe()系統(tǒng)調(diào)用
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#define MSG_SIZE 16
const char *msg1 = "hello, world #1";
const char *msg2 = "hello, world #2";
const char *msg3 = "hello, world #3";
int main()
{
char inbuf[MSG_SIZE];
int p[2], i;
// 創(chuàng)建管道
if (pipe(p) < 0)
exit(1);
// 寫入管道
write(p[1], msg1, MSG_SIZE);
write(p[1], msg2, MSG_SIZE);
write(p[1], msg3, MSG_SIZE);
for (i = 0; i < 3; i++) {
// 從管道中讀取
read(p[0], inbuf, MSG_SIZE);
printf("%s\n", inbuf);
}
return 0;
}
輸出
hello, world #1
hello, world #2
hello, world #3
例子2
父子共享管道
當(dāng)我們在任何進(jìn)程中使用fork時,文件描述符在子進(jìn)程和父進(jìn)程之間保持打開狀態(tài),如果我們在創(chuàng)建管道后調(diào)用fork,那么父進(jìn)程和子進(jìn)程就可以通過管道進(jìn)行通信。

// C程序演示C中父子共享的管道系統(tǒng)調(diào)用
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#define MSG_SIZE 16
char* msg1 = "hello, world #1";
char* msg2 = "hello, world #2";
char* msg3 = "hello, world #3";
int main()
{
char inbuf[MSG_SIZE];
int p[2], pid, nbytes;
if (pipe(p) < 0)
exit(1);
// 父進(jìn)程中
if ((pid = fork()) > 0) {
write(p[1], msg1, MSG_SIZE);
write(p[1], msg2, MSG_SIZE);
write(p[1], msg3, MSG_SIZE);
// 添加下面一行代碼來關(guān)閉管道的寫入口就不會引起進(jìn)程掛起
// close(p[1]);
wait(NULL);
// 子進(jìn)程中
} else {
// 添加下面一行代碼來關(guān)閉管道的寫入口就不會引起進(jìn)程掛起
// close(p[1]);
while ((nbytes = read(p[0], inbuf, MSG_SIZE)) > 0)
printf("s %s\n", inbuf);
if (nbytes != 0)
exit(2);
printf("Finished reading\n");
}
return 0;
}
輸出
hello world, #1
hello world, #2
hello world, #3
(hangs) // 程序不終止但掛起
在這個程序中,完成讀/寫后,父進(jìn)程和子進(jìn)程都會阻塞,而不是終止進(jìn)程,這就是程序掛起的原因。之所以會發(fā)生這種情況,是因為read系統(tǒng)調(diào)用獲得的數(shù)據(jù)要么與它請求的數(shù)據(jù)一樣多,要么與管道擁有的數(shù)據(jù)一樣多,兩者中取較少的一個。
這樣一來,如果管道為空,并且我們調(diào)用read系統(tǒng)調(diào)用,則在沒有進(jìn)程的寫入端打開的情況下,管道上的讀取將返回EOF(返回值0)。而如果其他進(jìn)程打開著管道的寫入端,read將阻塞以等待寫入端的新數(shù)據(jù)。
因此此代碼輸出將掛起,因為write結(jié)束但父進(jìn)程沒有關(guān)閉管道寫入端,子進(jìn)程因為完全拷貝父進(jìn)程狀態(tài),它的管道輸入端也是打開著的,所以當(dāng)管道內(nèi)容為空的時候程序?qū)炱稹?/p>
思考:如果把上述代碼的父進(jìn)程和子進(jìn)程的下面代碼去掉注釋會怎么樣? 如果只是注釋掉其中的一個又會怎么樣?
// 添加下面一行代碼來關(guān)閉管道的寫入口就不會引起進(jìn)程掛起
close(p[1]);