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