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