linux 程式間通訊之管道

Wu_XMing發表於2018-10-17

1.概述

1.1 一個管道是一個位元組流:

  • 使用管道時是不存在訊息或訊息邊界,即讀取程式可以讀取任意大小的資料塊,而不管寫入程式寫入管道的資料塊大小是什麼。
  • 管道傳遞的資料是順序的,即從管道中讀取出來的位元組的順序與被寫入管道的順序一致。

1.2 從管道中讀取資料:

  • 讀取資料為空的管道將會堵塞直到至少有一個位元組被寫入到管道為止。
  • 管道的寫入端沒有關閉,那麼讀取資料將會堵塞直到所有管道的寫入端關閉。
  • 管道的寫入端關閉,那麼程式可以讀取任意大小的資料塊,直至讀完了管道中的所有資料即read()返回0 。

1.3 管道是單向的:

  • 管道中資料的傳遞方向是單向的,管道的一端用於寫入,另一端用於讀取。

1.4 管道的容量是有限的:

  • 管道其實是一個在核心記憶體中維護的緩衝器,這個緩衝器的儲存能力是有限的。
  • 管道被填滿之後,後續向管道寫入操作都會被堵塞直到有讀取程式讀取管道中的資料。

1.5 可以確保寫入不超過PIPE_BUF位元組的操作是原子的:

  • 如果多個程式寫入同一個管道,那麼如果他們在一個時刻寫入的資料量不超過PIPE_BUF位元組,那麼就可以確保寫入的資料不會混淆。
  • 寫入的資料塊大小超過了PIPE_BUF位元組,那麼核心會將資料分割成幾個較小的片段傳輸,在讀者從管道中消耗資料時再附加上後續的資料。(write()呼叫會阻塞直到所有資料被寫入到管道為止),所以當多個寫入程式寫入大資料塊的時候,可能會出現資料交叉的現象。

2.建立和使用管道

#include<unistd.h>
int pipe(int filedes[2]);//return 0 on success,or -1 on error
複製程式碼
  • filedes[0]表示管道的讀取端;

  • filedes[1]表示管道的寫入端;

  • 使用read()和write()系統呼叫來在管道上執行I/O。

linux 程式間通訊之管道
建立完管道之後處理檔案描述符

3.關閉未使用管道檔案描述符

linux 程式間通訊之管道

3.1 讀取程式需關閉其持有的管道寫入描述符的原因:

read()會堵塞直到所有管道的寫入描述符關閉為止。

3.2 寫入程式需關閉其持有的管道讀取描述符的原因:

當一個程式試圖向管道中寫入資料但沒有任何程式擁有該管道的開啟著的讀取描述符的時候,核心會向寫入程式傳送一個SIGPIPE訊號(預設會殺死程式),程式可以捕獲或忽略該訊號,這樣write操作將會返回EPIPE的錯誤。收到SIGPIPE訊號或得到EPIPE錯誤可以獲知管道的狀態,可以避免寫入被堵塞的風險,因為一直寫入資料沒有讀取,將會充滿管道,那麼後續的寫入請求都將會被堵塞。

3.3 關閉未使用管道檔案描述符的原因:

當所有程式中的管道檔案描述符都關閉之後才會銷燬該管道以及釋放該管道佔用的資源以供其他程式複用。同時,管道中的資料都會丟失。

4.管道可以作為一種程式同步的方法

父程式在建立子程式之前構建了一個管道。每個子程式會繼承管道的寫入端的檔案描述符並完成動作之後關閉這些描述符。當所有子程式都關閉了管道的寫入端的檔案描述符之後,父程式在管道上的read()就會結束並返回檔案結束(0)。這時,父程式就能夠做其他工作了。(父程式要關閉管道的寫入端,否則將永遠阻塞。)

   switch (fork()) {
        case -1:
                ERR_EXIT("fork");
        case 0:
                if (close(pfd[0]) == -1)
                    ERR_EXIT("close");
                
                //Child does some work,and lets parent know It‘s done
                
                if(close(pfd[1])==-1)
                    ERR_EXIT("close");

                //child now carries on to do other things...
               
                _exit(EXIT_SUCCESS);
        default:
                break;
    }

    if(close(pfd[1])==-1)
        ERR_EXIT("close");
    //parent may do other work,then synchronizes with children

    if(read(pfd[0],&dummy,1)!=0)//block
        ERR_EXIT("read");
 
複製程式碼

5.通過管道與shell命令進行通訊:popen()

#include<stdio.h>
FIFE *popen(const char *command,const char *mode);
    //return file stream,or NULL on error
int pclose(FILE *stream);
    //return termination status of child process,or -1 on error
複製程式碼

popen() 函式建立了一個管道,然後建立了一個子程式來執行shell,而shell又建立了一個子程式來執行command字串。mode引數是一個字串,它確呼叫程式是從管道中讀取資料(mode是r)還是將資料寫入到管道中(mode是w)。

mode的取值確定了所執行的命令的標準輸出是連線到管道的寫入端還是將其標準輸入連線到管道的讀取端。

呼叫程式fp--(fork(),exec())-->/bin/sh--(fork(),exec())-->命令(stdout)--(管道)-->呼叫程式fp

a)mode is r

呼叫程式fp--(fork(),exec())-->/bin/sh--(fork(),exec())-->命令(stdin)

呼叫程式fp--(管道)-->命令stdin

b) mode is w

相關文章