管道機(jī)制

本文摘抄自linux基礎(chǔ)編程

管道指的是從一個進(jìn)程連接數(shù)據(jù)流到另一個進(jìn)程。它具有以下特點(diǎn):

  • 管道是半雙工的,數(shù)據(jù)只能向一個方向流動;需要雙方通信時,需要建立起兩個管道;
  • 只能用于父子進(jìn)程或者兄弟進(jìn)程之間(具有親緣關(guān)系的進(jìn)程);
  • 單獨(dú)構(gòu)成一種獨(dú)立的文件系統(tǒng):管道對于管道兩端的進(jìn)程而言,就是一個文件,但它不是普通的文件,它不屬于某種文件系統(tǒng),而是自立門戶,單獨(dú)構(gòu)成一種文件系統(tǒng),并且只存在與內(nèi)存中。

數(shù)據(jù)的讀出和寫入:一個進(jìn)程向管道中寫的內(nèi)容被管道另一端的進(jìn)程讀出。寫入的內(nèi)容每次都添加在管道緩沖區(qū)的末尾,并且每次都是從緩沖區(qū)的頭部讀出數(shù)據(jù)。

本文詳細(xì)介紹管道相關(guān)信息。

管道

基于標(biāo)準(zhǔn)庫popen和pclose的進(jìn)程管道

#include <stdio.h>  
FILE *popen(const char *command, const char *type);  
int pclose(FILE *stream);  

popen允許一個程序?qū)⒘硪粋€程序作為新進(jìn)程啟動。并可以傳遞數(shù)據(jù)給它或者通過它接受數(shù)據(jù)。type僅能為"r"和“w”兩種。當(dāng)type="r"時,調(diào)用程序可以通過FILE文件流指針從被調(diào)用程序的輸出獲得數(shù)據(jù);當(dāng)type="w"時,調(diào)用程序可以通過fwrite函數(shù)向被調(diào)用程序發(fā)送數(shù)據(jù)。popen函數(shù)不支持任何其他選項。當(dāng)popen啟動的進(jìn)程結(jié)束后,需要利用pclose關(guān)閉與之關(guān)聯(lián)的文件流。如果在調(diào)用pclose時候,進(jìn)程還在運(yùn)行,那么pclose將會阻塞等待進(jìn)程結(jié)束再返回。

static void check_image(char * imagename)  
{  
    FILE * fp;  
    char command[120];  
    char buf[200];  
    bool ret;  
  
    sprintf(command, "cksum %s", imagename);  
    fp = popen(command, "r");  
    if(fp == NULL) return;  
  
    fgets(buf, 200, fp);  
    fprintf(stdout, "Check Image:  %s... Done\n", buf);  
    fclose(fp);  
    return;  
}  

基于系統(tǒng)調(diào)用pipe的進(jìn)程管道

#include <unistd.h>  
int pipe(int pipefd[2]);  

利用pipe創(chuàng)建的管道包含兩個文件描述符fd[0]以及fd[1],需要注意的是,該處是文件描述符而不是文件流,對該文件描述符進(jìn)行讀寫必須采用read和write系統(tǒng)調(diào)用,管道的兩端是固定了任務(wù)的。即一端只能用于讀,由描述字fd[0]表示,稱其為管道讀端;另一端則只能用于寫,由描述字fd[1]來表示,稱其為管道寫端。如果試圖從管道寫端讀取數(shù)據(jù),或者向管道讀端寫入數(shù)據(jù)都將導(dǎo)致錯誤發(fā)生。結(jié)構(gòu)如下圖:

從管道中讀取數(shù)據(jù)
如果管道的寫端不存在,則認(rèn)為已經(jīng)讀到了數(shù)據(jù)的末尾,讀函數(shù)返回的讀出字節(jié)數(shù)為0;
當(dāng)管道的寫端存在時,如果請求的字節(jié)數(shù)目大于PIPE_BUF,則返回管道中現(xiàn)有的數(shù)據(jù)字節(jié)數(shù),如果請求的字節(jié)數(shù)目不大于PIPE_BUF,則返回管道中現(xiàn)有數(shù)據(jù)字節(jié)數(shù)(此時,管道中數(shù)據(jù)量小于請求的數(shù)據(jù)量);或者返回請求的字節(jié)數(shù)(此時,管道中數(shù)據(jù)量不小于請求的數(shù)據(jù)量)。

