linux中的匿名管道和命名管道


一、管道的概念
      管道是一種兩個進程間進行單向通信的機制。 管道是一種最基本的IPC機制,作用於有血緣關系的進程之間,完成數據傳遞。調用pipe系統函數即可創建一個管道。管道又分為匿名管道和命名管道。管道有如下特質:

(1)其本質是一個偽文件(實為內核緩沖區)

(2) 由兩個文件描述符引用,一個表示讀端,一個表示寫端。

(3) 規定數據從管道的寫端流入管道,從讀端流出。

匿名管道的特征:

(1)只能進行單向通信;

(2)只適用於有血緣關系之間的進程;

(3)自帶同步基質;

(4)在進行通信時面向字節流服務;

(5)生命進程隨周期。

     管道的原理: 管道實為內核使用環形隊列機制,借助內核緩沖區(4k)實現。

     因為管道傳遞數據的單向性,管道又稱為半雙工管道。管道的這一特點決定了器使用的局限性。

     管道的局限性:

① 數據自己讀不能自己寫。

② 數據一旦被讀走,便不在管道中存在,不可反復讀取。

③ 由於管道采用半雙工通信方式。因此,數據只能在一個方向上流動。

④ 只能在有公共祖先的進程間使用管道。

常見的通信方式有,單工通信、半雙工通信、全雙工通信。


pipe函數
創建管道

    int pipe(int pipefd[2]); 成功:0;失敗:-1,設置errno

函數調用成功返回r/w兩個文件描述符。無需open,但需手動close。規定:fd[0] → r; fd[1] → w,就像0對應標准輸入,1對應標准輸出一樣。向管道文件讀寫數據其實是在讀寫內核緩沖區。

管道創建成功以后,創建該管道的進程(父進程)同時掌握着管道的讀端和寫端。如何實現父子進程間通信呢?通常可以采用如下步驟:

 

 

1. 父進程調用pipe函數創建管道,得到兩個文件描述符fd[0]、fd[1]指向管道的讀端和寫端。

2. 父進程調用fork創建子進程,那么子進程也有兩個文件描述符指向同一管道。

3. 父進程關閉管道讀端,子進程關閉管道寫端。父進程可以向管道中寫入數據,子進程將管道中的數據讀出。由於管道是利用環形隊列實現的,數據從寫端流入管道,從讀端流出,這樣就實現了進程間通信。


  使用管道需要注意以下4種特殊情況(假設都是阻塞I/O操作,沒有設置O_NONBLOCK標志):

1. 如果所有指向管道寫端的文件描述符都關閉了(管道寫端引用計數為0),而仍然有進程從管道的讀端讀數據,那么管道中剩余的數據都被讀取后,再次read會返回0,就像讀到文件末尾一樣。

2. 如果有指向管道寫端的文件描述符沒關閉(管道寫端引用計數大於0),而持有管道寫端的進程也沒有向管道中寫數據,這時有進程從管道讀端讀數據,那么管道中剩余的數據都被讀取后,再次read會阻塞,直到管道中有數據可讀了才讀取數據並返回。

3. 如果所有指向管道讀端的文件描述符都關閉了(管道讀端引用計數為0),這時有進程向管道的寫端write,那么該進程會收到信號SIGPIPE,通常會導致進程異常終止。當然也可以對SIGPIPE信號實施捕捉,不終止進程。具體方法信號章節詳細介紹。

4. 如果有指向管道讀端的文件描述符沒關閉(管道讀端引用計數大於0),而持有管道讀端的進程也沒有從管道中讀數據,這時有進程向管道寫端寫數據,那么在管道被寫滿時再次write會阻塞,直到管道中有空位置了才寫入數據並返回。

總結:

     ① 讀管道:

1. 管道中有數據,read返回實際讀到的字節數。

2. 管道中無數據:

(1) 管道寫端被全部關閉,read返回0 (好像讀到文件結尾)

(2) 寫端沒有全部被關閉,read阻塞等待(不久的將來可能有數據遞達,此時會讓出cpu)

     ② 寫管道:

1. 管道讀端全部被關閉, 進程異常終止(也可使用捕捉SIGPIPE信號,使進程不終止)

2. 管道讀端沒有全部關閉:

(1) 管道已滿,write阻塞。

(2) 管道未滿,write將數據寫入,並返回實際寫入的字節數。

 

 

命名管道(FIFO):

     FIFO不同於管道之處在於它提供一個路徑名與之關聯,以FIFO的文件形式存儲於文件系統中。命名管道是一個設備文件,因此,即使進程與創建FIFO的進程不存在親緣關系,只要可以訪問該路徑,就能夠通過FIFO相互通信。值得注意的是,FIFO(first input first output)總是按照先進先出的原則工作,第一個被寫入的數據將首先從管道中讀出。


命名管道是通過網絡來完成進程之間的通信的,命名管道依賴於底層網絡接口,其中包括有 DNS 服務,TCP/IP 協

議 等等機制,但是其屏蔽了底層的網絡協議細節。

  命名管道的創建與讀寫

Linux下有兩種⽅式創建命名管道。一是在Shell下交互地建立一個命名管道,一是在程序中使用系統函數建立命名管

道。Shell方式下可使用mknod或mkfifo命令,下面命令使用mknod創建了一個命名管道:

mknod namedpipe

創建命名管道的系統函數有兩個:mknod和mkfifo。兩個函數均定義在頭文件sys/stat.h,

函數原型如下

#include <sys/types.h>
#include <sys/stat.h>
int mknod(const char *path,mode_t mod,dev_t dev);
int mkfifo(const char *path,mode_t mode);


函數mknod參數中path為創建的命名管道的全路徑名:mod為創建的命名管道的模式,指明其存取權限;dev為設

備值,該值取決於文件創建的種類,它只在創建設備文件時才會用到。這兩個函數調用成功都返回0,失敗都返回

-1。 

 

我們可以使用兩下函數之一來創建一個命名管道,他們的原型如下:
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *filename, mode_t mode);
int mknod(const char *filename, mode_t mode | S_IFIFO, (dev_t)0);


這兩個函數都能創建一個FIFO文件,注意是創建一個真實存在於文件系統中的文件,filename指定了文件名,

而mode則指定了文件的讀寫權限。mknod是比較老的函數,而使用mkfifo函數更加簡單和規范,所以建議在可能的

情況下,盡量使用mkfifo而不是mknod。


mkfifo函數的作用是在文件系統中創建一個文件,該文件用於提供FIFO功能,即命名管道。前邊講的那些管道都沒有

名字,因此它們被稱為匿名管道,或簡稱管道。對文件系統來說,匿名管道是不可見的,它的作用僅限於在父進程和

子進程兩個進程間進行通信。而命名管道是一個可見的文件,因此,它可以用於任何兩個進程之間的通信,不管這兩

個進程是不是父子進程,也不管這兩個進程之間有沒有關系。 

    下面就用一個例子程序來說明一下,兩個進程如何通過FIFO實現通信吧。這里有兩個源文件,一個fifo write.c,它在需要時創建管道,然后向管道寫入數據,數據由文件Data.txt提供,大小為10M,內容全是字符‘0’。另一個源文件為fifo read.c,它從FIFO中讀取數據,並把讀到的數據保存到另一個文件DataFormFIFO.txt中。為了讓程序更加簡潔,忽略了有些函數調用是否成功的檢查。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM