Linux 管道

洗菜劍心發表於2018-04-19

什麼是管道

管道是最基本的程式間通訊,它是半雙工的通訊(資料流是定向的)。它建立於記憶體緩衝區中,用於連線一個寫程式一個讀程式,大小剛好為一個頁(4KB)。一個程式把資料寫入管道,由核心定向的流入另一個讀程式。(多程式通訊中要用鎖控制,防止一個程式在讀取管道里資料沒讀完就被其他程式讀走之類的問題)

管道概念

  1. 管道用的是利用檔案系統file結構,和VFS索引節點inode來完成的結構。通過兩個file檔案指向一個l臨時VFS節點,VFS再指向一個物理頁面。
  2. 當管道資料被一個讀程式讀走後,資料就被刪除了,其他讀程式無法繼續讀取被讀走的資料
  3. 寫程式是在管道尾部寫入,讀程式是在管道頭部讀取。
  4. 當管道空了時候,read()函式會阻塞,當管道滿了時候,write()函式會阻塞,也可以通過0_ONBLOCK調成非阻塞
  5. 如果管道對應的讀端關閉了檔案描述符,則write產生一個訊號SIGPIPE
  6. 如果寫端關閉,則read返回0

管道的分類

(1)有名管道
(2)無名管道

無名管道

無名管道故名思義,無名即是無檔案標識,在系統檔案中不可見。那麼問題來了讀程式和寫程式如何指向同一塊記憶體緩衝區空間呢?這裡就是需要用父子程式這種關係來建立一個無名管道。


建立無名管道

先用圖形來介紹下建立的流程
第一步是建立無名管道
int fd[2];
pipe(int fd[2];
介紹下pipe這個函式
int pipe(int fd[2])
標頭檔案是:#include<unistd.h>
傳入的引數是int fd[2](是一個儲存兩個int的一維陣列)。當實參傳入是fd[2]時候2變的無意義,它退化成一個指標。
fd[0]是指向管道的讀端,fd[1]是指向管道寫端的。這個是不可反轉的。
                                                                              

第二步就是fork()一下程式A,產生子程式B,如右邊圖片所示。

這個時候程式A fork()產生了一個子程式B,因為fork時候檔案描述符表也會被複制到子程式中。
那麼就會出現有兩個讀端和兩個寫端的指向管道。就解決了我們一開始提出的問題無名管道無檔案標識,如何指向同一塊的記憶體快取器空間呢。
接下來就是要確定哪一個程式作為讀端和寫端,這裡我們以程式A為寫端為例,我們這個時候需要修改下程式A對管道操作的許可權,就是在fork立即關閉讀端close(fd[0])。我們要把程式B變成讀端,就要先close(fd[1])。如果在程式結束最後才關閉的話,雖然程式執行可能會成功,但是程式對管道的許可權是不對的,這裡安全性不夠。
到這一步基本也算完成了無名管道的建立了。
剩下的都是讀寫的操作


我們以一個小題目為例子
我們將一個程式A把標準輸入的資料,通過管道定向傳給程式B。程式B把讀取到的資料大寫變成小寫,傳回去給程式B。

1)建立好兩個管道,fork一個子程式。



2)子程式把讀到的資料,處理好,通過管道2傳回給程式A


3)父程式就是就是標準輸入buff,寫入管道1中,通過管道2把處理好的資料讀出來。


程式碼算是完成了。

有名管道

有名管道是為了解決無名管道所面對的問題,無名管道的限制是必須是有親緣關係的程式。
有名管道的限制是程式必須有訪問管道的許可權。
有名管道就是先創一個管道檔案,這個管道檔案會在目錄樹一個檔案標識,然後通過open()函式進行操作,然後程式間通訊。如果只有一個程式開啟管道,則該程式會阻塞在open處,直到另一方程式也open了,那麼就open成功,且才會開闢記憶體緩衝區的空間。當雙方通訊完成,關閉了檔案描述符,之後,臨時VFS索引節點丟棄,然後系統釋放記憶體緩衝區那麼部分被開闢的空間。


有名管道建立
1.直接在shell下建立,通過mkfifo命令來建立
mkfifo FIFO
也可以通過在函式裡建立
int mkfifo(const char *pathname,mode_t mode);
當程式碼執行後,如果建立管道是在函式裡完成的,在執行該程式的第一次建立完成,就會一直存在目錄中,除非刪除該檔案,如果該管道檔案存在那麼建立就會失敗。

一個程式建立了管道,另個程式就沒必要繼續管道了,也就是管道只要有一個程式建立就夠了,另個程式只要開啟管道。
建立好的管道名會存在磁碟裡,但是實際的系統的呼叫還是由mknod()來完成的。

這樣就建立好了管道檔案
2然後通過open函式來開啟和設定操作管道檔案許可權
3.在和無名管道操作一樣進行讀寫操作,完成資料的傳輸

我們演示和無名管道一樣的使用說明













相關文章