Linux 中的管道是什么?管道重定向是如何工作的?
我們?cè)诿钚兄薪?jīng)常會(huì)用到類似 cmd0 | cmd1 | cmd2 的寫法。其實(shí),這是管道重定向(pipe redirection),用于將一個(gè)命令的輸出作為輸入重定向到下一個(gè)命令。
那么,你知道它具體是怎么工作的嗎?今天我們來詳細(xì)了解一下。
注:本文中會(huì)有多個(gè)地方使用 Unix 這個(gè)術(shù)語(而不是Linux),因?yàn)楣艿赖母拍钇鹪从?Unix。
Linux 中的管道:總體思路
以下是關(guān)于“什么是 Unix 管道?”的內(nèi)容:
Unix 管道是一種 IPC(Inter Process Communication,進(jìn)程間通信)機(jī)制,它將一個(gè)程序的輸出轉(zhuǎn)發(fā)到另一程序的輸入。
現(xiàn)在,我們換一種更加專業(yè)且易懂的語言重新解釋一下:
Unix 管道是一種 IPC(Inter Process Communication,進(jìn)程間通信)機(jī)制,它接收程序的標(biāo)準(zhǔn)輸出(stdout),并通過緩沖區(qū)將其轉(zhuǎn)發(fā)給另一個(gè)程序的標(biāo)準(zhǔn)輸入(stdin)。
這樣的描述,大家應(yīng)該能理解了。參考下圖可以了解管道的工作原理:
管道命令的最簡單示例之一是將一些命令輸出傳遞給 grep 命令以搜索特定字符串。
比如,我們可以搜索名稱包含txt的文件,如下所示:
管道將標(biāo)準(zhǔn)輸出重定向到標(biāo)準(zhǔn)輸入,但不是作為命令參數(shù)
有個(gè)非常重要的一點(diǎn)需要注意,管道命令將標(biāo)準(zhǔn)輸出(stdout)傳遞到另一個(gè)命令的標(biāo)準(zhǔn)輸入(stdin),但不是作為參數(shù)。下面我們舉個(gè)例子來說明這一點(diǎn)。
如果我們不帶任何參數(shù)使用 cat 命令,它默認(rèn)會(huì)從 stdin 讀取內(nèi)容??聪旅娴睦樱?/p>
在上面的例子中,沒有帶任何參數(shù)使用了 cat,因此它默認(rèn)會(huì)讀取 stdin。接下來,我寫了一行文字,然后按鍵 Ctrl+d 告訴它我寫完了(Ctrl+d 表示 EOF 或文件結(jié)束)。隨后,cat 命令讀取 stdin,然后把之前我寫的那行文字輸出到了終端中。
現(xiàn)在,看如下命令:
管道右邊的命令并不等于 cat hey。這里,標(biāo)準(zhǔn)輸出(stdout)"hey" 被放在了緩沖區(qū)(buffer),并被傳輸?shù)搅?cat 命令的標(biāo)準(zhǔn)輸入(stdin)。由于沒有命令行參數(shù),所以 cat 默認(rèn)讀取 stdin,而 stdin 中恰好有了內(nèi)容(即“hey”),因此 cat 讀取了這個(gè)內(nèi)容,并將其打印到 stdout。
為了演示這個(gè)區(qū)別,我們可以創(chuàng)建一個(gè)名為 hey 的文件,并在其中添加一些文本。參見下圖:
Linux 中的管道類型
Linux 中有兩種類型的管道:
1)匿名管道,也就是未命名管道;
2)命名管道。
匿名管道
顧名思義,匿名管道就是沒有名稱。當(dāng)你使用 | 符號(hào)時(shí),它們就會(huì)由 Unix shell 動(dòng)態(tài)創(chuàng)建了。
我們通常所說的管道,就是指的匿名管道。它用起來很方便,作為最終用戶,我們不需要跟蹤它的運(yùn)行,shell 自動(dòng)會(huì)處理這一切。
命名管道
這個(gè)稍有不同,命名管道在文件系統(tǒng)中確實(shí)存在。它們像普通文件一樣存在,可以使用下面的命令創(chuàng)建命名管道:
這將創(chuàng)建一個(gè)名為 pipe 的文件,執(zhí)行以下命令:
請(qǐng)注意開頭的“p”,這意味著該文件是一個(gè)管道?,F(xiàn)在我們來使用這個(gè)管道。
如前所述,管道將命令的輸出轉(zhuǎn)發(fā)給另一個(gè)命令的輸入。這就像快遞服務(wù),你把包裹從一個(gè)地址送到另一個(gè)地址。因此,第一步是提供包裹。
我們會(huì)看到 echo 信息沒有打印出來,看起來像是被掛起了。新打開一個(gè)終端,嘗試讀取該文件:
我們看下兩個(gè)終端的輸出結(jié)果,如下圖所示:
驚訝嗎?這兩個(gè)命令同時(shí)完成了執(zhí)行。
這是普通文件和命名管道之間的基本區(qū)別之一。在其他進(jìn)程讀取管道之前,不會(huì)將任何內(nèi)容寫入管道。
那么,為什么要使用命名管道呢?我們來看一下。
命名管道不會(huì)占用磁盤上的任何內(nèi)存。
如果我們執(zhí)行命令 du -s pipe,就會(huì)發(fā)現(xiàn)它不會(huì)占用任何空間。這是因?yàn)槊艿谰拖駨膬?nèi)存緩沖區(qū)讀寫的端點(diǎn)。寫入命名管道的任何內(nèi)容實(shí)際上都存儲(chǔ)在臨時(shí)內(nèi)存緩沖區(qū)中,當(dāng)從另一個(gè)進(jìn)程執(zhí)行讀取操作時(shí),該緩沖區(qū)將被刷新。
節(jié)省 IO
因?yàn)閷懭朊艿酪馕吨鴮?shù)據(jù)存儲(chǔ)到內(nèi)存中的緩沖區(qū)中,因此如果涉及大文件的操作的話,就會(huì)大幅減少磁盤 I/O。
兩個(gè)不同進(jìn)程之間的通信
通過使用命名管道,可以高效地從另一個(gè)進(jìn)程實(shí)時(shí)獲取事件的輸出。因?yàn)樽x和寫同時(shí)發(fā)生,所以沒有等待時(shí)間。
較低層次的管道理解(針對(duì)高級(jí)用戶和開發(fā)人員)
接下來我們更深入的討論一下管道,以及具體的實(shí)現(xiàn)。這些需要對(duì)以下內(nèi)容有基本的了解:
- C 程序工作原理;
- 什么是系統(tǒng)調(diào)用;
- 什么是進(jìn)程;
- 什么是文件描述符。
我們不會(huì)很詳細(xì)的介紹這些概念,只討論與管道相關(guān)的內(nèi)容。對(duì)于大多數(shù)Linux用戶來說,下面的內(nèi)容可以選擇性的閱讀。
為了進(jìn)行編譯,在文章最后提供了一個(gè)示例 makefile。當(dāng)然,這只是用來說明的偽代碼。
看以下程序:
在第16行,我使用 pipe() 函數(shù)創(chuàng)建了一個(gè)匿名管道,傳遞了一個(gè)長度為 2 的帶符號(hào)整數(shù)數(shù)組。
這是因?yàn)楣艿乐皇且粋€(gè)包含兩個(gè)無符號(hào)整數(shù)的數(shù)組,代表兩個(gè)文件描述符。一個(gè)用于寫,一個(gè)用于讀。它們都指向內(nèi)存上的緩沖區(qū)位置,通常為1mb。
這里我將變量命名為fd。fd[0] 是輸入文件描述符,fd[1] 是輸出文件描述符。在該程序中,一個(gè)進(jìn)程將字符串寫入 fd[1] 文件描述符,另一個(gè)進(jìn)程從 fd[0] 文件描述符讀取。
命名管道也一樣,使用命名管道(而不是兩個(gè)文件描述符),你可以從任何一個(gè)進(jìn)程中打開一個(gè)文件,并像其他文件一樣對(duì)其進(jìn)行操作。同時(shí)應(yīng)記住管道的特性。
下面是一個(gè)示例程序,它執(zhí)行與前一個(gè)程序相同的操作,但它創(chuàng)建的不是匿名管道,而是命名管道:
在這里,我使用 mknod 系統(tǒng)調(diào)用來創(chuàng)建命名管道。如你所見,雖然在完成時(shí)刪除了管道,但你可以不使用它,只需要打開并寫入本例中的 npipe 文件,就可以輕松的實(shí)現(xiàn)在不同進(jìn)程之間的通信。
其實(shí)現(xiàn)實(shí)中,我們也不必創(chuàng)建兩個(gè)管道來實(shí)現(xiàn)雙向通信,匿名管道就是這樣的。
以下是一個(gè)簡單的 Makefile 的源代碼示例(只是示例),將其與前面的程序放在同一個(gè)目錄中(分別為 pipe.c 和 fifo.c)。
以上就是本次分享的關(guān)于 Unix 管道的全部內(nèi)容,歡迎討論。