在Linux進程間通信(IPC)的世界裏,我們已經熟悉了匿名管道(pipe)——那個只能在父子、兄弟等“家庭成員”之間使用的“內線電話”。它簡單、高效,但它的“血緣”限制也讓我們在面對兩個毫無關係的獨立進程時束手無策。
這時,我們需要一個“公共電話亭”,任何知道號碼(路徑)的進程都可以拿起聽筒進行通話。這個“公共電話亭”,就是命名管道(FIFO,First-In, First-Out)。
一、 從“內線電話”到“公共電話亭”
|
特性
|
匿名管道 (Pipe)
|
命名管道 (FIFO)
|
|
標識 |
內核中的一塊內存,通過繼承的文件描述符訪問
|
文件系統中的一個特殊文件,通過路徑名訪問 |
|
生命週期 |
隨進程,所有持有其文件描述符的進程退出後消失
|
隨文件系統,只要不被刪除就一直存在
|
|
通信範圍 |
僅限有血緣關係的進程(父子、兄弟) |
任意進程,只要它們有權限訪問同一個FIFO文件 |
|
比喻 |
家庭內線電話
|
公共電話亭
|
FIFO的出現,就是為了打破匿名管道的“家族壁壘”,提供一種更為通用的、基於文件系統的管道通信機制。
二、 搭建“公共電話亭”:創建FIFO
我們有兩種方式來創建一個FIFO。
1. 命令創建(管理員方式)
在Shell中,使用mkfifo命令可以輕鬆創建一個命名管道。
# 創建一個名為 myfifo 的命名管道
mkfifo myfifo
# 查看文件類型
ls -l myfifo
運行結果
prw-r--r-- 1 user group 0 Jan 1 10:00 myfifo
注意文件類型 p,它代表這是一個管道(pipe)文件。
2. 函數創建(程序員方式)
在C代碼中,我們使用mkfifo()函數來創建。
核心函數
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
pathname: FIFO文件的路徑名,例如"./myfifo"。mode: 文件的權限,使用八進制表示,如0664。- 返回值:成功返回
0,失敗返回-1。
三、 實戰演練:兩個陌生進程的對話
現在,我們來編寫兩個完全獨立的程序:一個寫者(fifo_writer.c)和一個讀者(fifo_reader.c)。它們沒有任何父子關係,將通過我們創建的myfifo文件進行通信。
寫者程序 (fifo_writer.c)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#define FIFO_PATH "./myfifo"
int main() {
int fd;
char message[100];
int count = 1;
// 嘗試創建FIFO,如果已存在則忽略錯誤
mkfifo(FIFO_PATH, 0664);
printf("Writer: Waiting to open FIFO for writing...\n");
// 以只寫方式打開FIFO,如果此時沒有讀者打開,這裏會阻塞
fd = open(FIFO_PATH, O_WRONLY);
if (fd == -1) {
perror("open fifo error");
exit(1);
}
printf("Writer: FIFO opened. Start writing.\n");
while (1) {
snprintf(message, sizeof(message), "Message #%d from writer", count++);
write(fd, message, strlen(message) + 1);
printf("Sent: '%s'\n", message);
sleep(1); // 每秒發送一次
}
close(fd);
return 0;
}
讀者程序 (fifo_reader.c)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define FIFO_PATH "./myfifo"
int main() {
int fd;
char buffer[100];
int n;
printf("Reader: Waiting to open FIFO for reading...\n");
// 以只讀方式打開FIFO
fd = open(FIFO_PATH, O_RDONLY);
if (fd == -1) {
perror("open fifo error");
exit(1);
}
printf("Reader: FIFO opened. Waiting for data...\n");
while ((n = read(fd, buffer, sizeof(buffer))) > 0) {
printf("Received: '%s'\n", buffer);
}
if (n == 0) {
printf("Reader: All writers have closed the FIFO. Exiting.\n");
}
close(fd);
return 0;
}
運行與驗證
- 編譯程序
gcc fifo_writer.c -o writer
gcc fifo_reader.c -o reader
- 啓動讀者(打開一個終端)
./reader
輸出:
Reader: Waiting to open FIFO for reading...
Reader: FIFO opened. Waiting for data...
(此時程序會阻塞,等待寫者寫入數據)
- 啓動寫者(打開另一個終端)
./writer
寫者終端輸出:
Writer: Waiting to open FIFO for writing...
Writer: FIFO opened. Start writing.
Sent: 'Message #1 from writer'
Sent: 'Message #2 from writer'
...
讀者終端輸出:
... (接上文)
Received: 'Message #1 from writer'
Received: 'Message #2 from writer'
...
- 關閉寫者 在寫者終端按
Ctrl+C結束程序。 - 觀察讀者 讀者終端會立即輸出:
Reader: All writers have closed the FIFO. Exiting.
(程序正常退出)
實驗成功!兩個毫無關係的進程,通過一個文件系統中的節點,實現了完美的通信。
四、 深入探索:FIFO的獨特行為
FIFO的行為與匿名管道既有相似之處,也有關鍵區別:
|
場景
|
匿名管道 (Pipe) 的行為
|
命名管道 (FIFO) 的行為
|
|
寫端打開,但無讀端 |
進程收到 |
|
|
讀端打開,但管道為空 |
|
|
|
所有寫端關閉後,讀端再讀 |
|
|
|
數據被讀取後 |
數據從管道中消失,不可重複讀取。
|
數據從管道中消失,不可重複讀取。 (與Pipe相同)
|
|
多進程併發 |
父子/兄弟間可共享。
|
任意多個進程可同時讀/寫同一個FIFO,數據會被競爭消費。
|
最重要的區別:對於寫者來説,匿名管道是“暴脾氣”,發現沒人聽(無讀端)就直接“崩潰”(SIGPIPE);而FIFO是“耐心派”,沒人聽就一直“等待”,直到聽眾出現。這個阻塞特性使得FIFO在編寫健壯的服務時更加方便。
五、 知識小結
- 核心價值:FIFO通過在文件系統中創建一個特殊文件,解決了匿名管道只能用於有血緣關係進程的限制。
- 創建:使用
mkfifo命令或mkfifo()函數。 - 使用:像操作普通文件一樣,使用
open,read,write,close。 - 阻塞特性:
open寫端時,若無讀端則阻塞。read時,若無數據則阻塞。
- 生命週期:與文件系統同在,不隨進程退出而消失。
命名管道FIFO是Linux IPC工具箱中一把強大而靈活的瑞士軍刀。當你需要一種比匿名管道更通用,又比套接字或共享內存更簡單的通信方式時,FIFO無疑是你的最佳選擇。