Windows XP Windows 7 Windows 2003 Windows Vista Windows 綜合教學 Linux 系統教學
Windows 10 Windows 8 Windows 2008 Windows NT Windows Server 電腦軟體教學
您现在的位置: Windows教程網 >> Windows教程 >  >> Linux 系統教學 >> 關於 Linux

Linux 管道機制深度解析:pipe() 系統呼叫、無名/命名管道與實戰範例

編輯:關於 Linux

管道 是 Unix/Linux 進程間通訊 (IPC) 最經典的機制。「一切皆檔案」哲學下,管道以檔案描述符抽象資料流,實現「生產者-消費者」模型。本文深入剖析 pipe() 系統呼叫、無名管道與命名管道 (FIFO) 的原理、API、同步特性與完整 C 範例。

一、核心概念對比

特性無名管道命名管道
建立方式pipe(int fd[2]) 系統呼叫mkfifo() / mknod() / Shell mkfifo 指令
存在形式僅在記憶體 (核心緩衝區)檔案系統中有 inode (路徑可見)
通訊雙方親緣進程 (父子、兄弟)任意無關進程 (知路徑即可)
生命週期隨進程結束銷毀檔案刪除前持久存在
方向性單向 (fd[0]讀, fd[1]寫)單向 (開啟模式決定)

二、pipe() 系統呼叫詳解

#include <unistd.h>

int pipe(int fd[2]);
  • 參數:fd 為長度 2 的整數陣列,函數成功返回時:
    fd[0] = 讀端檔案描述符
    fd[1] = 寫端檔案描述符
  • 返回值:0 成功,-1 失敗 (設定 errno)。
  • 常見錯誤:EMFILE (進程檔案描述符用盡)、ENFILE (系統檔案表滿)、EFAULT (指標無效)。

管道容量:Linux 管道預設 64 KB (可用 fcntl(fd, F_SETPIPE_SZ, size) 調整,最大 /proc/sys/fs/pipe-max-size,預設 1 MB)。寫入滿時阻塞,讀取空時阻塞 —— 內建同步機制。

三、無名管道完整範例:父子進程通訊

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>

#define MSG "Hello from parent via pipe!"

int main() {
    int fd[2];
    pid_t pid;
    char buf[128];

    // 1. 建立管道 (必須在 fork 前)
    if (pipe(fd) == -1) {
        perror("pipe");
        exit(EXIT_FAILURE);
    }

    // 2. 建立子進程
    pid = fork();
    if (pid == -1) {
        perror("fork");
        exit(EXIT_FAILURE);
    }

    if (pid == 0) {          // ===== 子進程:讀取 =====
        close(fd[1]);        // 關閉寫端
        ssize_t n = read(fd[0], buf, sizeof(buf)-1);
        if (n > 0) {
            buf[n] = '\0';
            printf("[Child] Received: %s\n", buf);
        }
        close(fd[0]);
        exit(EXIT_SUCCESS);
    } else {                 // ===== 父進程:寫入 =====
        close(fd[0]);        // 關閉讀端
        write(fd[1], MSG, strlen(MSG));
        close(fd[1]);        // 關閉寫端 → 子進程 read 返回 0 (EOF)
        wait(NULL);          // 回收子進程
        printf("[Parent] Done.\n");
    }
    return 0;
}

關鍵點解析:

  1. fork 前建立管道:子進程自動繼承父進程的檔案描述符表,雙方持有同一管道的兩端。
  2. 必須關閉無用端:父關讀端、子關寫端。若不關閉,讀端永遠不會收到 EOF (因寫端仍開著),導致 read 永久阻塞。
  3. 單向性:需雙向通訊請建立兩個管道。

四、命名管道 (FIFO) 建立與使用

1. Shell 建立:

mkfifo /tmp/my_fifo
ls -l /tmp/my_fifo  # prw-r--r--  p 表示 FIFO

2. C 程式建立:

#include <sys/stat.h>
#include <fcntl.h>

mkfifo("/tmp/my_fifo", 0666);  // 返回 0 成功,-1 失敗 (EEXIST 已存在)

3. 開啟模式 (關鍵):

  • open(path, O_RDONLY):阻塞直到有寫端開啟。
  • open(path, O_WRONLY):阻塞直到有讀端開啟。
  • open(path, O_RDONLY | O_NONBLOCK):非阻塞,無寫端時返回 -1 (ENXIO)。

4. 範例:兩個獨立程式通訊

writer.c

#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main() {
    int fd = open("/tmp/my_fifo", O_WRONLY);
    if (fd == -1) { perror("open"); return 1; }
    write(fd, "Hello FIFO!\n", 12);
    close(fd);
    return 0;
}

reader.c

#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

int main() {
    int fd = open("/tmp/my_fifo", O_RDONLY);
    if (fd == -1) { perror("open"); return 1; }
    char buf[64];
    ssize_t n = read(fd, buf, sizeof(buf)-1);
    if (n > 0) { buf[n] = '\0'; printf("Received: %s", buf); }
    close(fd);
    return 0;
}

編譯:gcc writer.c -o writer && gcc reader.c -o reader
執行:終端 1 執行 ./reader (阻塞等待) → 終端 2 執行 ./writer → reader 印出訊息並結束。

五、進階主題

原子性保證

POSIX 規定:寫入 ≤ PIPE_BUF (Linux 4096 bytes) 的資料是原子的,不會與其他寫入交織。大於此值可能分段寫入。

管道與 Shell 管線 |

Shell cmd1 | cmd2 內部即透過 pipe() + fork() + dup2() 實現:將 cmd1 標準輸出重導向至管道寫端,cmd2 標準輸入重導向至管道讀端。

效能考量

  • 管道避免磁碟 I/O,資料在核心緩衝區流動,延遲極低 (微秒級)。
  • 大量資料傳輸優於 Socket (無協議開銷),但僅限本機進程間。

六、常見錯誤與除錯

現象原因修正
read 永久阻塞寫端未關閉 (含父進程未關閉 fd[1])所有寫端進程必須關閉寫端
SIGPIPE 導致進程終止讀端已關閉,仍嘗試寫入捕捉/忽略 SIGPIPE,或檢查 write 返回 -1 && errno==EPIPE
命名管道 open 卡住對端未開啟 (預設阻塞模式)先開讀端再開寫端,或用 O_NONBLOCK
資料亂序/交織多進程並發寫入超過 PIPE_BUF自行加鎖,或改用 Socket/訊息佇列

選型建議:親緣進程單向流 → pipe();無關進程/持久化/多對多 → 命名管道 / Unix Domain Socket / System V 訊息佇列 / POSIX 訊息佇列。

Copyright © Windows教程網 All Rights Reserved