在linux中,管道是一種通信機(jī)制,是把一個(gè)程序的輸出直接連接到另一個(gè)程序的輸入。從本質(zhì)上說,管道也是一種文件,但它又和一般的文件有所不同,管道可以克服使用文件進(jìn)行通信的兩個(gè)問題,具體表現(xiàn)為:限制管道的大小、讀取進(jìn)程可能工作得比寫進(jìn)程快。
本教程操作環(huán)境:linux7.3系統(tǒng)、Dell G3電腦。
管道是Linux中很重要的一種通信方式,是把一個(gè)程序的輸出直接連接到另一個(gè)程序的輸入。常說的管道多是指無名管道,無名管道只能用于具有親緣關(guān)系的進(jìn)程之間,這是它與有名管道的最大區(qū)別。
有名管道叫named pipe或者FIFO(先進(jìn)先出),可以用函數(shù)mkfifo()創(chuàng)建。
Linux管道的實(shí)現(xiàn)機(jī)制
在Linux中,管道是一種使用非常頻繁的通信機(jī)制。從本質(zhì)上說,管道也是一種文件,但它又和一般的文件有所不同,管道可以克服使用文件進(jìn)行通信的兩個(gè)問題,具體表現(xiàn)為:
-
限制管道的大小。實(shí)際上,管道是一個(gè)固定大小的緩沖區(qū)。在Linux中,該緩沖區(qū)的大小為1頁(yè),即4K字節(jié),使得它的大小不象文件那樣不加檢驗(yàn)地增長(zhǎng)。使用單個(gè)固定緩沖區(qū)也會(huì)帶來問題,比如在寫管道時(shí)可能變滿,當(dāng)這種情況發(fā)生時(shí),隨后對(duì)管道的write()調(diào)用將默認(rèn)地被阻塞,等待某些數(shù)據(jù)被讀取,以便騰出足夠的空間供write()調(diào)用寫。
-
讀取進(jìn)程也可能工作得比寫進(jìn)程快。當(dāng)所有當(dāng)前進(jìn)程數(shù)據(jù)已被讀取時(shí),管道變空。當(dāng)這種情況發(fā)生時(shí),一個(gè)隨后的read()調(diào)用將默認(rèn)地被阻塞,等待某些數(shù)據(jù)被寫入,這解決了read()調(diào)用返回文件結(jié)束的問題。
注意:從管道讀數(shù)據(jù)是一次性操作,數(shù)據(jù)一旦被讀,它就從管道中被拋棄,釋放空間以便寫更多的數(shù)據(jù)。
1. 管道的結(jié)構(gòu)
?????在?Linux?中,管道的實(shí)現(xiàn)并沒有使用專門的數(shù)據(jù)結(jié)構(gòu),而是借助了文件系統(tǒng)的file結(jié)構(gòu)和VFS的索引節(jié)點(diǎn)inode。通過將兩個(gè)?file?結(jié)構(gòu)指向同一個(gè)臨時(shí)的?VFS?索引節(jié)點(diǎn),而這個(gè)?VFS?索引節(jié)點(diǎn)又指向一個(gè)物理頁(yè)面而實(shí)現(xiàn)的。
2.管道的讀寫
??????管道實(shí)現(xiàn)的源代碼在fs/pipe.c中,在pipe.c中有很多函數(shù),其中有兩個(gè)函數(shù)比較重要,即管道讀函數(shù)pipe_read()和管道寫函數(shù)pipe_wrtie()。管道寫函數(shù)通過將字節(jié)復(fù)制到?VFS?索引節(jié)點(diǎn)指向的物理內(nèi)存而寫入數(shù)據(jù),而管道讀函數(shù)則通過復(fù)制物理內(nèi)存中的字節(jié)而讀出數(shù)據(jù)。當(dāng)然,內(nèi)核必須利用一定的機(jī)制同步對(duì)管道的訪問,為此,內(nèi)核使用了鎖、等待隊(duì)列和信號(hào)。
?????當(dāng)寫進(jìn)程向管道中寫入時(shí),它利用標(biāo)準(zhǔn)的庫(kù)函數(shù)write(),系統(tǒng)根據(jù)庫(kù)函數(shù)傳遞的文件描述符,可找到該文件的?file?結(jié)構(gòu)。file?結(jié)構(gòu)中指定了用來進(jìn)行寫操作的函數(shù)(即寫入函數(shù))地址,于是,內(nèi)核調(diào)用該函數(shù)完成寫操作。寫入函數(shù)在向內(nèi)存中寫入數(shù)據(jù)之前,必須首先檢查?VFS?索引節(jié)點(diǎn)中的信息,同時(shí)滿足如下條件時(shí),才能進(jìn)行實(shí)際的內(nèi)存復(fù)制工作:
-
內(nèi)存中有足夠的空間可容納所有要寫入的數(shù)據(jù);
-
內(nèi)存沒有被讀程序鎖定。
如果同時(shí)滿足上述條件,寫入函數(shù)首先鎖定內(nèi)存,然后從寫進(jìn)程的地址空間中復(fù)制數(shù)據(jù)到內(nèi)存。否則,寫入進(jìn)程就休眠在?VFS?索 引節(jié)點(diǎn)的等待隊(duì)列中,接下來,內(nèi)核將調(diào)用調(diào)度程序,而調(diào)度程序會(huì)選擇其他進(jìn)程運(yùn)行。寫入進(jìn)程實(shí)際處于可中斷的等待狀態(tài),當(dāng)內(nèi)存中有足夠的空間可以容納寫入 數(shù)據(jù),或內(nèi)存被解鎖時(shí),讀取進(jìn)程會(huì)喚醒寫入進(jìn)程,這時(shí),寫入進(jìn)程將接收到信號(hào)。當(dāng)數(shù)據(jù)寫入內(nèi)存之后,內(nèi)存被解鎖,而所有休眠在索引節(jié)點(diǎn)的讀取進(jìn)程會(huì)被喚 醒。
管 道的讀取過程和寫入過程類似。但是,進(jìn)程可以在沒有數(shù)據(jù)或內(nèi)存被鎖定時(shí)立即返回錯(cuò)誤信息,而不是阻塞該進(jìn)程,這依賴于文件或管道的打開模式。反之,進(jìn)程可 以休眠在索引節(jié)點(diǎn)的等待隊(duì)列中等待寫入進(jìn)程寫入數(shù)據(jù)。當(dāng)所有的進(jìn)程完成了管道操作之后,管道的索引節(jié)點(diǎn)被丟棄,而共享數(shù)據(jù)頁(yè)也被釋放。
因?yàn)楣艿赖膶?shí)現(xiàn)涉及很多文件的操作,因此,當(dāng)讀者學(xué)完有關(guān)文件系統(tǒng)的內(nèi)容后來讀pipe.c中的代碼,你會(huì)覺得并不難理解。
Linux 管道的創(chuàng)建和使用都要簡(jiǎn)單一些,唯一的原因是它需要更少的參數(shù)。實(shí)現(xiàn)與 Windows 相同的管道創(chuàng)建目標(biāo),Linux 和 UNIX 使用下面的代碼片段:
創(chuàng)建 Linux 命名管道
int?fd1[2]; if(pipe(fd1)) { ?printf("pipe()?FAILED:?errno=%d",errno); ?return?1; }
Linux 管道對(duì)阻塞之前一次寫操作的大小有限制。 專門為每個(gè)管道所使用的內(nèi)核級(jí)緩沖區(qū)確切為 4096 字節(jié)。 除非閱讀器清空管道,否則一次超過 4K 的寫操作將被阻塞。 實(shí)際上這算不上什么限制,因?yàn)樽x和寫操作是在不同的線程中實(shí)現(xiàn)的。
Linux 還支持命名管道。對(duì)這些數(shù)字的早期評(píng)論員建議我,為公平起見,應(yīng)該比較 Linux 的命名管道和 Windows 的命名管道。我寫了另一個(gè)在 Linux 上使用命名管道的程序。我發(fā)現(xiàn)對(duì)于 Linux 上命名的和未命名的管道,結(jié)果是沒有區(qū)別。
Linux 管道比 Windows 2000 命名管道快很多,而 Windows 2000 命名管道比 Windows XP 命名管道快得多。
例子:
#include<stdio.h> #include<unistd.h> int?main() { int?n,fd[2];?????????????????????????//?這里的fd是文件描述符的數(shù)組,用于創(chuàng)建管道做準(zhǔn)備的 pid_t?pid; char?line[100]; if(pipe(fd)0){???????????????????//這里是父進(jìn)程,先關(guān)閉管道的讀出端,然后在管道的寫端寫入“hello?world" ????close(fd[0]); ????write(fd[1],"hello?word/n",11); } else{ ????close(fd[1]);?????????????????//這里是子進(jìn)程,先關(guān)閉管道的寫入端,然后在管道的讀出端讀出數(shù)據(jù) ???n=?read(fd[0],line,100); ????write(STDOUT_FILENO,line,n); } exit(0); }</unistd.h></stdio.h>
推薦學(xué)習(xí):Linux視頻教程