更新時間:2020-12-16 17:42:17 來源:動力節(jié)點 瀏覽1588次
管道(pipe)是一個我們在學(xué)習(xí)Linux命令行的時候就會引入的一個很重要的概念。管道是UNIX環(huán)境中歷史最悠久的進程間通信方式,從本質(zhì)上說,管道也是一種文件,也是遵循UNIX的“一切皆文件”的原則設(shè)計的。雖然實現(xiàn)形態(tài)上是文件,但是管道本身并不占用磁盤或者其他外部存儲的空間。在Linux的實現(xiàn)上,它占用的是內(nèi)存空間。所以,Linux管道實際上就是一個操作方式為文件的內(nèi)存緩沖區(qū)。
一、Linux管道分兩種類型:匿名管道和命名管道也叫做有名或無名管道
匿名管道最常見的形態(tài)就是我們在shell操作中最常用的”|”。它的特點是只能在父子進程中使用,父進程在產(chǎn)生子進程前必須打開一個管道文件,然后fork產(chǎn)生子進程,這樣子進程通過拷貝父進程的進程地址空間獲得同一個管道文件的描述符,以達(dá)到使用同一個管道通信的目的。此時除了父子進程外,沒人知道這個管道文件的描述符,所以通過這個管道中的信息無法傳遞給其他進程。這保證了傳輸數(shù)據(jù)的安全性,當(dāng)然也降低了管道了通用性,于是系統(tǒng)還提供了命名管道。
二、Linux管道的實現(xiàn)機制
在Linux中,管道是一種使用非常頻繁的通信機制。從本質(zhì)上說,管道也是一種文件,但它又和一般的文件有所不同,管道可以克服使用文件進行通信的兩個問題:
1.限制管道的大小。實際上,管道是一個固定大小的緩沖區(qū)。在Linux中,該緩沖區(qū)的大小為1頁,即4K字節(jié),使得它的大小不象文件那樣不加檢驗地增長。使用單個固定緩沖區(qū)也會帶來問題,比如在寫管道時可能變滿,當(dāng)這種情況發(fā)生時,隨后對管道的write()調(diào)用將默認(rèn)地被阻塞,等待某些數(shù)據(jù)被讀取,以便騰出足夠的空間供write()調(diào)用寫。
2.讀取進程也可能工作得比寫進程快。當(dāng)所有當(dāng)前進程數(shù)據(jù)已被讀取時,管道變空。當(dāng)這種情況發(fā)生時,一個隨后的read()調(diào)用將默認(rèn)地被阻塞,等待某些數(shù)據(jù)被寫入,這解決了read()調(diào)用返回文件結(jié)束的問題。注意:從管道讀數(shù)據(jù)是一次性操作,數(shù)據(jù)一旦被讀,它就從管道中被拋棄,釋放空間以便寫更多的數(shù)據(jù)。
三、linux管道的結(jié)構(gòu)
在 Linux 中,管道的實現(xiàn)并沒有使用專門的數(shù)據(jù)結(jié)構(gòu),而是借助了文件系統(tǒng)的file結(jié)構(gòu)和VFS的索引節(jié)點inode。通過將兩個 file 結(jié)構(gòu)指向同一個臨時的 VFS 索引節(jié)點,而這個 VFS 索引節(jié)點又指向一個物理頁面而實現(xiàn)的。
四、linux管道的代碼示例
管道由pipe函數(shù)創(chuàng)建:
#include <unistd.h>
int pipe(int filedes[2]);
調(diào)用pipe函數(shù)時在內(nèi)核中開辟一塊緩沖區(qū)(稱為管道)用于通信,它有一個讀端一個寫端,然后通過filedes參數(shù)傳出給用戶程序兩個文件描述符,filedes[0]指向管道的讀端,filedes[1]指向管道的寫端。向這個文件讀寫數(shù)據(jù)其實是在讀寫內(nèi)核緩沖區(qū)。pipe函數(shù)調(diào)用成功返回0,調(diào)用失敗返回-1。
子進程通過管道向父進程發(fā)送數(shù)據(jù)。限制在父子進程間通信。
#include<stdlib.h>
#include<stdio.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
#include<string.h>
int main () {
char* msg;
char buf[20];
int pipe_filed[2];
pipe(pipe_filed);
pid_t pid = fork();
if(pid < 0) {
perror("fork errir.");
exit(1);
} else if (0 == pid) {
msg = "child";
write(pipe_filed[1], msg, sizeof(msg));
printf("child process send: %s\n", msg);
} else {
read(pipe_filed[0], buf, sizeof(buf));
printf("parent process recv: %s\n", buf);
int status;
wait(&status);
if (WIFEXITED(status))
printf("Child exited with code %d\n", WEXITSTATUS(status));
else if (WIFSIGNALED(status))
printf("Child terminated abnormally, signal %d\n", WTERMSIG(status));
}
return 0;
}
兩個進程通過一個管道只能實現(xiàn)單向通信,比如上面的例子,子進程寫父進程讀,如果有時候也需要父進程寫子進程讀,就必須另開一個管道。
管道其實是一個在內(nèi)核內(nèi)存中維護的緩沖器,這個緩沖器的存儲能力是有限的。管道被填滿之后,后續(xù)向管道寫入操作都會被堵塞直到有讀取進程讀取管道中的數(shù)據(jù)。至于如何從Linux管道中讀取數(shù)據(jù),可以在本站的Linux教程中找到詳細(xì)的解答。