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 進程通信:無名管道編程完整指南

編輯:Linux 教學

前言

管道 是 Unix 系統最經典的進程間通信(IPC)機制之一,實現了「生產者-消費者」模型。本文系統介紹管道原理、分類、核心系統調用,並提供一個完整的「雙子進程寫入、父進程讀取」範例程式,幫助開發者快速掌握管道編程精髓。

一、什麼是管道?

管道是一個共享檔案,連接一個寫進程與一個讀進程,資料單向流動:

  • 寫入端(句柄 fd[1]):生產者寫入資料。
  • 讀出端(句柄 fd[0]):消費者讀取資料。

核心內部以環形緩衝區實現,維護讀/寫指針,遵循先進先出(FIFO)原則,並透過鎖機制保證互斥訪問。

二、管道分類

類型建立方式命名空間適用範圍生命週期
無名管道pipe()無路徑名,僅以檔案描述符標識僅限父子進程或有親緣關係的進程進程結束時核心回收
有名管道 (FIFO)mkfifo() / mknod()檔案系統中有路徑名任意進程均可通過路徑訪問永久存在,除非手動刪除

本文專注於無名管道。

三、核心系統調用

1. pipe() —— 建立管道

#include <unistd.h>
int pipe(int filedes[2]);
  • 成功返回 0,filedes[0] 為讀端,filedes[1] 為寫端。
  • 失敗返回 -1,設定 errno。

2. read() —— 從管道讀取

ssize_t read(int fd, void *buf, size_t count);
  • 從 fd 讀取最多 count 字節到 buf。
  • 若管道為空且寫端未關閉,阻塞等待;若寫端已關閉,返回 0(EOF)。

3. write() —— 向管道寫入

ssize_t write(int fd, const void *buf, size_t count);
  • 將 buf 前 count 字節寫入 fd。
  • 若管道滿且讀端未關閉,阻塞等待;若讀端已關閉,進程收到 SIGPIPE 信號(預設終止)。

四、實驗需求與設計

目標:建立一條無名管道,創建兩個子進程 P1、P2 分別寫入訊息,父進程按順序(先 P1 後 P2)讀取並顯示。

  • P1 寫入:"Child 1 is sending a message!"
  • P2 寫入:"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;
}

六、關鍵點解析

  1. 關閉未使用的端點:每個進程只保留自己需要的端點(子進程關讀端、父進程關寫端),否則 read 永遠不會返回 EOF,導致父進程永久阻塞。
  2. 同步機制:範例中父進程按順序 read 兩次,配合子進程依序 fork,自然實現「先 P1 後 P2」的讀取順序。若需嚴格同步,可引入信號或另一管道做確認。
  3. 訊息邊界:管道是位元組流,無訊息邊界。本例在字串後附加 \0,讀端以字串方式處理;實際專案常採用「長度前綴」或「定界符」協議。
  4. 錯誤處理:所有系統調用均檢查返回值並配合 perror 輸出錯誤原因。

七、常見坑與避坑指南

問題原因解決
父進程 read 永久阻塞寫端未全部關閉(子進程或父進程自身持有寫端)父進程 close(pipe_fd[1]),子進程寫完即 close
子進程收到 SIGPIPE 終止讀端已關閉仍嘗試 write確保讀端在寫入期間保持開啟;或捕獲/忽略 SIGPIPE
資料截斷/亂序多進程並發寫入超過 PIPE_BUF(通常 4KB)單次 write 不超過 PIPE_BUF 保證原子性;大數據分塊寫入
僵屍進程堆積父進程未 wait/waitpid父進程循環 waitpid(-1, NULL, WNOHANG) 或安裝 SIGCHLD 處理器

八、擴展:有名管道 (FIFO) 速覽

// 建立
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 的協作模式,並注意「關閉未用端點」這一核心原則,即能編寫出穩健高效的管道通信程式。

Copyright © Windows教程網 All Rights Reserved