向管道中寫入數(shù)據(jù)
向管道中寫入數(shù)據(jù)時,linux將不保證寫入的原子性,管道緩沖區(qū)一有空閑區(qū)域,寫進(jìn)程就會試圖向管道寫入數(shù)據(jù)。如果讀進(jìn)程不讀走管道緩沖區(qū)中的數(shù)據(jù),那么寫操作將一直阻塞。
注:只有在管道的讀端存在時,向管道中寫入數(shù)據(jù)才有意義。否則,向管道中寫入數(shù)據(jù)的進(jìn)程將收到內(nèi)核傳來的SIFPIPE信號,應(yīng)用程序可以處理該信號,也可以忽略(默認(rèn)動作則是應(yīng)用程序終止)。

管道的主要局限性正體現(xiàn)在它的特點(diǎn)上:

只支持單向數(shù)據(jù)流;
只能用于具有親緣關(guān)系的進(jìn)程之間;
沒有名字;
管道的緩沖區(qū)是有限的(管道制存在于內(nèi)存中,在管道創(chuàng)建時,為緩沖區(qū)分配一個頁面大小);
管道所傳送的是無格式字節(jié)流,這就要求管道的讀出方和寫入方必須事先約定好數(shù)據(jù)的格式,比如多少字節(jié)算作一個消息(或命令、或記錄)等等;

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <sys/types.h>  
int main()  
{  
    pid_t pid = 0;  
    int fds[2], nwr = 0;  
    char buf[128];  
    pipe(fds);  
    pid = fork();  
    if(pid < 0)  
    {   
        printf("Fork error.\n");  
        return -1;   
    }else if(pid == 0)  
    {   
        printf("This is child process, pid = %d\n", getpid());  
  
        //part A  
        printf("Child:waiting for message...\n");  
        nwr = read(fds[0], buf, sizeof(buf))  
        printf("Child:received\"%s\"\n", buf);   
  
        //part B  
        printf("Child:send reply\n");  
        strcpy(buf, "Reply from child");  
        nwr = write(fds[1], buf, sizeof(buf));   
        printf("Child:send %d bytes to parent.\n", nwr);  
  
    }else{  
        printf("This is parent process, pid = %d\n", getpid());  
  
        printf("Parent:sending message...\n");  
        strcpy(buf, "Message from parent");  
        nwr = write(fds[1], buf, sizeof(buf));  
        printf("Parent:send %d bytes to child.\n", nwr);  
  
        //part C  
        printf("Parent:waiting for reply from child...\n");  
        nwr = read(fds[0], buf, sizeof(buf));  
        printf("Parent:received \"%s\" from child\n", buf);  
    }  
    return 0;  
}  

命名管道

管道應(yīng)用的一個重大限制是它沒有名字,因此,只能用于具有親緣關(guān)系的進(jìn)程間通信,在有名管道(named pipe或FIFO)提出后,該限制
得到了克服。FIFO不同于管道之處在于它提供一個路徑名與之關(guān)聯(lián),以FIFO的文件形式存在于文件系統(tǒng)中。這樣,即使與FIFO的創(chuàng)建進(jìn)程不存在親緣關(guān)系的進(jìn)程,只要可以訪問該路徑,就能夠彼此通過FIFO相互通信(能夠訪問該路徑的進(jìn)程以及FIFO的創(chuàng)建進(jìn)程之間),因此,通過FIFO不相關(guān)的進(jìn)程也能交換數(shù)據(jù)。值得注意的是,F(xiàn)IFO嚴(yán)格遵循先進(jìn)先出(first in first out),對管道及FIFO的讀總是從開始處返回數(shù)據(jù),對它們的寫則把數(shù)據(jù)添加到末尾。它們不支持諸如lseek()等文件定位操作。

對命名管道的操作和對文件操作相似,包括創(chuàng)建,打開,讀寫,和關(guān)閉操作。

命名管道的創(chuàng)建

#include <sys/types.h>  
#include <sys/stat.h>  
  
int mkfifo(const char *pathname, mode_t mode);  

