【Linux】Linux的管道

管道是Linux由Unix那裏繼承過來的進程間的通訊機制,它是Unix早期的一個重要通訊機制。其思想是,在內存中建立一個共享文件,從而使通訊雙方利用這個共享文件來傳遞信息。因爲這種方式具備單向傳遞數據的特色,因此這個做爲傳遞消息的共享文件就叫作「管道」。java

在管道的具體實現中,根據通訊所使用的的文件是否具備名稱,有「匿名管道」和「命名管道」。node

 

管道與共享內存的區別
乍一看,感受管道和共享內存並非區別很大,這裏介紹一下二者之間的區別:linux

管道須要在內核和用戶空間進行四次的數據拷貝:由用戶空間的buf中將數據拷貝到內核中 -> 內核將數據拷貝到內存中 -> 內存到內核 -> 內核到用戶空間的buf。而共享內存則只拷貝兩次數據:用戶空間到內存 -> 內存到用戶空間。
管道用循環隊列實現,連續傳送數據能夠不限大小。共享內存每次傳遞數據大小是固定的;
共享內存能夠隨機訪問被映射文件的任意位置,管道只能順序讀寫;
管道能夠獨立完成數據的傳遞和通知機制,共享內存須要藉助其餘通信方式進行消息傳遞。
也就是說,二者之間最大的區別就是: 共享內存區是最快的可用IPC形式,一旦這樣的內存區映射到共享它的進程的地址空間,這些進程間數據的傳遞,就再也不經過執行任何進入內核的系統調用來傳遞彼此的數據,節省了時間。c++

 

匿名管道
匿名管道是在具備公共祖先的進程之間進行通訊的一種方式。面試

前面在介紹進程的建立時講到,由父進程建立的子進程將會賦值父進程包括文件在內的一些資源。若是父進程建立子進程以前建立了一個文件,那麼這個文件的描述符就會被父進程在隨後所建立的子進程所共享。也就是說,父、子進程能夠經過這個文件進行通訊。若是通訊的雙方一方只能進行讀操做,而另外一方只能進行寫操做,那麼這個文件就是一個只能單方向傳送消息的管道,以下圖所示:數組

進程能夠經過調用函數pipe()建立一個管道。函數pipe()的原型以下:服務器

int pipe(int fildes[2]);
與該函數pipe()相對應的系統調用sys_pipe()的原型以下:數據結構

asmlinkage int sys_pipe(unsigned long __user * fildes);
從本質上來講,pipe()函數的功能就是建立一個內存文件,但與建立普通文件的函數不一樣,函數pipe()將在參數fildes中爲進程返回這個文件的兩個文件描述符fildes[0]和fildes[1]。其中,fildes[0]是一個具備「只讀」屬性的文件描述符,fildes[1]是一個具備「只寫」屬性的文件描述符,即進程經過fildes[0]只能進行文件的讀操做,而經過fildes[1]只能進行文件的寫操做。架構

這樣,就使得這個文件像一段只能單向流通的管道同樣,一頭專門用來輸入數據,另外一頭專門用來輸出數據,因此稱爲管道。因爲這種文件沒有文件名,不能被非親進程所打開,只能用於親屬進程間的通訊,因此這種沒有名稱的文件造成的通訊管道叫作「匿名管道」。框架

顯然,若是父進程建立的這種文件只是用來通訊,那麼它感興趣的只是該文件所佔用的內存空間,因此也就沒有必要建立一個正式文件,只需建立一個只存在於內存的臨時文件。從這一點來看,匿名管道與共享內存具備共同點,只不過匿名管道時單向通訊,並且這個通訊只能在親屬進程間進行。

爲支持匿名管道,內核初始化時由內核函數kernel_mount()安裝了一種特殊的文件系統,在該系統中所建立的都是臨時文件。

因爲匿名管道是一個文件,因此它也有i節點,其結構以下:

struct inode
{
        ...
        struct file_operations *i_fop;            //文件操做函數集
        struct pipe_inode_info *i_pipe;           //管道文件指針
        ...
};
能夠看到,在i節點的結構中有一個pipe_inode_info類型的指針i_pipe,在普通文件中這個指針的值爲NULL,而在管道文件中這個指針則只想一個叫作管道節點信息結構的pipe_inode_info,以代表這是一個管道文件。pipe_inode_info的結構以下:

