天天看點

Linux 進階程式設計 - 有名管道 FIFO

FIFO 和 Pipe 的差別

上一篇文章我們了解了無名管道 Pipe 的原理,這篇文章我們來學習 IPC 的第二種方式 FIFO 有名管道,既然同為管道,它們兩個有什麼差別呢?

1. 相同點:Pipe 和 FIFO 都用管道來進行 IPC

2. 相同點:Pipe 和 FIFO 的管道資料都存在核心記憶體的緩沖區中

3. 不同點:Pipe 不在磁盤上建立管道檔案,FIFO 在磁盤上建立管道檔案

4. 不同點:Pipe 需要通信的程序具有親緣關系,而 FIFO 在不相關的程序之間也能交換資料

有名管道 FIFO 和無名管道 Pipe 主要的差別就是 FIFO 在磁盤上建立管道檔案(FIFO 将核心資料緩沖區映射到了實際的檔案節點),是以我們可以在磁盤上實際看到,故稱為「有名字」,而 Pipe 沒有在磁盤上建立檔案,我們不能實際看到,故稱為「無名」,其實就這麼簡單的了解。了解了基本的差別,我們來看看操作 FIFO 的函數。

如何使用 FIFO?

我們使用 FIFO 是在磁盤上建立一個管道檔案,然後利用這個檔案作為管道的傳輸通道,但是這個管道檔案很特殊,它的大小始終為 0,原因是管道的資料是存放在核心的記憶體中的,不在管道檔案中,我們也可以驗證這個事實。

mkfifo 指令

shell

終端中你可以使用下面的指令來手動建立一個管道檔案:

mkfifo fifo_file
           

然後看一些這個檔案的屬性和大小,發現是黃色的管道檔案( p ),大小始終為 0,并且你也不能手動使用編輯器來編輯這個檔案:

ll fifo_file
# 結果
prw-r--r--  orange orange  Aug   : fifo_file|

vim fifo_file
# 不能編輯這個檔案
           

來看個實際的例子。

mkfifo 函數

Linux 不僅提供了建立管道檔案的指令,也提供了 API:

#include <sys/types.h>
#include <sys/stat.h>

/*
 * pathname:FIFO 檔案名稱
 * mode:FIFO 檔案通路權限
 * return:成功傳回 0, 失敗傳回 -1, 并設定 erron
 */
int mkfifo(const char *pathname, mode_t mode);
           

例子:fifo_r.c,fifo_w.c

來看一個實際使用有名管道的例子,在這個例子中

fifo_w

向管道檔案寫入資料,

fifo_r

從管道中讀取資料。先看看寫入檔案:

// fifo_w.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>

// FIFO 檔案名
#define FIFO_PATH "fifo_file"


int main() { 
    // 建立 FIFO 檔案,如果存在就不再建立
    if (mkfifo(FIFO_PATH, ) <  && errno != EEXIST) {
        perror("create fifo failed");
        return -;
    } else { 
        char cont_w[] = "I'm FIFO write.\n";
        // 以隻寫的方式打開
        int fd = open(FIFO_PATH, O_CREAT|O_WRONLY, );
        if (fd > ) {
            while () {
                // 循環寫入内容
                write(fd, cont_w, strlen(cont_w));
                sleep();
                printf("write: %s\n", cont_w);
            }
            close(fd);
        } 
    }

    return ;
}
           

這是從 FIFO 檔案中讀取:

// fifo_r.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>

// FIFO 檔案名
#define FIFO_PATH "fifo_file"

int main() {
    // 建立 FIFO 檔案,如果存在就不再建立
    if (mkfifo(FIFO_PATH, ) <  && errno != EEXIST) {
        perror("create fifo failed");
        return -;
    } else { 
        char cont_r[];
        // 以隻讀的方式打開
        int fd = open(FIFO_PATH, O_CREAT | O_RDONLY, );
        if (fd > ) {
            while () {
                // 讀取 FIFO 中的内容
                read(fd, cont_r, );
                printf("read: %s\n", cont_r);
            }
            close(fd);
        }
    }

    return ;
}
           

編譯運作

先編譯:

gcc fifo_w.c -o fifo_w
gcc fifo_r.c -o fifo_r
           

運作

fifo_w

寫入内容:

./fifo_w
           

再運作另一個

fifo_r

從管道中讀取内容:

./fifo_r
           

如果運作成功,會發現可以成功讀取寫入的内容,這跟 Pipe 的操作其實是相同的。但是要注意的是當運作

fifo_w

,而沒有運作

fifo_r

的時候,寫入端将會阻塞,這主要是當用

open

打開 FIFO 檔案時會有下面 2 個狀态:

1. 在一般情況下(沒有指定

O_NONBLOCK

),以隻讀的方式 open FIFO 檔案會阻塞到某個程序為寫而打開這個 FIFO 為止,同樣以隻寫的方式 open FIFO 檔案會阻塞到某個程序為讀而打開這個檔案為止。

2. 如果指定了

O_NONBLOCK

,則隻讀 open 立即傳回,如果沒有程序為讀而打開一個 FIFO,那麼隻寫 open 将傳回 -1,并将 erron 設定成 ENXIO。

下面也來分析下核心中的 FIFO 實作。

FIFO 的核心實作

FIFO 的核心實作和 Pipe 的大同小異,這裡還是以 Linux-3.4 核心來分析,來看看 FIFO 在核心大體的執行過程:

fs/fifo.c

Linux 進階程式設計 - 有名管道 FIFO

fifo.c

實際上也是一個核心的驅動檔案,從

fifo_open

開始,然後對檔案進行加鎖,之後就像 Pipe 一樣為管道的記憶體資料配置設定核心記憶體空間,因為 FIFO 檔案裡面是不存儲資料的,資料都存儲在核心緩存區中,之後判斷目前的讀寫模式進行相應的操作,最後解鎖檔案。

結語

這樣我們就學習了第二種 Linux IPC 機制:有名管道 FIFO。我們知道了 FIFO 和 Pipe 的機制是差不多的,隻是 FIFO 在磁盤上有個可以看到的檔案,實際的讀寫資料還是在核心記憶體中的。

Pipe 進行 IPC 需要程序有親緣關系,但是通過 FIFO,不相關的程序也能交換資料。shell 指令也是使用 FIFO 将資料從一條管道傳送到另一條,進而實作了指令的組合使用,并且無需建立中間臨時檔案,例如:

ps -aux | grep xxx

。就這些了,希望你能認真實踐。

感謝你的閱讀,我們下次再見 :)

推薦關注我的微信公衆号 CDeveloper,堅持技術原創,隻說真話!
Linux 進階程式設計 - 有名管道 FIFO

繼續閱讀