該函數(shù)的第一個參數(shù)是一個普通的路徑名,也就是創(chuàng)建后FIFO的名字。第二個參數(shù)與打開普通文件的open()函數(shù)中的mode 參數(shù)相同。如果mkfifo的第一個參數(shù)是一個已經(jīng)存在的路徑名時,會返回EEXIST錯誤,所以一般典型的調(diào)用代碼首先會檢查是否返回該錯誤,如果確實
返回該錯誤,那么只要調(diào)用打開FIFO的函數(shù)就可以了嗎,程序如下:

if(access(FIFO_NAME,F_OK)==-1){  
       res=mkfifo(FIFO_NAME,0777);  
       if(res!=0){  
               fprintf(stderr,"Could not create fifo %s\n",FIFO_NAME);
               exit(EXIT_FAILURE);  
      }  
}  
res=open(FIFO_NAME,open_mode);  

命名管道的打開

打開的FIFO的限制是:由于FIFO是單向數(shù)據(jù)傳輸,程序不能以O(shè)_RDWR方式打開FIFO同時進(jìn)行讀寫操作,只能是O_RDONLY或者O_WRONLY方式,打開函數(shù)如下:

#include <sys/types.h>  
#include <sys/stat.h>  
#include <fcntl.h>    
int open(const char *pathname, int flags);  

打開FIFO文件和普通文件的另外一個差別是:O_NONBLOCK選項對open的阻塞的影響,主要分為下面幾種情況:

  • flags=O_RDONLY:open將會調(diào)用阻塞,除非有另外一個進(jìn)程以寫的方式打開同一個FIFO,否則一直等待。

  • flags=O_WRONLY:open將會調(diào)用阻塞,除非有另外一個進(jìn)程以讀的方式打開同一個FIFO,否則一直等待。

  • flags=O_RDONLY|O_NONBLOCK:如果此時沒有其他進(jìn)程以寫的方式打開FIFO,此時open也會成功返回,此時FIFO被讀打開,而不會返回錯誤。

  • flags=O_WRONLY|O_NONBLOCK:立即返回,如果此時沒有其他進(jìn)程以讀的方式打開,open會失敗打開,此時FIFO沒有被打開,返回-1。

命名管道創(chuàng)建和打開測試程序:

#include<unistd.h>  
#include<stdlib.h>  
#include<stdio.h>  
#include<string.h>  
#include<fcntl.h>  
#include<sys/types.h>  
#include<sys/stat.h>  
  
#define FIFO_NAME "/tmp/my_fifo"  
  
