本文轉載自:http://blog.csdn.net/Xiejingfa/article/details/50819660
進程間通信,英文又稱作IPC(InterProcess Communication)就是在不同的進程之間交換信息。我們知道,進程的用戶是相空間互獨立的,不能相互訪問,所以爲了使得進程間可以相互通信,操作系統需要提供一種介質使通信雙方都可以訪問。類似這樣的介質有很多,比如磁盤上的文件,我們只要協調好進程訪問文件的順序,一個進程先“寫”,一個進程後“讀”,這樣,兩者就可以相互交換信息了。又比如我們經常使用的數據庫,也可以作爲信息交換的媒介。但是這些通信手段效率都太低了,需要在系統級別提供高效的進程間通信手段。今天我們就來介紹一種最古老的進程間通信方式 – 管道。
1、什麼是管道?
如果你熟悉Linux系統,一定不會對管道感到陌生。在Linux系統中,我們經常通過符號“|”來使用管道,用以連接兩個或多個命令。那管道是什麼?實際上,管道是進程與進程間的數據流通道,它使得數據可以以一種“流”的形式在進程間流動。
管道也是Unix/Linux系統中一種最常見的進程間通信方式,它在兩個通信進程之間實現一個數據流的通道從而進行信息傳遞。
管道通信具有以下特點:
- 管道是沒有名字的,因此有稱爲匿名管道。它在系統中是沒有實名的,不能在文件系統中看到管道。
- 在大多數系統中,管道通信是半雙工的,也就是說數據只能在一個方向上流動。
- 管道中傳送的是無格式的字節流。
- 管道的緩衝區是有限的,它的大小在include/linux/limits.h中由PIPE_BUF定義。
- 管道只能用於具有親緣關係的進程間進行通信,如父子進程或者兄弟進程。
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等都可以用於管道的讀寫。
如下圖所示:
經過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環境高級編程》