struct pipe_inode_info {
    wait_queue_head_t wait;            //等待進程隊列
    unsigned int nrbufs, curbuf;
    struct page *tmp_page;
    unsigned int readers;
    unsigned int writers;
    unsigned int waiting_writers;
    unsigned int r_counter;            //以只讀方式訪問管道的進程計數器
    unsigned int w_counter;            //以只寫方式訪問管道的進程計數器
    struct fasync_struct *fasync_readers;
    struct fasync_struct *fasync_writers;
    struct inode *inode;
    struct pipe_buffer bufs[PIPE_BUFFERS];            //緩衝區數組
};
結構中的域bufs就是構成管道的內存緩衝區。該緩衝區用結構pipe_buffer來描述:

struct pipe_buffer {
    struct page *page;                    //緩衝頁的結構
    unsigned int offset, len;
    const struct pipe_buf_operations *ops;            //緩衝區的操做函數集指針
    unsigned int flags;
    unsigned long private;
};
從上面的數據結構中能夠看到,管道實質上就是一個被當作文件來管理的內存緩衝區。

在建立一個管道的i節點時,結構inode中的域i_fop被賦予rdwr_pipefifo_fops,即管道文件自己是既可讀又可寫的。rdwr_pipefifo_fops在文件linux/fs/pipe.c中的定義以下:

const struct file_operations rdwr_pipefifo_fops = {
    .llseek        = no_llseek,
    .read        = do_sync_read,
    .aio_read    = pipe_read,
    .write        = do_sync_write,
    .aio_write    = pipe_write,
    .poll        = pipe_poll,
    .unlocked_ioctl    = pipe_ioctl,
    .open        = pipe_rdwr_open,
    .release    = pipe_rdwr_release,
    .fasync        = pipe_rdwr_fasync,
};
而爲進程所建立的打開文件描述符fildes[0]和fildes[1]中的i_fop,則被分別賦予了只讀的函數操做集read_pipefifo_fops和只寫的函數操做集write_pipefifo_fops。

read_pipefifo_fops和write_pipefifo_fops這兩個操做函數集在文件linux/fs/pipe.c中分別定義以下:

const struct file_operations read_pipefifo_fops = {
    .llseek        = no_llseek,
    .read        = do_sync_read,
    .aio_read    = pipe_read,
    .write        = bad_pipe_w,
    .poll        = pipe_poll,
    .unlocked_ioctl    = pipe_ioctl,
    .open        = pipe_read_open,
    .release    = pipe_read_release,
    .fasync        = pipe_read_fasync,
};
 
const struct file_operations write_pipefifo_fops = {
    .llseek        = no_llseek,
    .read        = bad_pipe_r,
    .write        = do_sync_write,
    .aio_write    = pipe_write,
    .poll        = pipe_poll,
    .unlocked_ioctl    = pipe_ioctl,
    .open        = pipe_write_open,
    .release    = pipe_write_release,
    .fasync        = pipe_write_fasync,
};
建立匿名管道的進程與管道之間的關係以下圖所示:

當一個進程調用函數pipe()建立一個管道後,管道的鏈接方式以下所示:

從圖中能夠看到,因爲管道的出入口都在同一個進程之中,這種管道沒有多大的用途的。可是當這個進程在建立一個新進程以後,狀況就變得大不同了。

若是父進程建立一個管道以後,又建立了一個子進程,那麼因爲子進程繼承了父進程的文件資源,因而管道在父子進程中的鏈接狀況就變成以下圖同樣的狀況了:

在肯定管道的傳輸方向以後,在父進程中關閉(close())文件描述符fildes[0],在子進程中關閉(close())文件描述符fildes[1],因而管道的鏈接狀況就變成以下狀況的單向傳輸管道:

也能夠想象,經過關閉文件描述符的方法,在兩個兄弟進程之間也能夠實現通訊管道。

建立完管道以後,怎麼利用管道來進行數據的通訊呢?

管道使用read()和write()函數,採用字節流的方式,具備流動性,讀數據時,每讀一段數據,則管道內會清除已讀走的數據。

讀管道時,若管道爲空,則被堵塞,直至管道另外一端write將數據寫入到管道爲止。若寫段已關閉,則返回0;
寫管道時,若管道已滿,則被阻塞,直到管道另外一端read將管道內數據取走爲止。
用close()函數,在建立管道時,寫端須要關閉fildes[0]描述符,讀端須要關閉fildes[1]描述符。當進程關閉前,每一個進程須要將沒有關閉的描述符都進行關閉。

匿名管道具備以下特色:

