Linux進程間通信——管道

本文轉載自:http://blog.csdn.net/Xiejingfa/article/details/50819660

進程間通信,英文又稱作IPC(InterProcess Communication)就是在不同的進程之間交換信息。我們知道,進程的用戶是相空間互獨立的,不能相互訪問,所以爲了使得進程間可以相互通信,操作系統需要提供一種介質使通信雙方都可以訪問。類似這樣的介質有很多,比如磁盤上的文件,我們只要協調好進程訪問文件的順序,一個進程先“寫”,一個進程後“讀”,這樣,兩者就可以相互交換信息了。又比如我們經常使用的數據庫,也可以作爲信息交換的媒介。但是這些通信手段效率都太低了,需要在系統級別提供高效的進程間通信手段。今天我們就來介紹一種最古老的進程間通信方式 – 管道。


1、什麼是管道?

如果你熟悉Linux系統,一定不會對管道感到陌生。在Linux系統中,我們經常通過符號“|”來使用管道,用以連接兩個或多個命令。那管道是什麼?實際上,管道是進程與進程間的數據流通道,它使得數據可以以一種“流”的形式在進程間流動。

管道也是Unix/Linux系統中一種最常見的進程間通信方式,它在兩個通信進程之間實現一個數據流的通道從而進行信息傳遞。

管道通信具有以下特點:

  1. 管道是沒有名字的,因此有稱爲匿名管道。它在系統中是沒有實名的,不能在文件系統中看到管道。
  2. 在大多數系統中,管道通信是半雙工的,也就是說數據只能在一個方向上流動。
  3. 管道中傳送的是無格式的字節流。
  4. 管道的緩衝區是有限的,它的大小在include/linux/limits.h中由PIPE_BUF定義。
  5. 管道只能用於具有親緣關係的進程間進行通信,如父子進程或者兄弟進程。

2、管道的創建

Linux下管道是通過pipe函數創建的,函數原型如下:

 #include <unistd.h>
 int pipe(int pipefd[2]); // 若成功則返回0,出錯則返回-1
  • 1
  • 2
  • 1
  • 2

其中參數pipefd[2]是一個長度爲2的文件描述符數組,pipefd[0]是讀端,pipefd[1]是寫端。

管道的關閉則使用close函數。

 #include <unistd.h>
 int close(int fd);
  • 1
  • 2
  • 1
  • 2

管道關閉時,需要將pipefd[0]和pipefd[1]都關閉掉。

我們先用一段代碼演示一下管道的創建:

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>

int main()
{
    char buf[128];
    int fd[2];
    if (pipe(fd) < 0)
    {
        printf("pipe error!\n");
        exit(1);
    }

    write(fd[1], "Hello World!", 12);
    bzero(buf, sizeof(buf));
    read(fd[0], buf, sizeof(buf));
    printf("%s\n", buf);
    close(fd[0]);
    close(fd[1]);
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

代碼輸出如下:

Hello World!
  • 1
  • 1

在上面的例子中,我們使用pipe()函數創建了一個管道。到這裏你可能會感到奇怪:不是說管道是進程間通信的方式嗎?在單個進程中創建管道,然後從一段寫入從另一端讀出有什麼意義?下面我們重點講解一下管道如何實現進程間通信。

3、管道的讀寫

正如你所想的,在單個進程中的創建管道幾乎沒有意義。前面我們說過管道只能用於父子進程或者兄弟進程間進行通信,通常我們先創建pipe,接着調用fork函數,從而創建從父子進程或者兄弟進程間的ipc通道。

爲什麼這樣就能創建進程通信的管道呢?其中的功臣就是fork函數。fork函數創建出來的子進程是父進程的一個拷貝,子進程從父進程中得到數據段和堆棧段的拷貝。這樣,父子都擁有事先創建管道的文件描述符,都可以往管道中讀寫信息。一般用於文件的IOh函數如read、write等都可以用於管道的讀寫。

如下圖所示:

摘自APUE一書

經過fork函數調用之後,父子進程間存在兩種數據流向的管道:一個是從父進程到子進程的數據通道,另一個是從子進程到父進程的數據通道。管道只支持半雙工通信,所以我們必須關閉其中一個數據通道!!

比如:如果你想建立一個父進程到子進程的數據通道,就要在父進程中關閉讀端,在子進程中關閉寫端。

通常,利用pipe和fork的組合,我們可以構造父子進程或者兄弟進程間任意方向的的數據通道。

示例:

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>

#define BUF_SIZE 256

int main()
{
    int fd[2];
    char data[] = "Hello, I am parent!";
    char buf[BUF_SIZE];
    pid_t pid;

    if (pipe(fd) < 0)
    {
        printf("pipe error!\n");
        exit(1);
    }

    pid = fork();
    if (pid < 0)
    {
        printf("pipe error!\n");
        exit(1);
    }
    else if (pid == 0)
    {
        close(fd[1]);
        int len = read(fd[0], buf, sizeof(buf));
        printf("child: %s\n", buf);
    }
    else 
    {
        close(fd[0]);
        write(fd[1], data, strlen(data));
        printf("parent: %s\n", data);
        sleep(1);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

輸出如下:

parent: Hello, I am parent!
child: Hello, I am parent!
  • 1
  • 2
  • 1
  • 2

上面的例子演示了利用管道在父進程到子進程的通信,而管道在兄弟進程間通信則需要在父進程調用兩次fork函數創建兩個子進程,在兩個兄弟子進程中維護通信管道。

4、管道讀寫的規則

管道寫入的規則:程序向管道寫入數據時,如果緩衝區有空閒空間,寫進程立即向管道寫入數據。如果緩衝區已滿,那麼寫操作就一直阻塞到讀進程讀走數據爲止。

管道讀取的規則:如果請求的字節數大於PIPE_SIZE,則返回管道中現有的字節數。如果請求的字節數不大於PIPE_SIZE,則返回請求的字節數或現有數據的字節數(管道中的數據量小於請求的數據量)。

如果讀寫一端已經被關閉的管道是呢?

如果讀一個寫端已經被關閉的管道時,當所有的數據都被讀取後,read返回0,表示文件結束。

如果寫一個讀端已經被關閉的管道,則產生SIGPIPE信號。如果忽略該信號或從處理程序中返回後,write函數返回-1,並且errno被設置爲EPIPE。


參考資料:《UNIX環境高級編程》

發佈了41 篇原創文章 · 獲贊 145 · 訪問量 25萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章