你所不知道的linux匿名管道知識

相信很多在linux平臺工作的童鞋, 都很熟悉管道符 '|', 通過它, 我們能夠很靈活的將幾種不同的命令協同起來完成一件任務。就好像下面的命令:



不過這次咱們不來說這些用法, 而是來探討一些更加有意思的, 那就是 管道兩邊的數據流"實時性" 和 管道使用的小提示。

其實我們在利用管道的時候, 可能會不經意的去想, 我前一個命令的輸出, 是全部處理完再通過管道傳給第二個命令, 還是一邊處理一邊輸出呢? 可能在大家是試驗中或者工作經驗中, 應該是左邊的命令全部處理完再一次×××給右邊的命令進行處理, 不光是大家, 我在最初接觸管道時, 也曾有這麼一個誤會, 因爲我們通過現象看到的就是這樣。

但其實只要有簡單瞭解過管道這工具, 應該都不難得出解釋:

管道是兩邊是同時進行, 也就是說, 左邊的命令輸出到管道, 管道的右邊將馬上進行處理。

管道的定義

管道是由內核管理的一個緩衝區,相當於我們放入內存中的一個紙條。管道的一端連接一個進程的輸出。這個進程會向管道中放入信息。管道的另一端連接一個進程的輸入,這個進程取出被放入管道的信息。一個緩衝區不需要很大,它被設計成爲環形的數據結構,以便管道可以被循環利用。當管道中沒有信息的話,從管道中讀取的進程會等待,直到另一端的進程放入信息。當管道被放滿信息的時候,嘗試放入信息的進程會堵塞,直到另一端的進程取出信息。當兩個進程都終結的時候,管道也自動消失。



管道工作流程圖

通過上面的解釋可以看到, 假設 COMMAND1 | COMMAND2, 那麼COMMAND1的標準輸出, 將會被綁定到管道的寫端, 而COMMAND2的標準輸入將會綁定到管道的讀端, 所以當COMMAND1一有輸出, 將會馬上通過管道傳給COMMAND2, 我們先來做個實驗驗證下:





在上面的命令, 我們可以猜測下輸出結果: 究竟是 睡眠6秒之後, 輸出"1111222", 還是輸出 "1111" 睡眠3秒, 再輸出 "2222", 然後再睡眠3秒, 再輸出"1111" 呢? 答案就是: 都不是! what! 這不可能, 大家可以嘗試下, 我們會看到終端沒反應了, 爲什麼呢? 這就要涉及到文件IO的緩衝方式了,這裏不多說, 簡單提一下文件IO的三種緩衝方式:

  • 全緩衝: 直到緩衝區被填滿,才調用系統I/O函數, (一般是針對文件)

  • 行緩衝: 遇到換行符就輸出(標準輸出)

  • 無緩衝: 沒有緩衝區,數據會立即讀入或者輸出到外存文件和設備上(標準錯誤

因爲python是默認採用帶緩衝的fputs, 又因爲標準輸出被改寫到管道, 所以將會採取全緩衝的方式(shell 命令具體要看實現, 因爲有些是用不帶緩衝write實現,如果不帶緩衝區,會直接寫入管道), 所以將會採取全緩衝的方式, 也就是說, 直到緩衝區被填滿, 或者手動顯示調用flush刷入,才能看到輸出。那我們可以將代碼改寫成下面兩種方式吧



輸出結果:



在這裏我們已經能夠得出結果, 如果像我們以前所想的那樣, 要等到COMMAND1全部執行完才一次性輸出給COMMAND2, 那麼結果應該是無限堵塞。因爲我的程序一直沒有執行完。這樣應該是不符合老前輩們設計初衷的, 因爲這樣可能會導致管道越來越大。然而管道也是有大小的~ 具體可以去看posix標準, 所以我們得出結論是: 只要COMMAND1的輸出寫入管道的寫端(不管是緩衝區滿還是手動flush), COMMAND2都將立刻得到數據並且馬上處理。

那麼 管道兩邊的數據流"實時性" 討論到就先暫告一段落, 接下來將在這個基礎上繼續討論: 管道使用的小提示。

在開始討論前, 我想先引入一個專業術語, 也是我們偶爾會遇到的, 那就是: SIGPIPE。

或者是一個更加具體的描述: broken pipe (管道破裂)

上面的專業術語都是跟管道讀寫規則息息相關的, 那咱們來看下 管道的讀寫規則吧:

1.當沒有數據可讀時

  • O_NONBLOCK (未設置):read調用阻塞,即進程暫停執行,一直等到有數據來到爲止。

  • O_NONBLOCK ( 設置 ) :read調用返回-1,errno值爲EAGAIN。

2.當管道滿的時候

  • O_NONBLOCK (未設置): write調用阻塞,直到有進程讀走數據

  • O_NONBLOCK ( 設置 ):調用返回-1,errno值爲EAGAIN

3.如果所有管道寫端對應的文件描述符被關閉,則read返回0

4.如果所有管道讀端對應的文件描述符被關閉,則write操作會產生信號SIGPIPE

5.當要寫入的數據量不大於PIPE_BUF時,linux將保證寫入的原子性。

6.當要寫入的數據量大於PIPE_BUF時,linux將不再保證寫入的原子性。


在上面我們可以看到, 如果我們收到SIGPIPE信號, 那麼一般情況就是讀端被關閉, 但是寫端卻依舊嘗試寫入

咱們來重現下 SIGPIPE



這次執行命令需要考驗手速了, 因爲我們要趕在py醒過來之前, 將讀端進程殺掉



輸出結果



從上圖我們可以驗證兩個點: 

  1. 當我們殺掉讀端時, 寫端會收到SIGPIPE而默認退出, 管道結束

  2. 當我們殺掉讀端時, 寫端的程序並不會馬上收到SIGPIPE, 相反的, 只有真正寫入管道寫端時纔會觸發這個錯誤

如果寫入一個 讀端已經關閉的管道, 將會收到一個 SIGPIPE, 那讀一個寫端已經關閉的管道又會這樣呢?





在上面也已經證明了上文提到的讀寫規則: 如果所有管道寫端對應的文件描述符被關閉,將產生EOF結束標誌,read返回0, 程序退出

總結

通過上面的理論和實驗, 我們知道在使用管道時, 兩邊命令的數據傳輸過程, 以及對管道讀寫規則有了初步的認識, 希望我們以後在工作時, 再接觸管道時, 能夠更加有把握的去利用這一強大的工具。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章