1、管道的概念
管道是一種兩個進程間進行單向通訊的機制。 管道是一種最基本的IPC機制,做用於有血緣關係的進程之間,完成數據傳遞。調用pipe系統函數便可建立一個管道。管道又分爲匿名管道和命名管道。管道有以下特質:網絡
(1)其本質是一個僞文件(實爲內核緩衝區)函數
(2) 由兩個文件描述符引用,一個表示讀端,一個表示寫端。orm
(3) 規定數據從管道的寫端流入管道,從讀端流出。blog
匿名管道的特徵:接口
(1)只能進行單向通訊;隊列
(2)只適用於有血緣關係之間的進程;進程
(3)自帶同步基質;ip
(4)在進行通訊時面向字節流服務;input
(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中。爲了讓程序更加簡潔,忽略了有些函數調用是否成功的檢查。