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中。為了讓程式更加簡潔,忽略了有些函式呼叫是否成功的檢查。