進程間通信之管道

進程間通信之管道


進程間通信

每個進程各自有不同的用戶地址空間,任何一個進程的全局變量在另一個進程中都看不到,所以進程之間要交換數據必須通過內核(操作系統),在內核中開闢一塊緩衝區,進程1把數據從用戶空間拷到內核緩衝區,進程2再從內核緩衝區把數據讀走,內核提供的這種機制稱爲進程間通信。

wKioL1eNaCTjmkiaAAAarN8J6BE509.png-wh_50

本質讓不同的進程可以訪問同一塊系統資源。


實現方式

一、最基本的進程間通信機制--管道(由pipe函數創建)

 

管道的實現機制 

Linux中,管道是一種使用非常頻繁的通信機制。從本質上說,管道也是一種文件,但它又和一般的文件有所不同,管道可以克服使用文件進行通信的兩個問題,具體表現爲:

 

(1)限制管道的大小。實際上,管道是一個固定大小的緩衝區。在Linux中,該緩衝區的大小爲1頁,即4K字節,使得它的大小不像文件那樣不加檢驗地增長。使用單個固定緩衝區也會帶來問題,比如在寫管道時可能變滿,當這種情況發生時,隨後再對管道的write()調用將默認地被阻塞,等待某些數據被讀取,以便騰出足夠的空間供write()調用寫。

(2)讀取進程時也可能工作得比寫進程快。當所有當前進程數據已被讀取時,管道變空。當這種情況發生時,一個隨後的read()調用將默認地被阻塞,等待某些數據被寫入,這解決了read()調用返回文件結束的問題。

注意:從管道讀數據是一次性操作,數據一旦被讀,它就從管道中被拋棄,釋放空間以便寫更多的數據。


1、管道的結構:

Linux中,管道的實現並沒有使用專門的數據結構,而是藉助了文件系統的file結構和VFS的索引節點inode。通過將兩個file結構指向同一個臨時的VFS索引節點,而這個VFS索引節點又指向一個物理頁面而實現的。如圖所示

wKioL1eNaPuQpljkAACa0Cx8ULs732.png-wh_50

上圖中兩個 file 數據結構,但它們定義文件操作例程地址是不同的,其中一個是向管道中寫入數據的例程地址,而另一個是從管道中讀出數據的例程地址。這樣,用戶程序的系統調用仍然是通常的文件操作,而內核卻利用這種抽象機制實現了管道這一特殊操作。


2、管道的讀寫

管道實現的源代碼在fs/pipe.c中,在pipe.c中有很多函數,其中有兩個函數比較重要,即管道讀函數pipe_read()和管道寫函數pipe_wrtie()。管道寫函數通過將字節複製到VFS索引節點指向的物理內存而寫入數據,而管道讀函數則通過複製物理內存中的字節而讀出數據。當然,內核必須利用一定的機制同步對管道的訪問,爲此,內核使用了鎖、等待隊列和信號。

當寫進程向管道中寫入時,它利用標準的庫函數write(),系統根據庫函數傳遞的文件描述符,可找到該文件的file結構。file結構中指定了用來進行寫操作的函數(即寫入函數)地址,於是,內核調用該函數完成寫操作。寫入函數在向內存中寫入數據之前,必須首先檢查VFS索引節點中的信息,同時滿足如下條件時,才能進行實際的內存複製工作:

· 內存中有足夠的空間可容納所有要寫入的數據;

· 內存沒有被讀程序鎖定。

如果同時滿足上述條件,寫入函數首先鎖定內存,然後從寫進程的地址空間中複製數據到內存。否則,寫入進程就休眠在VFS索引節點的等待隊列中,接下來,內核將調用調度程序,而調度程序會選擇其他進程運行。寫入進程實際處於可中斷的等待狀態,當內存中有足夠的空間可以容納寫入數據,或內存被解鎖時,讀取進程會喚醒寫入進程,這時,寫入進程將接收到信號。當數據寫入內存之後,內存被解鎖,而所有休眠在索引節點的讀取進程會被喚醒。

管道的讀取過程和寫入過程類似。但是,進程可以在沒有數據或內存被鎖定時立即返回錯誤信息,而不是阻塞該進程,這依賴於文件或管道的打開模式。反之,進程可以休眠在索引節點的等待隊列中等待寫入進程寫入數據。當所有的進程完成了管道操作之後,管道的索引節點被丟棄,而共享數據頁也被釋放。

因此管道的實現涉及很多文件的操作。

獲取Linux 內存頁(基頁)大小的命令:getconf  PAGE_SIZE ,一般的輸出是4096,即 4KB

wKioL1eNaZWQY6FhAAAK6kiK7UE649.png-wh_50


    管道創建後如何實現兩個進程間的通信(比如如下實現步驟)

 實現步驟:

 1、父進程調用pipe開闢管道,得到兩個文件描述符指向管道的兩端。如下圖:

wKioL1eNahygULrdAABpcjqOLpE626.png-wh_50


 2、父進程調用fork創建子進程,那麼子進程也有兩個文件描述符指向同一管道。如下圖:

wKiom1eNaleBkZMtAADDAe_jXIA979.png-wh_50

 3、父進程關閉管道讀端,子進程關閉管道寫端。父進程可以往管道里寫,子進程可以從管道里讀,管道是用環形隊列實現的,數據從寫端流入從讀端流出,這樣就實現了進程間通信。如下圖:

wKioL1eNatSzInNKAAC0SEdRYuU953.png-wh_50

演示代碼:

wKioL1eNaybBkKd-AABnE9no6CQ731.png-wh_50

wKiom1eNazrigT1sAAA0MWPz1Fg571.png-wh_50


   管道的特點:

    1、管道只允許具有血緣關係的進程間通信,如父子進程間的通信。

    2、管道只允許單向通信。

    3、依賴文件系統

    4、面向字節流

    5、管道隨進程,進程在管道在,進程消失管道對應的端口也關閉,兩個進程都消失管道也消失。


管道的容量

測試代碼如下圖:

wKioL1eNbBPBYiMHAABmwH98fsA771.png-wh_50

wKiom1eNbBOj_nkuAAA__WLhC2Q980.png-wh_50

    運行結果如下:

wKioL1eNbGHgrwN4AAAGuUswnQ8478.png-wh_50

    由運行結果可得,管道容量約爲64KB


注意管道以下的四種特殊情況:


    1、寫端關閉,讀端繼續,當把管道中剩餘的數據讀完,再次read將返回0,就像讀到文件末尾一樣。

wKiom1eNbTrABQfAAABaRyV2pzc696.png-wh_50

wKiom1eNbTujBq3lAABTnqG55rk731.png-wh_50

2、寫端不關,且寫端不寫,讀端繼續,當管道中剩餘的數據都被讀取後,再次read將被阻塞,直到管道中有數據可讀了纔讀取數據並返回。

wKioL1eNbsLAMftbAAA4GjU8UwE554.png-wh_50

3、讀端關閉,進程向管道的寫端write,那麼進程會收到信號SIGPIPE,導致進程異常終止。

wKiom1eNb1KQU9S9AABQx--hVSU271.png-wh_50


4、讀端不關且讀端不讀,寫端繼續,管道被寫滿時再次write會阻塞,直到管道中有空位置了才寫入數據並返回。

wKioL1eNc67Srr9CAABZLEc0RUw750.png-wh_50


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