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
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiADNyEzLcd3LcJzLcJzdllmVldWYtl2Q3UCcpJHdz9CX05WZpJ3bt8Gd1F2LcJjcn9WTldWYtl2Pn5GcuEGZzEzN1ATM3UzN4QWN5ATL1gzMzEjN08CXzV2Zh1WafRWYvxGc19CXvlmL1h2cuFWaq5ycldWYtlWLkF2bsBXdvw1LcpDc0RHaiojIsJye.png)
fifo.c
實際上也是一個核心的驅動檔案,從
fifo_open
開始,然後對檔案進行加鎖,之後就像 Pipe 一樣為管道的記憶體資料配置設定核心記憶體空間,因為 FIFO 檔案裡面是不存儲資料的,資料都存儲在核心緩存區中,之後判斷目前的讀寫模式進行相應的操作,最後解鎖檔案。
結語
這樣我們就學習了第二種 Linux IPC 機制:有名管道 FIFO。我們知道了 FIFO 和 Pipe 的機制是差不多的,隻是 FIFO 在磁盤上有個可以看到的檔案,實際的讀寫資料還是在核心記憶體中的。
Pipe 進行 IPC 需要程序有親緣關系,但是通過 FIFO,不相關的程序也能交換資料。shell 指令也是使用 FIFO 将資料從一條管道傳送到另一條,進而實作了指令的組合使用,并且無需建立中間臨時檔案,例如:
ps -aux | grep xxx
。就這些了,希望你能認真實踐。
感謝你的閱讀,我們下次再見 :)
推薦關注我的微信公衆号 CDeveloper,堅持技術原創,隻說真話!