管道是 Unix 最早的 IPC 機制。本文系統梳理無名管道(親緣進程)與有名管道 FIFO(非親緣進程)的建立、讀寫規則、原子性保證,並提供 C 語言驗證程式碼片段。
| 特性 | 無名管道 (pipe) | 有名管道 (FIFO/mkfifo) |
|---|---|---|
| 適用對象 | 親緣進程 (父子、兄弟) | 任意進程 (無親緣限制) |
| 命名空間 | 無名稱,僅存在記憶體 | 檔案系統路徑 (如 /tmp/myfifo) |
| 建立方式 | pipe(int fd[2]) | mkfifo(path, mode) 或 shell mkfifo |
| 生命週期 | 隨進程結束銷毀 | 檔案持久存在,除非手動刪除 |
| 雙向通訊 | 需建立兩個管道 | 同理,或雙方開啟同一 FIFO 讀寫 |
#include
int pipe(int fd[2]); // 成功返回 0,fd[0]=讀端, fd[1]=寫端
典型用法:父進程 pipe() → fork() → 父關讀端寫資料、子關寫端讀資料 (或相反)。
read() 返回 0 (EOF)PIPE_BUF (POSIX ≥512B, Linux 4096B) → 返回現有資料 (可能 < 請求量)PIPE_BUF → 阻塞直到滿足或 EOFSIGPIPE (預設終止),write() 返回 EPIPEPIPE_BUF → 原子性保證 (不會與其他寫入交錯)PIPE_BUF → 不保證原子性,可能被其他寫入插入
// 程式建立
#include
mkfifo("/tmp/myfifo", 0666);
// Shell 建立
mkfifo /tmp/myfifo
// 開啟方式 (阻塞預設)
int fd = open("/tmp/myfifo", O_RDONLY); // 讀端:阻塞直到有寫端開啟
int fd = open("/tmp/myfifo", O_WRONLY); // 寫端:阻塞直到有讀端開啟
// 非阻塞開啟
open("/tmp/myfifo", O_RDONLY | O_NONBLOCK);
// readtest.c - 驗證讀端規則
#include
#include
#include
int main() {
int fd[2];
pipe(fd);
if (fork() == 0) { // 子進程:寫端
close(fd[0]);
write(fd[1], "hello", 5);
close(fd[1]);
_exit(0);
}
close(fd[1]); // 父進程:讀端
char buf[100];
int n = read(fd[0], buf, sizeof(buf));
printf("Read %d bytes: %.*s\n", n, n, buf); // 輸出: Read 5 bytes: hello
return 0;
}
select/poll/epoll 處理signal(SIGPIPE, SIG_IGN) 避免寫端斷開時進程被殺write() ≤ PIPE_BUFsendmsg 傳遞檔案描述符、效能更高管道雖古老,但簡單、可靠、Shell 原生支援,仍是進程協作、Shell 管線、容器標準輸入輸出重導向的基石。掌握其讀寫語意,是系統程式設計師的必修功課。