眾所周知作為UNIX IPC中老的一種形式管道,是所有UNIX系統都提供的一種通信機制,因而它應用的范圍非常廣泛。例如我們可以使用管道符“|”來連接進程。在Linux系統中,由管道連接起來的進程可以自動運行,就如同在他們有一個數據流一樣。根據管道的適用范圍將其分為:無名管道(pipe)和有名管道(fifo)。本文主要圍繞二者出發,討論管道通信的機制。
一、無名管道(pipe)
1.什么是管道
一個管道實際上就是個只存在于內存中的文件,對這個文件的操作要通過兩個已經打開文件進行,它們分別代表管道的兩端。管道是一種特殊的文件,它不屬于某一種文件系統,而是一種獨立的文件系統,有其自己的數據結構。類似時空隧道的概念,建立兩個進程之間的通訊橋梁。數據的讀出和寫入:一個進程向管道中寫的內容被管道另一端的進程讀出。寫入的內容每次都添加在管道緩沖區的末尾,并且每次都是從緩沖區的頭部讀出數據。
2.無名管道的特性
(1)只能用于具有親緣關系的進程之間的通信,通常一個管道由一個進程創建,然后該進程調用fork,此后父子進程之間就可以通過管道通信。
(2)半雙工的通信模式,具有固定的讀端和寫端:傳輸方向同時只能是一個方向,。
(3)管道可以看成是一種特殊的文件,對于它的讀寫可以使用文件IO如read、write函數:但是在文件系統里并不存在pipe對應的文件而且不支持如lseek() 操作。
3.無名管道的創建
無名管道可以由pipe()函數創建
#include
int pipe(int pipefd[2]);
pipe函數調用成功返回0,調用失敗返回-1。
調用pipe函數時在內核中開辟一塊緩沖區(稱為管道)用于通信,它有一個讀端一個寫端,然后通過pipefd參數傳出給用戶程序兩個文件描述符,pipefd[0]指向管道的讀端,pipefd[1]指向管道的寫端(很好記,就像0是標準輸入1是標準輸出一樣)。所以管道在用戶程序看起來就像一個打開的文件,通過read(pipefd[0]);或者write(pipefd[1]);向這個文件讀寫數據其實是在讀寫內核緩沖區。
詳細的創建流程:
step1: 父進程創建一個pipe,其中fd[0]固定用于讀管道,而fd[1]固定用于寫管道。
Step2:父進程fork,子進程繼承了父進程的管道
Step3:之后取決于我們想要的數據流方向來關閉相應的端。
4.無名管道讀寫注意事項
當管道的一端被關閉后:
(1)當讀一個寫端已被關閉的管道時,在所有數據都被讀取后, read返回0,以指示達到了文件結束處(從技術方面考慮,管道的寫端還有進程時,就不會產生文件的結束。可以復制一個管道的描述符,使得有多個進程具有寫打開文件描述符。但是,通常一個管道只有一個讀進程,一個寫進程)。
(2)如果寫一個讀端已被關閉的管道,則產生信號SIGPIPE。如果忽略該信號或者捕捉該信號并從其處理程序返回,則write出錯返回,errn設置為EPIPE。在寫管道時,常數PIPE_BUF規定了內核中管道緩存器的大小。如果對管道進行write調用,而且要求寫的字節數小于等于PIPE_BUF,則此操作不會與其他進程對同一管道(或FIFO)的write相交錯。但是,若有多個進程同時寫一個管道(或FIFO),而且某個或某些進程要求寫的字節數超過PIPE_BUF字節數,則數據可能會與其他寫操作的數據相交錯。
5.popen和pclose函數
因為常見的操作是創建一個連接到另一個進程的管道,然后讀其輸出或向其發送輸入,所以標準I / O庫為實現這些操作提供了兩個函數popen和pclose。在這邊就不詳細說明了,大家感興趣的化可以查看Man手冊中的描述。
6.示例代碼
#include
#include
#include
int pid1, pid2;
int main( )
{
int fd[2];
char outpipe[100], inpipe[100];
pipe(fd); /*創建一個管道*/
while ((pid1 = fork( )) == -1);
if (pid1 == 0)
{
sprintf(outpipe, "child 1 process is sending message!");
/*把串放入數組outpipe中*/
write(fd[1], outpipe, 50); /*向管道寫長為50字節的串*/
exit(0);
}
else
{
while((pid2 = fork( )) == -1);
if (pid2 == 0)
{
sprintf(outpipe, "child 2 process is sending message!");
write(fd[1], outpipe, 50);
exit(0);
}
else
{
//wait(NULL); /*同步*/
read(fd[0], inpipe, 50); /*從管道中讀長為50字節的串*/
printf("%s\n", inpipe);
//wait(NULL);
read(fd[0], inpipe, 50);
printf("%s\n",inpipe);
exit(0);
}
}
return 0;
}
二、有名管道(fifo)
1.有名管道的概念
為何要提出有名管道的說法,目的是為了克服無名管道的不足之處:
(1)無名管道只能用于具有親緣關系的進程之間,這就限制了無名管道的使用范圍
(2)有名管道可以使互不相關的兩個進程互相通信。有名管道可以通過路徑名來指出,并且在文件系統中可見
為了這種有名管道,Linux中專門設立了一個專門的特殊文件系統--管道文件,以FIFO的文件形式存在于文件系統中,這樣,即使與FIFO的創建進程不存在親緣關系的進程,只要可以訪問該路徑,就能夠彼此通過FIFO相互通信,因此,通過FIFO不相關的進程也能交換數據。但在磁盤上只是一個節點,而文件的數據則只存在于內存緩沖頁面中,與普通管道一樣。
2.有名管道的創建
(1)有名管道可以從命令行上創建,命令行方法是使用下面這個命令:
$ mkfifo myfifo
(2)有名管道也可以從程序里創建,相關API有:
#include
#include
int mkfifo(const char *filename,mode_t mode);
mkfifo函數成功返回0,失敗返回-1并且設置errno。
該函數的第一個參數是一個普通的路徑名,也就是創建后FIFO的名字。第二個參數與打開普通文件的open()函數中的mode參數相同。如果mkfifo的一個參數是一個已經存在路徑名時,會返回EEXIST錯誤,所以一般典型的調用代碼首先會檢查是否返回該錯誤,如果確實返回該錯誤,那么只要調用打開FIFO的函數open就可以了。
3.FIFO的open打開規則
O_RDONLY、O_WRONLY和O_NONBLOCK標志共有四種合法的組合方式:
flags=O_RDONLY:open將會調用阻塞,除非有另外一個進程以寫的方式打開同一個FIFO,否則一直等待。
flags=O_WRONLY:open將會調用阻塞,除非有另外一個進程以讀的方式打開同一個FIFO,否則一直等待。
flags=O_RDONLY|O_NONBLOCK:如果此時沒有其他進程以寫的方式打開FIFO,此時open也會成功返回,此時FIFO被讀打開,而不會返回錯誤。
flags=O_WRONLY|O_NONBLOCK:立即返回,如果此時沒有其他進程以讀的方式打開,open會失敗打開,此時FIFO沒有被打開,返回-1。
總而言之:
● 在一個FIFO上打開一個讀端
● 在一個FIFO上打開一個寫端
4.有名管道的讀寫規則
有名管道的讀寫原則和無名管道的讀寫原則基本一致,主要參考無名管道的讀寫原則即可。
5.示例代碼
使用完成拷貝文件的功能:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define ERR_EXIT(m)
do {
perror(m);
exit(EXIT_FAILURE);
} while(0)
int main(int argc, char *argv[])
{
mkfifo("tp", 0644);
int infd = open("Makefile", O_RDONLY);
if (infd == -1)
ERR_EXIT("open error");
int outfd;
outfd = open("tp", O_WRONLY);
if (outfd == -1)
ERR_EXIT("open error");
char buf[1024];
int n;
while ((n = read(infd, buf, 1024)) > 0)
write(outfd, buf, n);
close(infd);
close(outfd);
return 0;
}
三、無名管道與有名管道的區別與聯系
1.PIPE和FIFO的區別:
對于FIFO和無名管道的編碼區別:
(1)創建并打開一個管道只需調用pipe。創建并打開一個FIFO則需在調用mkfifo后再調用open。
(2)管道在所有進程終都關閉它之后自動消失。FIFO的名字則只有通過調用unlink才文件系統刪除。
FIFO需要額外調用的好處是:FIFO在文件系統中有一個名字,該名字允許某個進程創建個FIFO,與它無親緣關系的另一個進程來打開這個FIFO。對于管道來說,這是不可能的。
系統規定 :如果寫入的數據長度小于等于PIPE_BUF字節,那么或者寫入全部字節,要么一個字節都不寫入。
在非阻塞的write調用情況下,如果FIFO 不能接收所有寫入的數據,將按照下面的規則進行:
(1)請求寫入的數據的長度大于PIPE_BUF字節,調用失敗,數據不能被寫入。
(2)請求寫入的數據的長度小于PIPE_BUF字節,將寫入部分數據,返回實際寫入的字節數,返回值也可能是0。
其中。PIPE_BUF是FIFO的長度,它在頭文件limits.h中被定義。在linux或其他類UNIX系統中,它的值通常是4096字節。注意:PIPE_BUF與FIFO容量是有區別的,PIPE_BUF表示可原子的寫往一個管道或FIFO的大數據量。PIPE_BUF為4096,但是FIFO的容量為65536.
2.PIPE和FIFO的相同點:
(1)雖然管道,特別是有名管道可以很方便地在雙向上打開讀寫,但其內核實現依然是單向的。嚴格遵循先進先出(first in first out),對管道及FIFO的讀總是從開始處返回數據,對它們的寫則把數據添加到末尾。
(2)pipe, fifo都不支持諸如lseek()等文件定位操作。
(3)對于pipe或者fifo,如果在讀端或者寫端打開了多個讀寫端(進程),之間的讀寫是不確定的,需要通過其他的同步機制實現多進程通訊的同步。