版權聲明:本文為 cdeveloper 原創文章,可以隨意轉載,但必須在明確位置注明出處!
FIFO 和 Pipe 的區別
上一篇文章我們了解了無名管道 Pipe 的原理,這篇文章我們來學習 IPC 的第二種方式 FIFO 有名管道,既然同為管道,它們兩個有什么區別呢?
- 相同點:Pipe 和 FIFO 都用管道來進行 IPC
- 相同點:Pipe 和 FIFO 的管道數據都存在內核內存的緩沖區中
- 不同點:Pipe 不在磁盤上建立管道文件,FIFO 在磁盤上建立管道文件
- 不同點:Pipe 需要通信的進程具有親緣關系,而 FIFO 在不相關的進程之間也能交換數據
有名管道 FIFO 和無名管道 Pipe 主要的區別就是 FIFO 在磁盤上建立管道文件(FIFO 將內核數據緩沖區映射到了實際的文件節點),所以我們可以在磁盤上實際看到,故稱為「有名字」,而 Pipe 沒有在磁盤上建立文件,我們不能實際看到,故稱為「無名」,其實就這么簡單的理解。了解了基本的區別,我們來看看操作 FIFO 的函數。
如何使用 FIFO?
我們使用 FIFO 是在磁盤上建立一個管道文件,然后利用這個文件作為管道的傳輸通道,但是這個管道文件很特殊,它的大小始終為 0,原因是管道的數據是存放在內核的內存中的,不在管道文件中,我們也可以驗證這個事實。
mkfifo 命令
在 shell
終端中你可以使用下面的命令來手動建立一個管道文件:
mkfifo fifo_file
然后看一些這個文件的屬性和大小,發現是黃色的管道文件( p ),大小始終為 0,并且你也不能手動使用編輯器來編輯這個文件:
ll fifo_file
# 結果
prw-r--r-- 1 orange orange 0 Aug 5 14:05 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, 0666) < 0 && errno != EEXIST) {
perror("create fifo failed");
return -1;
} else {
char cont_w[] = "I'm FIFO write.\n";
// 以只寫的方式打開
int fd = open(FIFO_PATH, O_CREAT|O_WRONLY, 0666);
if (fd > 0) {
while (1) {
// 循環寫入內容
write(fd, cont_w, strlen(cont_w));
sleep(1);
printf("write: %s\n", cont_w);
}
close(fd);
}
}
return 0;
}
這是從 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, 0666) < 0 && errno != EEXIST) {
perror("create fifo failed");
return -1;
} else {
char cont_r[255];
// 以只讀的方式打開
int fd = open(FIFO_PATH, O_CREAT | O_RDONLY, 0666);
if (fd > 0) {
while (1) {
// 讀取 FIFO 中的內容
read(fd, cont_r, 255);
printf("read: %s\n", cont_r);
}
close(fd);
}
}
return 0;
}
編譯運行
先編譯:
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 個狀態:
- 在一般情況下(沒有指定
O_NONBLOCK
),以只讀的方式 open FIFO 文件會阻塞到某個進程為寫而打開這個 FIFO 為止,同樣以只寫的方式 open FIFO 文件會阻塞到某個進程為讀而打開這個文件為止。 - 如果指定了
O_NONBLOCK
,則只讀 open 立即返回,如果沒有進程為讀而打開一個 FIFO,那么只寫 open 將返回 -1,并將 erron 設置成 ENXIO。
下面也來分析下內核中的 FIFO 實現。
FIFO 的內核實現
FIFO 的內核實現和 Pipe 的大同小異,這里還是以 Linux-3.4 內核來分析,來看看 FIFO 在內核大體的執行過程:fs/fifo.c
fifo.c
實際上也是一個內核的驅動文件,從 fifo_open
開始,然后對文件進行加鎖,之后就像 Pipe 一樣為管道的內存數據分配內核內存空間,因為 FIFO 文件里面是不存儲數據的,數據都存儲在內核緩存區中,之后判斷當前的讀寫模式進行相應的操作,最后解鎖文件。
結語
這樣我們就學習了第二種 Linux IPC 機制:有名管道 FIFO。我們知道了 FIFO 和 Pipe 的機制是差不多的,只是 FIFO 在磁盤上有個可以看到的文件,實際的讀寫數據還是在內核內存中的。
Pipe 進行 IPC 需要進程有親緣關系,但是通過 FIFO,不相關的進程也能交換數據。shell 命令也是使用 FIFO 將數據從一條管道傳送到另一條,從而實現了命令的組合使用,并且無需創建中間臨時文件,例如:ps -aux | grep xxx
。就這些了,希望你能認真實踐。
感謝你的閱讀,我們下次再見 :)