因爲這種管道沒有其餘同步措施,因此爲了避免產生混亂,它只能是半雙工的,即數據只能向一個方向流動。若是須要雙方互相傳遞數據,則須要創建兩個管道;
只能在父子進程或兄弟進程這些具備親緣關係的進程之間進行通訊;
匿名管道對於管道兩端的進程而言,就是一個只存在於內存的特殊文件;
一個進程向管道中寫的內容被管道另外一端的進程讀取。寫入的內容每次都添加在管道緩衝區的末尾,而且每次都是從緩衝區的頭部讀取數據。
匿名管道的侷限性主要有兩點:一是因爲管道創建在內存中,因此它的容量不可能很大;二是管道所傳送的是無格式字節流,這就要求使用管道的雙方實現必須對傳輸的數據格式進行約定。

例子:在父子進程之間利用匿名管道通訊。

#include <unist.h>
#include <string.h>
#include <wait.h>
#include <stdio.h>
 
#define MAX_LINE 80
 
int main()
{
    int testPipe[2], ret;
    char buf[MAX_LINE + 1];
    const char * testbuf = "主程序發送的數據";
 
    if (pipe(testbuf) == 0) {
        if (fork() == 0) {
            ret = read(testPipe[0], buf, MAX_LINE);
            buf[ret] = 0;
            printf("子程序讀到的數據爲:%s", buf);
            close(testPipe[0]);
        }else {
            ret = write(testPipe[1], testbuf, strlen(testbuf));
            ret = wait(NULL);
            close(testPipe[1]);
        }
    }
    
    return 0;
}
 

命名管道
因爲匿名管道沒有名稱,所以,它只能在一些具備親緣關係的進程之間進行通訊,這使它在應用方面受到極大的限制。

命名管道是在實際文件系統上實現的一種通訊機制。因爲它是一個與進程沒有「血緣關係」的、真正且獨立的文件,因此它能夠在任意進程之間實現通訊。因爲命名管道不支持諸如lseek()等文件定位操做,嚴格遵照先進先出的原則進行傳輸數據,即對管道的讀老是從開始處返回數據,對它的寫老是把數據添加到末尾,因此這種管道也叫作FIFO文件。

一樣,因爲須要由管道自身來保證通訊進程間的同步,命名管道也是一個只能單方向訪問的文件,而且數據傳輸方式爲FIFO方式。

也就是說,命名管道提供了一個路徑名與之關聯,以FIFO的文件形式存在於文件系統中,在文件系統中產生一個物理文件,其餘進程只要訪問該文件路徑,就能彼此經過管道通訊。在讀數據端以只讀方式打開管道文件,在寫數據端以只寫方式打開管道文件。

FIFO文件與普通文件的區別:

普通文件沒法實現字節流方式管理,並且多進程之間訪問共享資源會形成意想不到的問題;
FIFO文件採用字節流方式管理,遵循先入先出原則,不涉及共享資源訪問。
操做流程爲:mkfifo -> open -> read(write) -> close ->unlink。

在這裏給你們提供一個學習交流的平臺,C++,Linux服務器架構師羣:812855908

具備1-5工做經驗的,面對目前流行的技術不知從何下手,須要突破技術瓶頸的能夠加羣。

在公司待久了,過得很安逸,但跳槽時面試碰壁。須要在短期內進修、跳槽拿高薪的能夠加羣。

若是沒有工做經驗,但基礎很是紮實,對C++Linux工做機制,經常使用設計思想,經常使用java開發框架掌握熟練的能夠加羣。

________________________________________________________________________________________________

加Java架構師進階交流羣獲取C++Linux
Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協程,DPDK等多個知識點高級進階乾貨的直播免費學習權限

都是大牛帶飛 讓你少走不少的彎路的 羣號是:812855908 對了 小白勿進 最好是有開發經驗

注:加羣要求

一、具備工做經驗的,面對目前流行的技術不知從何下手,須要突破技術瓶頸的能夠加。

二、在公司待久了,過得很安逸,但跳槽時面試碰壁。須要在短期內進修、跳槽拿高薪的能夠加。

三、若是沒有工做經驗,但基礎很是紮實,對C++工做機制,經常使用設計思想,經常使用c/c++開發框架掌握熟練的,能夠加。

四、以爲本身很牛B,通常需求都能搞定。可是所學的知識點沒有系統化,很難在技術領域繼續突破的能夠加。

5.阿里Java高級大牛直播講解知識點,分享知識,多年工做經驗的梳理和總結,帶着你們全面、科學地創建本身的技術體系和技術認知!

相關文章
相關標籤/搜索