在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;
}
運行與驗證
  1. 編譯程序
gcc fifo_writer.c -o writer
gcc fifo_reader.c -o reader
  1. 啓動讀者(打開一個終端)
./reader

輸出:

Reader: Waiting to open FIFO for reading...
Reader: FIFO opened. Waiting for data...
(此時程序會阻塞,等待寫者寫入數據)
  1. 啓動寫者(打開另一個終端)
./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'
...
  1. 關閉寫者 在寫者終端按 Ctrl+C 結束程序。
  2. 觀察讀者 讀者終端會立即輸出:
Reader: All writers have closed the FIFO. Exiting.
(程序正常退出)

實驗成功!兩個毫無關係的進程,通過一個文件系統中的節點,實現了完美的通信。

四、 深入探索:FIFO的獨特行為

FIFO的行為與匿名管道既有相似之處,也有關鍵區別:

場景

匿名管道 (Pipe) 的行為

命名管道 (FIFO) 的行為

寫端打開,但無讀端

進程收到SIGPIPE信號,異常終止

open調用會阻塞,直到有讀者打開FIFO。

讀端打開,但管道為空

read阻塞,等待數據。

read阻塞,等待數據。 (與Pipe相同)

所有寫端關閉後,讀端再讀

read返回0 (EOF)。

read返回0 (EOF)。 (與Pipe相同)

數據被讀取後

數據從管道中消失,不可重複讀取。

數據從管道中消失,不可重複讀取。 (與Pipe相同)

多進程併發

父子/兄弟間可共享。

任意多個進程可同時讀/寫同一個FIFO,數據會被競爭消費。

最重要的區別:對於寫者來説,匿名管道是“暴脾氣”,發現沒人聽(無讀端)就直接“崩潰”(SIGPIPE);而FIFO是“耐心派”,沒人聽就一直“等待”,直到聽眾出現。這個阻塞特性使得FIFO在編寫健壯的服務時更加方便。

五、 知識小結
  • 核心價值:FIFO通過在文件系統中創建一個特殊文件,解決了匿名管道只能用於有血緣關係進程的限制
  • 創建:使用mkfifo命令或mkfifo()函數。
  • 使用:像操作普通文件一樣,使用open, read, write, close
  • 阻塞特性
  • open寫端時,若無讀端則阻塞。
  • read時,若無數據則阻塞。
  • 生命週期:與文件系統同在,不隨進程退出而消失。

命名管道FIFO是Linux IPC工具箱中一把強大而靈活的瑞士軍刀。當你需要一種比匿名管道更通用,又比套接字或共享內存更簡單的通信方式時,FIFO無疑是你的最佳選擇。

Linux進程通信的“公共電話亭”:命名管道FIFO詳解_文件系統