一、前言

管道是 Linux 最经典的进程间通信 IPC 方式,分为两种:

  1. 无名管道(匿名管道 pipe)
  2. 有名管道(命名管道 FIFO)

很多初学者分不清:什么时候用无名管道?什么时候用有名管道?区别在哪、怎么用、适用场景是什么?这篇一次性讲透,带原理 + 特点 + 代码实战。

二、无名管道(匿名管道)

1. 什么是无名管道

无名管道是 Linux 内核开辟的一块内存缓冲区,没有实体文件、没有文件名,只存在于内存中

2. 核心特性

  1. 只能用于具有亲缘关系的进程通信(父子进程、兄弟进程);
  2. 半双工通信:单向数据流动,一端读、一端写;
  3. 没有磁盘文件,随进程消亡自动释放
  4. 只能血缘进程共享文件描述符实现通信;
  5. 占用两个文件描述符:fd[0] 读端、fd[1] 写端。

3. 工作原理

  1. 父进程调用 pipe(fd) 创建管道,内核分配缓冲区;
  2. fork() 创建子进程,子进程复制父进程的文件描述符表
  3. 父子进程约定:一个关闭读端只写,一个关闭写端只读;
  4. 数据在内核缓冲区流转,不落地磁盘。

4. 无名管道使用限制

  • 不能无亲缘关系进程通信;
  • 只能单向通信,要双向得建两个管道;
  • 没有文件实体,只能通过继承 fd 通信。

5. 无名管道 C 语言实战代码

c

运行

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

int main()
{
    int fd[2];
    pid_t pid;
    char buf[1024] = {0};
    const char *msg = "Hello 无名管道";

    // 1. 创建无名管道
    if(pipe(fd) == -1)
    {
        perror("pipe error");
        exit(EXIT_FAILURE);
    }

    // 2. 创建子进程
    pid = fork();
    if(pid < 0)
    {
        perror("fork error");
        exit(EXIT_FAILURE);
    }

    if(pid > 0)  // 父进程:写数据
    {
        close(fd[0]);  // 关闭读端
        write(fd[1], msg, strlen(msg));
        close(fd[1]);
        wait(NULL);    // 等待子进程结束
    }
    else  // 子进程:读数据
    {
        close(fd[1]);  // 关闭写端
        read(fd[0], buf, sizeof(buf));
        printf("子进程收到:%s\n", buf);
        close(fd[0]);
    }

    return 0;
}

编译运行:

bash

运行

gcc pipe.c -o pipe
./pipe

三、有名管道(FIFO 命名管道)

1. 什么是有名管道

有名管道也叫 FIFO,是存在于磁盘上的特殊文件,类型标识为 p

有文件名、有路径,任何进程都可以通过路径打开通信

2. 核心特性

  1. 支持无亲缘关系进程间通信;
  2. 依然是半双工单向通信
  3. 文件形式存在磁盘,持久保存,不删就一直在;
  4. 遵循 先进先出 规则;
  5. 可以跨程序、跨终端、跨无关进程通信。

3. 创建方式

  1. 命令行手动创建:

bash

运行

mkfifo my_fifo

查看管道:

bash

运行

ls -l
# 开头带 p 就是命名管道
prw-r--r-- 1 zhiwan zhiwan 0 5月  7 10:00 my_fifo
  1. 代码函数创建:mkfifo(路径, 权限)

4. 通信原理

  1. 通过 mkfifo 创建管道特殊文件;
  2. 一个进程以只写方式打开,一个进程以只读方式打开
  3. 数据在内核缓冲区传输,不实际写入磁盘
  4. 进程退出后,管道文件不会自动消失,需手动 unlinkrm 删除。

5. 有名管道线程 / 进程通信代码(你刚才运行成功的版本简化版)

c

运行

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <pthread.h>

#define FIFO_PATH "./my_named_pipe"

// 写线程
void *write_func(void *arg)
{
    // 创建有名管道
    mkfifo(FIFO_PATH, 0666);
    int fd = open(FIFO_PATH, O_WRONLY);
    
    char msg[] = "Hello 有名管道";
    write(fd, msg, sizeof(msg));
    
    close(fd);
    pthread_exit(NULL);
}

// 读线程
void *read_func(void *arg)
{
    sleep(1);
    int fd = open(FIFO_PATH, O_RDONLY);
    
    char buf[1024] = {0};
    read(fd, buf, sizeof(buf));
    printf("读取到:%s\n", buf);
    
    close(fd);
    unlink(FIFO_PATH);  // 删除管道文件
    pthread_exit(NULL);
}

int main()
{
    pthread_t t1, t2;
    pthread_create(&t1, NULL, write_func, NULL);
    pthread_create(&t2, NULL, read_func, NULL);
    
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    return 0;
}

四、无名管道 VS 有名管道 核心对比

表格

对比项 无名管道 pipe 有名管道 FIFO
存在形式 内存缓冲区,无磁盘文件 磁盘特殊文件,有路径文件名
通信进程 仅限亲缘进程(父子) 任意无亲缘进程
创建方式 pipe() mkfifo() / 命令 mkfifo
生命周期 进程全部退出自动销毁 手动删除才消失
访问方式 继承文件描述符 文件路径打开
适用场景 父子进程简单通信 不同程序、跨终端进程通信
半双工 单向通信 单向通信

五、关键知识点总结

  1. 无名管道:无文件、只血缘进程、随进程销毁,简单轻量;
  2. 有名管道:有实体管道文件、任意进程可通信、生命周期持久;
  3. 管道都是半双工,单向流动,双向通信要建两个管道;
  4. 有名管道程序结束后如果不 unlink,文件会一直留在目录里;
  5. 管道数据走内核缓冲区,不会真正落地磁盘,效率高。

六、适用场景选择

  • 父子进程简单通信 → 用 无名管道
  • 两个独立程序、不同终端、无关进程要通信 → 用 有名管道 FIFO
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