Linux管道

helloxchen發表於2011-01-05

管道是Linux中很重要的一種通訊方式,是把一個程式的輸出直接連線到另一個程式的輸入,常說的管道多是指無名管道,無名管道只能用於具有親緣關係的程式之間,這是它與有名管道的最大區別。

有名管道叫named pipe或者FIFO(先進先出),可以用函式mkfifo()建立。

Linux管道的實現機制

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

· 限制管道的大小。實際上,管道是一個固定大小的緩衝區。在Linux中,該緩衝區的大小為1頁,即4K位元組,使得它的大小不象檔案那樣不加檢驗地增長。使用單個固定緩衝區也會帶來問題,比如在寫管道時可能變滿,當這種情況發生時,隨後對管道的write()呼叫將預設地被阻塞,等待某些資料被讀取,以便騰出足夠的空間供write()呼叫寫。

· 讀取程式也可能工作得比寫程式快。當所有當前程式資料已被讀取時,管道變空。當這種情況發生時,一個隨後的read()呼叫將預設地被阻塞,等待某些資料被寫入,這解決了read()呼叫返回檔案結束的問題。

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

1. 管道的結構

在 Linux 中,管道的實現並沒有使用專門的資料結構,而是藉助了檔案系統的file結構和VFS的索引節點inode。透過將兩個 file 結構指向同一個臨時的 VFS 索引節點,而這個 VFS 索引節點又指向一個物理頁面而實現的。

2.管道的讀寫

管道實現的原始碼在fs/pipe.c中,在pipe.c中有很多函式,其中有兩個函式比較重要,即管道讀函式pipe_read()和管道寫函式 pipe_wrtie()。管道寫函式透過將位元組複製到 VFS 索引節點指向的實體記憶體而寫入資料,而管道讀函式則透過複製實體記憶體中的位元組而讀出資料。當然,核心必須利用一定的機制同步對管道的訪問,為此,核心使用了鎖、等待佇列和訊號。

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

·記憶體中有足夠的空間可容納所有要寫入的資料;

·記憶體沒有被讀程式鎖定。

如果同時滿足上述條件,寫入函式首先鎖定記憶體,然後從寫程式的地址空間中複製資料到記憶體。否則,寫入程式就休眠在 VFS 索引節點的等待佇列中,接下來,核心將呼叫排程程式,而排程程式會選擇其他程式執行。寫入程式實際處於可中斷的等待狀態,當記憶體中有足夠的空間可以容納寫入資料,或記憶體被解鎖時,讀取程式會喚醒寫入程式,這時,寫入程式將接收到訊號。當資料寫入記憶體之後,記憶體被解鎖,而所有休眠在索引節點的讀取程式會被喚醒。

管道的讀取過程和寫入過程類似。但是,程式可以在沒有資料或記憶體被鎖定時立即返回錯誤資訊,而不是阻塞該程式,這依賴於檔案或管道的開啟模式。反之,程式可以休眠在索引節點的等待佇列中等待寫入程式寫入資料。當所有的程式完成了管道操作之後,管道的索引節點被丟棄,而共享資料頁也被釋放。

因為管道的實現涉及很多檔案的操作,因此,當讀者學完有關檔案系統的內容後來讀pipe.c中的程式碼,你會覺得並不難理解。

Linux 管道的建立和使用都要簡單一些,唯一的原因是它需要更少的引數。實現與 Windows 相同的管道建立目標,Linux 和 UNIX 使用下面的程式碼片段:

建立 Linux 命名管道

1
2
3
4
5
6
7
8
9
  int fd1[2];
if(pipe(fd1))
{ printf("pipe() FAILED: errno=%d",errno);
return 1;
}
Linux 管道對阻塞之前一次寫操作的大小有限制。 專門為每個管道所使用的核心級緩衝區確切為 4096 位元組。 除非閱讀器清空管道,否則一次超過 4K 的寫操作將被阻塞。 實際上這算不上什麼限制,因為讀和寫操作是在不同的執行緒中實現的。

  Linux 還支援命名管道。對這些數字的早期評論員建議我,為公平起見,應該比較 Linux 的命名管道和 Windows 的命名管道。我寫了另一個在 Linux 上使用命名管道的程式。我發現對於 Linux 上命名的和未命名的管道,結果是沒有區別。

  Linux 管道比 Windows 2000 命名管道快很多,而 Windows 2000 命名管道比 Windows XP 命名管道快得多。

例子:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include
#include
int main()
{
int n,fd[2]; // 這裡的fd是檔案描述符的陣列,用於建立管道做準備的
pid_t pid;
char line[100];
if(pipe(fd)<0) // 建立管道
printf("pipe create errorn");
if((pid=fork())<0) //利用fork()建立新程式
printf("fork errorn");
else if(pid>0){ //這裡是父程式,先關閉管道的讀出端,然後在管道的寫端寫入“hello world"
close(fd[0]);
write(fd[1],"hello wordn",11);
}
else{
close(fd[1]); //這裡是子程式,先關閉管道的寫入端,然後在管道的讀出端讀出資料
n= read(fd[0],line,100);
write(STDOUT_FILENO,line,n);
}
exit(0);
}
[@more@]

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/24790158/viewspace-1044146/,如需轉載,請註明出處,否則將追究法律責任。

相關文章