管道 是 Unix 系統最經典的進程間通信(IPC)機制之一,實現了「生產者-消費者」模型。本文系統介紹管道原理、分類、核心系統調用,並提供一個完整的「雙子進程寫入、父進程讀取」範例程式,幫助開發者快速掌握管道編程精髓。
管道是一個共享檔案,連接一個寫進程與一個讀進程,資料單向流動:
fd[1]):生產者寫入資料。fd[0]):消費者讀取資料。核心內部以環形緩衝區實現,維護讀/寫指針,遵循先進先出(FIFO)原則,並透過鎖機制保證互斥訪問。
| 類型 | 建立方式 | 命名空間 | 適用範圍 | 生命週期 |
|---|---|---|---|---|
| 無名管道 | pipe() | 無路徑名,僅以檔案描述符標識 | 僅限父子進程或有親緣關係的進程 | 進程結束時核心回收 |
| 有名管道 (FIFO) | mkfifo() / mknod() | 檔案系統中有路徑名 | 任意進程均可通過路徑訪問 | 永久存在,除非手動刪除 |
本文專注於無名管道。
#include <unistd.h>
int pipe(int filedes[2]);
filedes[0] 為讀端,filedes[1] 為寫端。errno。ssize_t read(int fd, void *buf, size_t count);
fd 讀取最多 count 字節到 buf。ssize_t write(int fd, const void *buf, size_t count);
buf 前 count 字節寫入 fd。SIGPIPE 信號(預設終止)。目標:建立一條無名管道,創建兩個子進程 P1、P2 分別寫入訊息,父進程按順序(先 P1 後 P2)讀取並顯示。
"Child 1 is sending a message!""Child 2 is sending a message!"#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MSG_SIZE 100
int main() {
int pipe_fd[2];
pid_t pid1, pid2;
char buf[MSG_SIZE];
ssize_t n;
// 1. 建立管道
if (pipe(pipe_fd) < 0) {
perror("pipe");
exit(EXIT_FAILURE);
}
// 2. 創建第一個子進程 P1
pid1 = fork();
if (pid1 < 0) {
perror("fork P1");
exit(EXIT_FAILURE);
}
if (pid1 == 0) { // --- 子進程 P1 ---
close(pipe_fd[0]); // 關閉讀端
const char *msg1 = "Child 1 is sending a message!";
write(pipe_fd[1], msg1, strlen(msg1) + 1); // 含結束符
close(pipe_fd[1]);
exit(EXIT_SUCCESS);
}
// 3. 創建第二個子進程 P2
pid2 = fork();
if (pid2 < 0) {
perror("fork P2");
exit(EXIT_FAILURE);
}
if (pid2 == 0) { // --- 子進程 P2 ---
close(pipe_fd[0]);
const char *msg2 = "Child 2 is sending a message!";
write(pipe_fd[1], msg2, strlen(msg2) + 1);
close(pipe_fd[1]);
exit(EXIT_SUCCESS);
}
// --- 父進程 ---
close(pipe_fd[1]); // 關閉寫端,關鍵:讓 read 能讀到 EOF
// 4. 依序讀取兩條訊息
for (int i = 0; i < 2; ++i) {
n = read(pipe_fd[0], buf, MSG_SIZE);
if (n > 0) {
printf("父進程收到: %s\n", buf);
} else if (n == 0) {
printf("管道已關閉,無更多資料\n");
break;
} else {
perror("read");
}
}
close(pipe_fd[0]);
// 5. 回收子進程,避免僵屍
waitpid(pid1, NULL, 0);
waitpid(pid2, NULL, 0);
return 0;
}
read 永遠不會返回 EOF,導致父進程永久阻塞。read 兩次,配合子進程依序 fork,自然實現「先 P1 後 P2」的讀取順序。若需嚴格同步,可引入信號或另一管道做確認。\0,讀端以字串方式處理;實際專案常採用「長度前綴」或「定界符」協議。perror 輸出錯誤原因。| 問題 | 原因 | 解決 |
|---|---|---|
父進程 read 永久阻塞 | 寫端未全部關閉(子進程或父進程自身持有寫端) | 父進程 close(pipe_fd[1]),子進程寫完即 close |
| 子進程收到 SIGPIPE 終止 | 讀端已關閉仍嘗試 write | 確保讀端在寫入期間保持開啟;或捕獲/忽略 SIGPIPE |
| 資料截斷/亂序 | 多進程並發寫入超過 PIPE_BUF(通常 4KB) | 單次 write 不超過 PIPE_BUF 保證原子性;大數據分塊寫入 |
| 僵屍進程堆積 | 父進程未 wait/waitpid | 父進程循環 waitpid(-1, NULL, WNOHANG) 或安裝 SIGCHLD 處理器 |
// 建立
mkfifo("/tmp/myfifo", 0666);
// 打開(阻塞直到另一端也打開)
int fd = open("/tmp/myfifo", O_RDONLY); // 讀端
int fd = open("/tmp/myfifo", O_WRONLY); // 寫端
適合無親緣關係進程通信,用法與無名管道幾乎相同。
無名管道雖簡單,卻是 Linux 進程通信的基石。掌握 pipe/fork/read/write/close/waitpid 的協作模式,並注意「關閉未用端點」這一核心原則,即能編寫出穩健高效的管道通信程式。
Magento 檔案與目錄權限設定完整指南:SSH、後台與 PHP 三種方式
Linux 網路配置基礎教學:路由表、介面設定與 DNS 解析
Linux Root 密碼重置實戰:單用戶模式與 GRUB 中斷啟動流程詳解
Linux usermod 指令詳解:修改用戶帳號屬性 (家目錄、Shell、群組、UID、鎖定/解鎖)
VMware Shared Folders 實現 Windows 宿主機與 Linux 客體機檔案互傳完整設定
Linux Shell 深度解析:終端機、解釋器、父子 Shell、變數作用域與啟動配置全景圖
Linux 核心編譯 ld: cannot find /ubuntu/omnibook/sections.lds 錯誤修正:Makefile 路徑修正