pipe() 系統(tǒng)調(diào)用

從概念上講,管道(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]); 
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容