linux程式間通訊--管道(PIPE & FIFO)

theboynoName發表於2020-11-28

linux程式間通訊–管道(PIPE & FIFO)

參考資料:

  1. overview of pipes and FIFOs:man 7 pipe
  2. 《The Linux Programming inTerface》

管道一般分為無名管道pipe和有名管道mkfifo.都用於程式之間的通訊。下面將一一介紹它們。

無名管道

無名管道(pipe)一般用於關聯程式(如父子程式)之間的通訊。它的使用類似檔案,但他不是普通檔案,不屬於某種檔案系統。我們最常見的pipe使用是shell命令,如ls | wc -l。pipe有如下特點:

  1. 半雙工:資料只能向一個方向流動.需要雙方通訊時,建立兩個管道.
  2. 關聯程式:只能用於關聯程式之間。如父子程式或者兄弟程式之間通訊。
  3. 面向位元組流:從管道可讀取任意大小的資料塊。且只能順序的讀取,即不能使用lseek()隨機訪問管道中的資料。
  4. 原子操作:如果多個程式向一個管道寫入資料,如果它們一次寫入的位元組不超過PIPE_BUF(在limits.h),就可以保證它們的資料不會混雜在一起。

管道在讀寫時的行為如下:

  1. 寫入達到上限:當寫入管道的資料達到上限,則阻塞.直到管道資料被讀走。然後在繼續寫入。上限預設為65536.可通過fcntl(fd, F_SETPIPE_SZ, size)修改。
  2. 讀出為空:從空的管道中讀取資料,程式阻塞。直到至少有一個位元組被寫入該管道。如果管道的寫端被關閉,那麼讀取資料的程式在讀取完管道中剩餘資料後將看到檔案結束(end-of-file)。

使用函式pipe()建立管道,需要包含標頭檔案unistd.h.:

  1. int pipe(int fildes[2])
    1. 建立一個管道,並將管道的讀寫端檔案描述符(分別)放入filedes[0]和filedes[1]中。有了檔案操作符後,我們就可以使用write(),read()進行讀寫了。
    2. fildes:檔案描述符陣列,fildes[0]儲存讀檔案描述符。fildes[0]儲存寫檔案描述符。
    3. return:if true:0,if false:-1.errno如下:
      1. EMFILE:程式開啟檔案過多
      2. ENFILE:整個系統中開啟的檔案太多了。

linux提供了非標準的pipe2()函式。功能比pipe()豐富。可自行查詢瞭解。

利用管道進行程式間通訊的原理如下圖:pipe

下面是一個pipe使用的例程,展示了父子程式之間的通訊:

#include <unistd.h>
#include <sys/ipc.h>
#include  <stdio.h>
#include <error.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

void main(void)
{
    int res;
    int pipe_fd[2];

    //1. 建立管道
    res = pipe(pipe_fd);
    if(res == -1){
        printf("pipe error\n");
        exit(1);
    }

    //2. 建立執行緒
    pid_t pid = fork();
    if(pid == 0){//子程式
        char buf[24];

        //3. 子程式用於讀,所以關掉寫檔案描述符
        if (close(pipe_fd[1]) == -1){
            printf("pipe pipe_fd[1] close error\n");
            exit(1);
        }

        //4. 等待接收父程式的資料
        while (1){
            ssize_t num = read(pipe_fd[0], buf, 24);
            if(num == 0){
                break;
            }
            else if (num == -1){
                printf("read error\n");
                exit(1);
            }
            write(STDOUT_FILENO, buf, num);//列印到控制檯
        }
        write(STDOUT_FILENO, "\n", 1);

        //5. 關掉讀檔案描述符
        if (close(pipe_fd[0]) == -1){
            printf("pipe pipe_fd[0] close error\n");
            exit(1);
        }

        exit(0);
    }
    else{//父程式
        //6. 父程式用於寫,所以關掉讀檔案描述符
        if (close(pipe_fd[0]) == -1){
            printf("pipe pipe_fd[0] close error\n");
            exit(1);
        }

        //7. 傳送相關資料
        char write_buff[] = "hello word";
        write(pipe_fd[1], write_buff,sizeof(write_buff));

        //8. 關掉寫檔案描述符
        if (close(pipe_fd[1]) == -1){
            printf("pipe pipe_fd[1] close error\n");
            exit(1);
        }

        wait(NULL);
        exit(0);
    }
}

有名管道

