一個小小的 Shell 管道符,內部實現可真不簡單!

管道命令咱們常常使用,將一個指令的輸出導入另外一個指令的輸入,也就是屁股對上嘴,這個原理連編程小學生都知道。可是若是要深刻問進去,一個指令的輸出是如何導入到另外一個指令的輸入,管道又起到什麼角色,估計能回答這個問題的人不足 1%。下面咱們來深刻分析一下管道指令的實現原理,對於下面的這條指令,shell 到底幹了些什麼shell

$ cmd1 | cmd2
複製代碼

首先我用下面這張圖來描述最終形態,而後再一步一步來分解最終形態的造成過程編程

圖片

上圖咱們看到了進程描述符表、管道、進程的父子關係。bash

fork 和 exec

shell 每次執行指令, 須要 fork 出一個子進程來執行,而後將子進程的鏡像替換成目標指令,這又會用到 exec 函數。好比下面這條簡單的指令微信

$ cmd
複製代碼

exec 函數不會改變當前進程的進程號,不會改變進程之間的父子關係。能夠將進程當作一個帶殼的球體,exec 以後,外面的殼不會變,球裏面的東西被徹底替換了。而輸入輸出文件描述符默認在殼上面,這意味着指令 cmd 的輸入輸出繼承了 shell 進程的輸入輸出。函數

$ cmd1 | cmd2
複製代碼

當指令裏面包含一個管道符,意味着須要並行執行兩個指令,這時候 shell 須要 fork 兩次生成兩個子進程,而後分別 exec 換成目標指令。spa

咱們注意到圖裏面還有一個 pipe,它就是負責父子進程通訊的管道。3d

pipe

管道用於父子進程的通訊,在 fork 以前建立 pipe,pipe將成爲 fork 以後父子進程之間的紐帶。pipe 函數會返回兩個描述符(pipe_in, pipe_out),一個用於讀,一個用於寫。code

dup2

下面咱們就須要調整圖中描述符的尖頭,將 cmd1 進程的 stdout 描述符指向管道寫,將 cmd2 進程的 stdin 描述符指向管道讀,這就須要神奇的 dup2(fd1, fd2) 函數,它的做用是將 fd1 描述符關聯 fd2 指向的內核對象,以前 fd1 指向的內核對象引用計數減一,若是減到零就銷燬。注意平時咱們調用 close 方法本質上只是遞減引用計數,同一個內核對象是能夠被多個進程共享的。當引用計數減到零時就會正式關閉。cdn

下面咱們將 dup2 函數的規則應用一下,對兩個進程分別調用 dup2 方法獲得對象

而後再將不須要的描述符關閉掉,就獲得了下面的終極圖,完美!

若是是兩個管道符三個命令以下,就會生成兩個管道

$ cmd1 | cmd2 | cmd3
複製代碼

若是任意一端的進程忽然掛掉了會發生什麼?

假設 cmd1 先掛掉,管道寫被動關閉,cmd2 在讀取管道內容時會遭遇 EOF,而後正常結束。 假設 cmd2 先掛掉,管道讀被動關閉,cmd1 繼續寫管道,這時候進程會收到一個 SIGPIPE 信號,默認動做是進程直接退出。

下一篇咱們將使用酷炫的代碼來實現上面的整個過程,咱們不只要知道其中的原理,並且還須要經過親手實驗來了解其中更多的細枝末節。

閱讀更多深度技術文章,掃一掃上面的二維碼關注微信公衆號「碼洞」

相關文章
相關標籤/搜索