int main(int argc,char *argv[])  
{  
        int res;  
        int open_mode=0;  
        if(argc < 2){  
                fprintf(stderr,"Usage:%s<some combination of \  
                        O_RDONLY,O_WRONLY,O_NONBLOCK\n",*argv);  
                exit(EXIT_FAILURE);  
        }  
        argv++;  
        if(strncmp(*argv,"O_RDONLY",8)==0)open_mode|=O_RDONLY;  
        if(strncmp(*argv,"O_WRONLY",8)==0)open_mode|=O_WRONLY;  
        if(strncmp(*argv,"O_NONBLOCK",10)==0)open_mode|=O_NONBLOCK;  
        argv++;  
        if(*argv){  
                if(strncmp(*argv,"O_RDONLY",8)==0)open_mode|=O_RDONLY;  
                if(strncmp(*argv,"O_WRONLY",8)==0)open_mode|=O_WRONLY;  
                if(strncmp(*argv,"O_NONBLOCK",10)==0)open_mode|=O_NONBLOCK;  
        }  
        if(access(FIFO_NAME,F_OK)==-1){  
                res=mkfifo(FIFO_NAME,0777);  
                if(res!=0){  
                        fprintf(stderr,"Could not create fifo %s\n",FIFO_NAME);  
                        exit(EXIT_FAILURE);  
                }  
        }  
        printf("process %d open FIFO with %d\n",getpid(),open_mode);  
        res=open(FIFO_NAME,open_mode);  
        printf("process %d result %d\n",getpid(),res);  
        sleep(5);  
        if(res!=-1)close(res);  
        printf("process %d finished\n",getpid());  
        exit(EXIT_SUCCESS);  
}  
命名管道的讀寫

對命名管道的讀寫需要利用系統(tǒng)調(diào)用read函數(shù):

#include <unistd.h>  
  
ssize_t read(int fd, void *buf, size_t count);  
ssize_t write(int fd, const void *buf, size_t count);  
讀取管道

是否采用O_NONBLOCK非阻塞標(biāo)志對管道的讀有影響:
對一個空的,阻塞的FIFO文件的read調(diào)用將會等待,直到有數(shù)據(jù)可以讀時才繼續(xù)執(zhí)行。
對一個空的,非阻塞的FIFO的read系統(tǒng)調(diào)用將會立即返回0字節(jié)。

寫管道

需要考慮FIFO可以存在的數(shù)據(jù)長度是有限制的,在limits.h文件中由#definde PIPE_BUF語句定義,通常是4096字節(jié)。
對于設(shè)置了阻塞標(biāo)志的寫操作
當(dāng)要寫入的數(shù)據(jù)量不大于PIPE_BUF時,linux將保證寫入的原子性。如果此時管道空閑緩沖區(qū)不足以容納要寫入的字節(jié)數(shù),則進(jìn)入睡眠,直到當(dāng)緩沖區(qū)中能夠容納要寫入的字節(jié)數(shù)時,才開始進(jìn)行一次性寫操作。
當(dāng)要寫入的數(shù)據(jù)量大于PIPE_BUF時,linux將不再保證寫入的原子性。FIFO緩沖區(qū)一有空閑區(qū)域,寫進(jìn)程就會試圖向管道寫入數(shù)據(jù),寫操作在寫完所有請求寫的數(shù)據(jù)后返回。

對于沒有設(shè)置阻塞標(biāo)志的寫操作
當(dāng)要寫入的數(shù)據(jù)量大于PIPE_BUF時,linux將不再保證寫入的原子性。在寫滿所有FIFO空閑緩沖區(qū)后,寫操作返回。
當(dāng)要寫入的數(shù)據(jù)量不大于PIPE_BUF時,linux將保證寫入的原子性。如果當(dāng)前FIFO空閑緩沖區(qū)能夠容納請求寫入的字節(jié)數(shù),寫完后成功返回;如果當(dāng)前FIFO空閑緩沖區(qū)不能夠容納請求寫入的字節(jié)數(shù),則返回EAGAIN錯誤,提醒以后再寫;

命名管道的關(guān)閉
#include <unistd.h>  
int close(int fd);  

總結(jié)

管道常用于兩個方面:在shell中時常會用到管道(作為輸入輸入的重定向),在這種應(yīng)用方式下,管道的創(chuàng)建對于用戶來說是透明的;用于具有親緣關(guān)系的進(jìn)程間通信,用戶自己創(chuàng)建管道,并完成讀寫操作。FIFO可以說是管道的推廣,克服了管道無名字的限制,使得無親緣關(guān)系的進(jìn)程同樣可以采用先進(jìn)先出的通信機(jī)制進(jìn)行通信。管道和FIFO的數(shù)據(jù)是字節(jié)流,應(yīng)用程序之間必須事先確定特定的傳輸"協(xié)議",采用傳播具有特定意義的消息

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

相關(guān)閱讀更多精彩內(nèi)容

  • Android跨進(jìn)程通信IPC整體內(nèi)容如下 1、Android跨進(jìn)程通信IPC之1——Linux基礎(chǔ)2、Andro...
    隔壁老李頭閱讀 16,022評論 19 113
  • 一.管道機(jī)制(pipe) 1.Linux的fork操作 在計算機(jī)領(lǐng)域中,尤其是Unix及類Unix系統(tǒng)操作系統(tǒng)中,...
    Geeks_Liu閱讀 3,805評論 1 9
  • 前言 管道是UNIX環(huán)境中歷史最悠久的進(jìn)程間通信方式,也是最簡單的進(jìn)程間通信方式,一般用來作為IPC的入門,最合適...
    GeekerLou閱讀 1,551評論 0 6
  • 命名管道 (有用的特點(diǎn)): 由于它們出現(xiàn)在文件系統(tǒng)中,所以他們可以像平常的文件名一樣在命令中使用。在創(chuàng)建的FIFO...
    helinyu閱讀 1,329評論 0 1
  • 下午三點(diǎn)左右,在高健家兩層小樓的前面,高健在剖甲魚,動作熟練,媳婦在一旁看著,從小獨(dú)立生活的高健,練就了自己搞飯菜...
    大荷08閱讀 349評論 0 0

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