1. 程式人生 > >Linux講解 程序間通訊 管道

Linux講解 程序間通訊 管道

  今天我們講解程序間的通訊,首先回顧一下程序的概念:程序是計算機中的程式關於某資料集合上的一次執行活動,是系統進行資源分配和排程的基本單位,是作業系統結構的基礎。程式是指令、資料及其組織形式的描述,程序是程式的實體。

  程序的使用者空間是相互獨立的,一般來說是不能相互訪問的,但是很多情況需要我們不同的程序共同完成一個任務,或者程序之間需要溝通交流的時候,就必須要用到我們的程序間通訊。

  經常在以下場景的時候用到我們的程序間的通訊:

  1. 資料傳輸:一個程序需要向另一個程序傳送資料資訊的時候,並且資料資訊不能太大的時候。

  2. 資源共享:多個程序之間共享某一部分的資源,假如其中一個程序對一個數據進行了修改,另一個程序要知道。

  3. 通知事件:一個程序需要向另一個程序傳送資訊,通知他們發生了某種事件的時候,比如我們的子程序結束的時候要發訊息給我們的父程序。

  4. 程序控制:當某一些程序控制另一部程序的時候,這時候這個程序就需要知道他想操控的程序的異常等等資訊。並且能夠知道它的一些狀態改變。

  進程序間通訊主要包括管道, 系統IPC(包括訊息佇列,訊號,共享儲存), 套接字(SOCKET).今天我們主要講解第一種--管道。管道是unix上中最古老的程序間通訊的形式。管道的本質就是一塊緩衝區,管道有一個很大的特性就是半雙工、單向通訊。也就是說資料只能向其中一個方向流動。如果需要雙方通訊的時候,就需要建立兩個管道。

  系統給我們提供了建立管道的介面,我們可以通過pipe函式來建立一個管道。

int pipe(int pipefd[2]); 這裡的pipefd[2]陣列是用來接收我們的管道建立成功之後返回的兩個檔案描述符pipefd[0]用於從管道讀取資料,而pipefd[1]用於往管道中寫入資料。大家也可以看到我們的函式返回值是int型別,如果我們的管道成功建立會返回0,如果建立失敗則返回-1。並且使用我們的pipe函式的時候需要包含我們的#include<unistd.h>標頭檔案。

#include<stdio.h>

#include<unistd.h>

#include<string.h>

#include<errno.h>

//實現匿名管道

int main()

{

    int fd[2];

    if(pipe(fd)<0)

       {

           perror("pipe error\n");

           return -1;

       }

    int pid=-1;

    pid=fork();//建立一個子程序

    if(pid<0)

    {

        perror("pid error\n");

        return -1;

    }

    if(pid==0)

     {

         //進來說明是子程序,我們讓子程序來讀取資料

        close(fd[1]);//管道是半雙工、單向所以需要關掉一個,這裡關掉的是fd【1】寫入資料的檔案描述符

        char buff[1024];

        sleep(5);

        read(fd[0],buff,1024);

        printf("child: %s\n",buff);

        close(fd[0]);

     }

    else

    {

        //這個進來的是父程序

        close(fd[0]);

        write(fd[1],"hello ",6);

        write(fd[1],"world ",6);

        write(fd[1],"pipe ",5);

        close(fd[1]);

    }

    return 0;

}

我們通過程式來建立一個管道,然後用它來進行傳輸資訊。

因為我們在程式中對子程序就行了休眠5s所以這裡執行程式之後需要等5s才能輸出資料,這裡我們是為了保證父程序把資料寫進去了,其實這裡3s,1s都可以。這算是實現了父程序和子程序之間的通訊,這是我們管道的實現。

  當我們執行完fork函式之後會變成上圖的樣子,子程序複製了父程序,他們都有一個fd陣列都能對管道進行讀寫,但是我們需要對它進行關閉。

關掉各自不用的之後就是一個讀一個寫。

我們站在檔案描述符來看我們建立的管道的時候就是

父程序建立了一個管道之後新建的兩個檔案描述符分別指向了我們管道的讀端和寫端,之後我們通過fork來建立一個子程序

這時候我們的子程序就也有對應的檔案描述符指向我們和父程序同一個的管道。

之後關掉之後我們就可以用對應的檔案描述符來對管道進行讀寫操作。

  我們的管道讀寫存在四種情況:

  1. 我們的寫端不斷的往管道里邊寫入資料,但是我們的讀端卻不去讀取資料,我們的管道也是有大小的,這樣下去就會導致我們的管道被資料寫滿,從而導致再次去呼叫write函式的時候導致阻塞,直到我們的管道中有空間寫入資料的時候再去返回。

  2. 我們的管道寫端沒有關閉,但是他並不忘裡邊寫資料,我們的讀端卻一直要去讀取資料,這時候我們呼叫read函式的時候會導致堵塞,直到我們管道中有資料讀取可以返回的時候再返回。

  3. 寫端不寫資料了,但是我們把寫端的檔案描述符fd[0]給關掉了,這時候我們讀端讀取完管道中剩餘的資料之後,如果沒有資料了,read就會返回0,和我們在讀取檔案的時候讀取到檔案結尾的情況是一樣的。

  4. 讀端不讀取資料了,並且關掉了我們讀取的描述符fd[1],但是寫資料的一段還在不斷的寫,程序就會收到SIGPIPE訊號導致我們的程序異常終止掉。

  在管道的寫端或者讀端有一個引用計數,就是來計算我們的寫端或者讀端是不是還有檔案描述符在控制著它。

  管道有幾大特點:

  1. 只能用於有親緣關係之間的程序使用,通常就是我們上邊介紹的由一個父程序建立一個管道然後通過fork函式來實現父子間都可以使用的管道。

  2. 一般而言,管道的生命週期和程序是一樣的,隨著程序的退出管道也會釋放。

  3. 一般來說程序對管道的操作是互斥和同步的。保證資料訪問的一致性。

  4. 管道是半雙工的,當需要雙向通訊的時候需要建立兩個管道。