有名管道(FIFO)是無名管道(pipe)的一種升級版。主要區別如下:

  1. 有名管道有檔名稱且在檔案系統中可見。
  2. 由於有名稱,所以可以在任意程式下建立通訊。

有名管道在讀寫時的行為如下:

  1. 讀取FIFO檔案的程式只能以RDONLY方式開啟FIFO檔案.
  2. 寫FIFO檔案的程式只能以WRONLY方式開啟FIFO.

其他與無名管道相似,例如:

  1. 不允許檔案定位,只能順序訪問。
  2. 面向位元組流。
  3. 管道在進行讀寫時必須都處於開啟狀態:這句話說的比較模糊需要解釋一下。筆者測試後得到如下結論:
    1. 在open()函式開啟fifo檔案階段:寫方式的open()會阻塞,等待讀方式的open()開啟fifo檔案。這個階段後管道的讀寫端便處於同時開啟狀態。即滿足上邊的條件。
    2. 使用read(),write()操作fifo階段:在fifo讀寫端都開啟之後(即滿足第一個條件後),便可以進行讀寫操作了。行為與pipe相同
      1. 寫入達到上限:當寫入管道的資料達到上限,則阻塞.直到管道資料被讀走。然後在繼續寫入。上限預設為65536.
      2. 讀出為空:從空的管道中讀取資料,程式阻塞。直到至少有一個位元組被寫入該管道。如果管道的寫端被關閉,那麼讀取資料的程式在讀取完管道中剩餘資料後將看到檔案結束(end-of-file)。
    3. 在讀寫過程中,如果close()了讀取管道的檔案操作符:此時進行寫操作會觸發SIGPIPE訊號,如果訊號被處理或阻塞,它會以錯誤程式碼EPIPE失敗。
    4. 在讀寫過程中,如果close()了寫入管道的檔案操作符:讀取資料的程式在讀取完管道中剩餘資料後將看到檔案結束(end-of-file)。

使用mkfifo()函式建立一個命名管道。使用時需要包含標頭檔案sys/stat.h。函式如下:

  1. int mkfifo (const char *filename, mode t mode)
    1. 建立一個命名管道.
    2. filename:檔名
    3. mode:檔案許可權。如0666
    4. return:if true:0,if false:-1.errno如下:
      1. EEXIST:名稱已經存在

其餘的函式和檔案操作函式相同。

演示例程如下:

#include <unistd.h>
#include <sys/ipc.h>
#include  <stdio.h>
#include <error.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
 #include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

void main(void)
{
    int res;
    //1. 建立fifo
    res = mkfifo("/tmp/fifo", 0666);
    if(res == -1  && errno != EEXIST){
        printf("mkfifo error\n");
        exit(1);
    }

    //2. 程式之間使用fifo通訊,為了方便直接使用父子程式
    pid_t pid = fork();
    if(pid == 0){//子程式
        //3. 開啟fifo
        int fd = open("/tmp/fifo", O_RDONLY);
        if(fd == -1){
            printf("(%d)open error\n",getpid());
            exit(1);
        }

        char buff[32];
        while(1){
            //4. 讀取fifo的內容
            ssize_t num = read(fd, buff, sizeof(buff));
            if(num == 0){
                break;
            }
            else if (num == -1){
                printf("read error\n");
                exit(1);
            }
            write(STDOUT_FILENO,buff,num);
        }
        write(STDOUT_FILENO, "\n", 1);

        //5. 關掉讀檔案描述符
        if (close(fd) == -1){
            printf("(%d) close error\n",getpid());
            exit(1);
        }
    }
    else{
        //6. 父程式以只寫方式開啟
        int fd = open("/tmp/fifo", O_WRONLY);
        if (fd == -1){
            printf("(%d)open error\n",getpid());
            exit(1);
        }

        //7. 傳送相關資料
        char write_buff[] = "hello word";
        write(fd, write_buff,sizeof(write_buff));

        //8. 關掉寫檔案描述符
        if (close(fd) == -1){
            printf("(%d) close error\n",getpid());
            exit(1);
        }

        wait(NULL);
        exit(0);
    }
}
buff,sizeof(write_buff));

        //8. 關掉寫檔案描述符
        if (close(fd) == -1){
            printf("(%d) close error\n",getpid());
            exit(1);
        }

        wait(NULL);
        exit(0);
    }
}

關於技術交流

此處後的文字已經和題目內容無關,可以不看。
qq群:825695030
微信公眾號:嵌入式的日常
如果上面的文章對你有用,歡迎打賞、點贊、評論。二維碼

相關文章