摘要:管道是進(jìn)程間通信的一個(gè)基本途徑,進(jìn)程可以通過管道進(jìn)行信息傳輸;管道就像它的名字一樣,數(shù)據(jù)從它的一頭流向另一頭(當(dāng)然有的情況也可以從另一頭流向這一頭)。本文詳細(xì)而全部地介紹了UNIX/Linux或POSIX下的管道技術(shù),并同時(shí)討論管道技術(shù)的一些典型應(yīng)用,使得對(duì)管道有個(gè)基本理解之后,對(duì)管道的應(yīng)用有更深刻的理解。
本站還包含另一篇有關(guān)管道的文章(Linux下進(jìn)程間通信:管道-pipe函數(shù)),這篇文章在網(wǎng)絡(luò)上傳播的很開,它是講述Linux管道的,但管道的基本要素是相通的,所以本文不會(huì)重復(fù)這篇文章的內(nèi)容,本文試圖更深入地討論管道的應(yīng)用。
管道中存在一些基本的使用知識(shí),它在Linux下進(jìn)程間通信:管道-pipe函數(shù)中或多或少都有所討論,不過這里整理出來,以作備忘:
- 管道存在基本的通信限制,它只能用于有親緣關(guān)系的進(jìn)程,同時(shí)管道很多時(shí)候被認(rèn)為是半雙工的,即數(shù)據(jù)的流向是單向的。
- 管道的讀寫操作不是原子的,在讀寫操作的過程中可能被打斷。
- 管道存在于內(nèi)存之中,它有一個(gè)大小為PIPE_BUF的緩沖區(qū),這個(gè)數(shù)值通常是操作系統(tǒng)的一個(gè)頁的大小。
- 向一個(gè)實(shí)管道(沒有讀端的管道)寫數(shù)據(jù)會(huì)產(chǎn)生一個(gè)SIGPIPE信號(hào),應(yīng)用程序應(yīng)該處理這個(gè)信號(hào)。
- 管道只能被兩個(gè)進(jìn)程使用,一個(gè)寫,一個(gè)讀;如何一個(gè)管道有多個(gè)進(jìn)程寫或多個(gè)進(jìn)程讀,之后的讀寫函數(shù)將失敗,并設(shè)置相應(yīng)的errno。
- 管道與標(biāo)準(zhǔn)輸入輸出聯(lián)合使用往往可以寫出具有簡(jiǎn)單易用的多進(jìn)程合作應(yīng)用,典型的應(yīng)用是協(xié)同進(jìn)程(或過濾進(jìn)程)。
popen與pclose函數(shù)
管道的應(yīng)用中,最常見的情形是,一個(gè)進(jìn)程從其它進(jìn)程讀取數(shù)據(jù)或者向其它進(jìn)程寫入數(shù)據(jù);popen就是為了完成這一典型應(yīng)用而產(chǎn)生的。popen會(huì)把數(shù)據(jù)寫入給定程序的標(biāo)準(zhǔn)輸入,或者從給定程序的標(biāo)準(zhǔn)輸出中讀數(shù)據(jù)。pclose用于關(guān)閉popen創(chuàng)建的流。
popen與pclose的原型如下:
#include<stdio.h>
FILE* popen(const char* cmd, const char* mode)
返回:成功 —— 一個(gè)FILE流,失敗 —— NULL
int pclose(FILE* stream)
返回:成功 —— 0, 失敗 —— -1
下面像往常一樣需要分別介紹這兩個(gè)函數(shù)的參數(shù)了,如果只是想簡(jiǎn)單地使用這兩個(gè)函數(shù),其時(shí)很簡(jiǎn)單的,可以與fopen、fclose進(jìn)行類比??紤]一下,fopen需要的兩個(gè)參數(shù),對(duì)應(yīng)于popen的兩個(gè)參數(shù);fclose需要一個(gè)函數(shù),對(duì)應(yīng)于pclose的一個(gè)參數(shù)。
- 參數(shù) cmd
- 參數(shù)cmd是一個(gè)命令,如果你對(duì)shell命令行比較熟悉,那么可以簡(jiǎn)單地理解與shell命令行輸入的命令完全相同。事實(shí)上,popen自己不是親自打開這個(gè)程序,它會(huì)調(diào)用系統(tǒng)內(nèi)的一個(gè)默認(rèn)的shell,然后通過這個(gè)shell去調(diào)用相應(yīng)的程序。通過shell調(diào)用的一個(gè)好處在于,被調(diào)用的程序可以使用shell提供的大量環(huán)境變量與支持,并使得程序的行為與在shell命令行中的行為相同或相似。然而正因?yàn)槿绱?,這個(gè)函數(shù)的行為可以在不同的系統(tǒng)中是不同的,甚至在相同的系統(tǒng)中,而系統(tǒng)shell不同也會(huì)引起這一變化。然而在UNIX系統(tǒng)中存在大量的這樣的使用實(shí)例,一個(gè)兼容shell可能不會(huì)引起多大問題,所以問題也并不是想象中那樣困難。
- 參數(shù) mode
- 這里的mode參數(shù)盡管是字符串形式的,但是它卻只能取兩個(gè)值,一值是"r",另一個(gè)值"w",前者表示讀,后者表示寫。也就是通過popen等到的FILE流只能是只讀或只寫,而不能是讀寫兩用的。這樣設(shè)計(jì)的原因很簡(jiǎn)單,是因?yàn)閜open返回的結(jié)果是FILE*,所以要想其它諸如fread的函數(shù)可以使用這個(gè)FILE*,則要求這個(gè)流必須是只讀或只寫的,如果讀寫的話則要求管道是全雙工的,并且假設(shè)需要打開的程序也假設(shè)這一點(diǎn),這帶來很大的兼容性問題,目前管道典型使用都是半雙工的。
- 參數(shù) stream
- 如同fclose一樣,pclose當(dāng)然需要這樣的一個(gè)結(jié)構(gòu)來關(guān)閉相應(yīng)的流。這里要注意的是,盡管popen返回FILE*,但這個(gè)流不能使用fclose關(guān)閉,原因是兩者的操作可以很不相同,pclose可能需要多做一些清理工作。在APUE中就講到這樣的情況,它給出了一個(gè)popen與pclose的原型實(shí)現(xiàn),上面就存在一個(gè)靜態(tài)數(shù)組,pclose需要清理這個(gè)靜態(tài)數(shù)組。
popen把一個(gè)流與一個(gè)進(jìn)程的標(biāo)準(zhǔn)輸入輸出相關(guān)聯(lián),如此可以實(shí)現(xiàn)很多應(yīng)用,過濾器就可以通過這種方式來實(shí)現(xiàn)。
協(xié)同進(jìn)程是一個(gè)進(jìn)程,與它相關(guān)的還存在另外一個(gè)進(jìn)程,這個(gè)進(jìn)程向協(xié)同進(jìn)程的標(biāo)準(zhǔn)輸入寫數(shù)據(jù),并從協(xié)同進(jìn)程的標(biāo)準(zhǔn)輸出讀數(shù)據(jù),這樣感覺協(xié)同進(jìn)程好像活在這個(gè)進(jìn)程里面,而協(xié)同進(jìn)程只是為了幫助這個(gè)進(jìn)程而存活的。協(xié)同進(jìn)程是兩個(gè)進(jìn)程合作的一種方式,這種方式被廣泛應(yīng)用,并被這些應(yīng)用證明具有廣泛的適應(yīng)性,以至于在有些腳本語言中在語言級(jí)支持協(xié)同進(jìn)程的概念,比如說Lua中的協(xié)程序。
協(xié)同進(jìn)程這種應(yīng)用方式可以通過雙管道來實(shí)現(xiàn),在父進(jìn)程中創(chuàng)建兩個(gè)管道,然后在創(chuàng)建子進(jìn)程,并在子進(jìn)程或父進(jìn)程或兄弟進(jìn)程中選擇一個(gè)進(jìn)程當(dāng)作協(xié)同進(jìn)程,實(shí)現(xiàn)協(xié)同進(jìn)程這種使用模式。