淺談Linux管道(pipe)

前言

管道(pipe)是Linux系統中重要的進程間通信(IPC)機制,又分爲匿名管道(anonymous pipe)和命名管道(named pipe/FIFO)兩種。今天用腦過度,幾句話簡單談論一下。

匿名管道

當我們在一般語境下提起管道這個詞時,說的就是匿名管道。匿名管道在兩個有親緣關係的進程(即存在父子或兄弟關係的進程)之間創建,本質上是由內核管理的一小塊內存緩衝區,默認大小由系統中的PIPE_BUF常量指定(默認爲一頁,即4096字節)。它的一端連接一個進程的輸出,用於寫入數據;另一端連接另一個進程的輸入,用於讀出數據。管道是半雙工工作的,也就是可以A進程讀B進程寫,也可以B進程讀A進程寫,但是A、B兩個進程不能同時讀寫。

匿名管道在父進程中通過系統調用int pipe(int fd[2])創建。fd[]爲兩個文件描述符的數組,其中fd[0]固定爲管道的讀端,fd[1]固定爲管道的寫端,不能弄反。在父進程fork出子進程之後,使用管道的兩方分別關閉fd[0]和fd[1],就可以操作管道了。如下圖所示,父進程關閉fd[1],從管道讀數據;子進程關閉fd[0],向管道寫數據。

管道的讀寫用最基本的read()/write()系統調用來實現。注意當管道讀取端沒有關閉且管道已滿時,write()會被阻塞;而當管道寫入端沒有關閉且管道爲空時,read()會被阻塞。當然,如果管道的讀寫兩端都被關閉,管道就會消失。

在Linux Shell中,匿名管道可以通過管道符號|創建,例如:cat my.txt | grep lmagic。此時cat是父進程,grep是子進程,cat進程的標準輸出通過管道對接grep進程的標準輸入。

命名管道

匿名管道只能用於有親緣關係的進程之間,而命名管道則可以用於任意進程之間。與匿名管道不同的是,命名管道會借用FIFO文件來實現。FIFO文件在文件系統中創建,有自己的路徑和名稱,但是它僅僅作爲一個管道的標記,其數據仍然由內核管理,存放在內存中。FIFO這個名稱正好也揭示了管道數據先入先出的本質。

我們可以通過系統調用int mkfifo(const char *filename, mode_t mode)來創建一個FIFO文件,而進程通過open()打開FIFO文件的方式決定了它是讀端(O_RDONLY)還是寫端(O_WRONLY)。注意不能以讀寫方式(O_RDWR)打開FIFO文件。特別地,若是設置了非阻塞標誌位O_NONBLOCK的話,如果有一個進程試圖以讀/寫方式打開一個沒有正在被寫/讀的FIFO文件,open()就會立即返回成功,反之則會阻塞。而read()/write()的阻塞方式與匿名管道是完全相同的。

最後,如果FIFO文件的讀端和寫端都關閉,它也不會消失,而是留在文件系統中以便其他進程使用。

管道的底層

匿名管道和命名管道的底層是相同的,就是藉助兩個file結構指向同一個VFS inode來實現,這個inode又指向一個頁面,如下圖所示。

注意兩個file結構的操作f_op是不同的,這樣就可以巧妙地通過普通的文件操作來實現對管道的操作了。

The End

就醬吧,洗洗睡了,民那晚安。

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