跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++

Linux 进程间通信:匿名管道原理与实现

Linux 进程间通信中,匿名管道是最基础的 IPC 方式。解析了管道的定义、核心特性及 pipe 函数创建方法,通过 fork 实现父子进程共享文件描述符进行数据交互。从内核视角阐述了管道缓冲区、file 与 inode 结构的关系,对比了管道与普通文件的异同,并提供了子写父读的实战代码示例。

颠三倒四发布于 2026/3/26更新于 2026/10/2583 浏览
Linux 进程间通信:匿名管道原理与实现

在这里插入图片描述

前言:

在 Linux 系统中,进程是资源分配的基本单位,各个进程拥有独立的地址空间,进程间的隔离性让数据无法直接互通,而进程间通信(IPC)就是打破这种隔离、实现进程数据交互和协同工作的核心技术。管道作为 Unix 系统中最古老的 IPC 方式,也是 Linux 下最基础、最常用的进程间通信手段,其设计贴合 Linux 一切皆文件的核心思想,简单易用且能满足亲缘进程间的通信需求。本文将从管道的基础概念出发,深入解析匿名管道的创建、工作原理,从文件描述符和内核视角带你吃透匿名管道的底层逻辑。

一。进程间通信基础认知

在学习管道之前,我们需要先明确进程间通信的核心目的和分类,建立对 IPC 技术的整体认知,这能帮助我们更好地理解管道的设计初衷和应用场景。

1.1 进程间通信的核心目的

进程间通信的本质是实现进程间的数据交互、资源共享和事件协同,具体可分为四个方面:

  • 数据传输:一个进程将自身数据发送给另一个进程,是最基础的 IPC 需求;
  • 资源共享:多个进程共享同一份系统资源(如文件、内存),提高资源利用率;
  • 通知事件:进程向其他进程发送事件通知,如子进程退出时通知父进程、进程完成任务后通知调度进程;
  • 进程控制:一个进程对另一个进程进行执行控制,如调试进程拦截目标进程的异常和陷入,实时获取其状态。

在这里插入图片描述

1.2 进程间通信的发展与分类

Linux 的 IPC 技术从 Unix 继承并不断发展,整体可分为三大类,管道是其中最基础的一类:

  • 管道:包括 匿名管道(pipe) 和 命名管道(FIFO),是最基础的 IPC 方式,基于文件系统实现;
  • System V IPC:包括共享内存、消息队列、信号量,由 System V 系统引入,基于内核的 IPC 资源管理实现;
  • POSIX IPC:遵循 POSIX 标准的 IPC 方式,是对 System V IPC 的改进,包括 POSIX 共享内存、消息队列、信号量等。

管道作为最原始的 IPC 方式,虽然功能简单,但却是理解 Linux 进程间通信和文件系统的关键,也是实现其他复杂 IPC 的基础。

在这里插入图片描述

二。管道的基础概念

2.1 管道的定义

管道是一种半双工的数据流通信方式,本质是内核中的一块缓冲区,它将一个进程的标准输出与另一个进程的标准输入相连,形成一条单向的数据流通道。我们可以把管道理解为进程间的 '一根水管',数据从一端写入,从另一端读出,实现单向的通信。

在 Linux 命令行中,我们经常使用的管道符 | 就是管道的典型应用,例如 who | wc -l:

  • who 进程的标准输出被重定向到管道的写端;

  • wc -l 进程的标准输入被重定向到管道的读端; 内核中的管道缓冲区作为中间介质,完成两个进程间的数据传递。

  • 从图中我们可以看出最后他们三个指令的父进程都是 bash 的,他们之间是具有血缘关系的进程

在这里插入图片描述

2.2 管道的核心特性

管道的设计贴合 Linux 一切皆文件的思想,其核心特性可总结为:

  • 半双工通信:数据只能沿一个方向流动,若需双向通信,需创建两个管道;
  • 基于缓冲区:管道的实质是内核缓冲区,数据写入后暂存于内核,直到被另一个进程读取;
  • 文件式操作:管道通过文件描述符操作,读写接口与文件一致(read/write),符合 Linux 文件操作规范;
  • 亲缘进程专属:匿名管道仅支持具有共同祖先的亲缘进程(父进程与子进程、兄弟进程)间通信。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

三。匿名管道的创建与 API

匿名管道是最基础的管道类型,通过系统调用 pipe 创建,其核心 API 简单且易用,是实现亲缘进程间通信的首选。

3.1 匿名管道的创建函数

#include <unistd.h>
int pipe(int pipefd[2]);

函数参数

  • pipefd:整型数组,是输出型参数,用于保存管道的读、写文件描述符:
    • pipefd[0]:管道的读端,仅用于读取管道中的数据;
    • pipefd[1]:管道的写端,仅用于向管道中写入数据。

返回值

  • 成功:返回 0;
  • 失败:返回 -1,并设置 errno 表示错误原因。

注意:调用 pipe 函数的进程会同时持有管道的读端和写端,若要实现两个进程间的单向通信,需要在进程创建后关闭各自无用的文件描述符,避免数据读写异常

在这里插入图片描述

3.2 匿名管道的简单使用示例

下面的示例实现了一个基础的匿名管道通信:从键盘读取数据写入管道,再从管道读取数据输出到屏幕,直观展示管道的读写操作。

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <unistd.h>

int main() {
    int fds[2];
    char buf[100];
    int len;
    if (pipe(fds) == -1) {
        std::perror("make pipe");
        std::exit(1);
    }
    while (std::fgets(buf, 100, stdin)) {
        len = std::strlen(buf);
        if (write(fds[1], buf, len) != len) {
            std::perror("write to pipe");
            break;
        }
        std::memset(buf, 0x00, sizeof(buf));
        if ((len = read(fds[0], buf, 100)) == -1) {
            std::perror("read from pipe");
            break;
        }
        if (write(1, buf, len) != len) {
            std::perror("write to stdout");
            break;
        }
    }
    return 0;
}

该示例中,进程自身同时完成管道的写和读操作,虽然未实现跨进程通信,但清晰展示了管道的基本读写流程:通过 fd[1] 写,通过 fd[0] 读,操作接口与普通文件完全一致。

四。基于 fork 的匿名管道跨进程通信

匿名管道本身由单个进程创建,要实现跨进程通信,需要借助 fork 函数创建子进程 —— 子进程会继承父进程的文件描述符表,从而与父进程共享同一个管道的读、写端,这是匿名管道实现亲缘进程通信的核心原理。

在这里插入图片描述

4.1 fork 共享管道的核心原理

fork 函数创建的子进程会复制父进程的文件描述符表,包括父进程创建的管道读、写端文件描述符,因此父子进程会共享同一个内核管道缓冲区,实现数据互通。其核心步骤分为三步:

  • 父进程创建管道:父进程调用 pipe 创建管道,持有 fd[0](读)和 fd[1](写)两个文件描述符;
  • 父进程 fork 创建子进程:子进程继承父进程的文件描述符表,同样持有管道的 fd[0] 和 fd[1];
  • 关闭无用的文件描述符:根据通信方向,父、子进程分别关闭无用的读 / 写端,实现单向通信。

例如要实现父进程读、子进程写,则:

  • 父进程关闭写端 fd[1],仅保留读端 fd[0];
  • 子进程关闭读端 fd[0],仅保留写端 fd[1]。

在这里插入图片描述

4.2 从文件描述符视角理解管道通信

从文件描述符的角度,我们可以更清晰地看到父子进程共享管道的过程,以父读子写为例:

✅️ 步骤 1:父进程创建管道
父进程的文件描述符表中,0、1、2 分别为标准输入、标准输出、标准错误,pipe 创建的管道分配到 3(读端 fd[0])和 4(写端 fd[1])。

父进程:0(tty) 1(tty) 2(tty) 3(pipe 读) 4(pipe 写)

✅️ 步骤 2:父进程 fork 创建子进程
子进程复制父进程的文件描述符表,此时父子进程的文件描述符 3、4 均指向同一个内核管道缓冲区。

父进程:0(tty) 1(tty) 2(tty) 3(pipe 读) 4(pipe 写)
子进程:0(tty) 1(tty) 2(tty) 3(pipe 读) 4(pipe 写)

核心关键点:父子进程的文件描述符指向同一个内核管道缓冲区,这是进程间能通过管道通信的根本原因;关闭无用描述符则是为了保证通信的单向性,避免出现数据读写的混乱。

✅️ 步骤 3:关闭无用文件描述符
父进程关闭写端 4,子进程关闭读端 3,此时管道形成单向的'子写父读'通道,数据只能从子进程写入,父进程读出。

父进程:0(tty) 1(tty) 2(tty) 3(pipe 读)
子进程:0(tty) 1(tty) 2(tty) 4(pipe 写)

核心关键点:父子进程的文件描述符指向同一个内核管道缓冲区,这是进程间能通过管道通信的根本原因;关闭无用描述符则是为了保证通信的单向性,避免出现数据读写的混乱。

4.3 子写父读的完整实战示例以及四个场景分析

下面的示例实现了子进程向管道写入字符串,父进程从管道读取并打印的功能,是'子写父读'的标准实现:

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

// 子进程写
void WriteData(int wfd) {
    int cnt = 1;
    pid_t id = getpid();
    while (true) {
        sleep(1);
        std::string message = "hello father process, ";
        message += "cnt: " + std::to_string(cnt++) + ", my pid is: " + std::to_string(id);
        write(wfd, message.c_str(), message.size());
    }
}

// 父进程:读
void ReadData(int rfd) {
    char inbuffer[1024];
    while (true) {
        ssize_t n = read(rfd, inbuffer, sizeof(inbuffer) - 1);
        if (n > 0) {
            sleep(5);
            inbuffer[n] = '\0';
            std::cout << getpid() << "# " << inbuffer << std::endl;
        }
    }
}

int main() {
    // 1. 创建管道成功
    int pipefd[2] = {0};
    int n = pipe(pipefd);
    (void)n;

    // 2. 创建子进程
    pid_t id = fork();
    if (id == 0) {
        // 3. 形成单向通信的信道
        // 子进程:写
        close(pipefd[0]); // 关掉我不用的这个读
        WriteData(pipefd[1]);
        close(pipefd[1]); // 可以关掉
        exit(0);
    } else {
        // 3. 形成单向通信的信道
        // 父进程:读
        close(pipefd[1]); // 关掉我不用的这个写
        ReadData(pipefd[0]);
        close(pipefd[0]); // 可以关掉
    }

    // 0->read 1->write
    // 0->嘴巴->读 1->笔->写
    // std::cout << "pipefd[0]: " << pipefd[0] << std::endl;
    // std::cout << "pipefd[1]: " << pipefd[1] << std::endl;
    return 0;
}

通过对上述案例进行一定程度上的修改,有一想 4 种情况,大家注意看一下,也可以根据我的截图自己改了去试一下。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

五。从内核视角看管道的本质

从文件描述符视角,我们理解了管道的使用流程,而从内核视角,我们能看透管道的底层实现 —— 管道的本质是内核中的一块缓冲区,由两个 file 结构体指向同一个 inode,贴合 Linux'一切皆文件'的设计思想。

5.1 管道的内核数据结构

在 Linux 内核中,管道的底层实现涉及三个核心数据结构:

  • file 结构体:进程的文件描述符表中的每个项都指向一个 file 结构体,记录文件的操作方式、当前偏移量等信息;
  • inode 结构体:用于描述文件的物理属性,管道的 inode 中保存了管道缓冲区的地址、大小、读写位置等核心信息;
  • 管道缓冲区:内核中的一块连续内存,是管道实际存储数据的地方。

对于匿名管道,父子进程的 fd[0] 和 fd[1] 会分别指向不同的 file 结构体,但这两个 file 结构体最终会指向同一个 inode 结构体,而该 inode 指向内核中的管道缓冲区。

在这里插入图片描述

5.2 管道的内核实现逻辑

当进程对管道执行 read/write 操作时,内核的处理逻辑如下:

  • 写操作 write(fd[1], data, len):内核将数据从进程地址空间复制到管道缓冲区,并更新 inode 中的写位置;
  • 读操作 read(fd[0], buf, len):内核将管道缓冲区中的数据复制到进程地址空间,并更新 inode 中的读位置;
  • 缓冲区同步:内核会保证管道缓冲区的读写同步,若缓冲区为空,读操作会阻塞;若缓冲区满,写操作会阻塞。

简单来说,管道的读写操作本质是进程地址空间与内核缓冲区之间的数据拷贝,而两个进程共享同一个内核缓冲区,就实现了数据的跨进程传递。

  • 补充:读写规则的一些了解,可以简单看看

在这里插入图片描述

5.3 管道与普通文件的异同

管道的操作接口与普通文件一致,但二者在底层实现和使用上有明显区别,核心对比如下:

特性管道普通文件
存储介质内核缓冲区磁盘 / 块设备
生命周期随进程(进程退出释放)随文件系统(需手动删除)
数据读写流式读写,不可随机访问支持随机访问(lseek)
共享方式仅亲缘进程通过文件描述符共享所有进程可通过路径 / 文件描述符共享
数据持久化不持久化(读出即删除)持久化(数据保存在磁盘)

但二者的核心共性是都遵循 Linux 的文件操作模型,通过文件描述符、file 结构体、inode 结构体实现操作,这也是管道能复用文件读写接口的根本原因。

六。核心要点总结

本文从进程间通信的基础出发,详细解析了 Linux 匿名管道的核心概念、创建 API、基于 fork 的跨进程通信实现,并从文件描述符和内核两个视角深入剖析了匿名管道的底层逻辑,核心要点可总结为:

  • 管道是 Linux 最基础的 IPC 方式,本质是内核中的一块缓冲区,贴合'一切皆文件'的设计思想,通过 read/write 接口操作;
  • 匿名管道通过 pipe 函数创建,返回读、写两个文件描述符,需借助 fork 实现亲缘进程间通信,核心是子进程继承父进程的文件描述符表,共享同一个内核管道缓冲区;
  • 匿名管道通信的标准流程是:创管道→fork 子进程→关闭无用文件描述符→读写通信→关闭描述符,关闭无用描述符是保证单向通信的关键;
  • 从文件描述符视角,父子进程的文件描述符指向同一个内核管道缓冲区;从内核视角,管道是由两个 file 结构体指向同一个 inode 的内核缓冲区,读写操作是进程与内核缓冲区之间的数据拷贝;
  • 匿名管道是半双工的,仅支持亲缘进程间的单向通信,若需双向通信需创建两个管道,且其生命周期随进程,数据不持久化。

在这里插入图片描述

目录

  1. 前言:
  2. 一。进程间通信基础认知
  3. 1.1 进程间通信的核心目的
  4. 1.2 进程间通信的发展与分类
  5. 二。管道的基础概念
  6. 2.1 管道的定义
  7. 2.2 管道的核心特性
  8. 三。匿名管道的创建与 API
  9. 3.1 匿名管道的创建函数
  10. 3.2 匿名管道的简单使用示例
  11. 四。基于 fork 的匿名管道跨进程通信
  12. 4.1 fork 共享管道的核心原理
  13. 4.2 从文件描述符视角理解管道通信
  14. 4.3 子写父读的完整实战示例以及四个场景分析
  15. 五。从内核视角看管道的本质
  16. 5.1 管道的内核数据结构
  17. 5.2 管道的内核实现逻辑
  18. 5.3 管道与普通文件的异同
  19. 六。核心要点总结

更多推荐文章

查看全部
  • 自然语言处理在医疗领域的应用与实战
  • C++ 右值引用与移动语义实战:从原理到完美转发
  • DeepSeek-OCR-WEBUI 部署与网页调用全流程实践
  • Linux 进程间通信进阶:管道与共享内存详解
  • 智能车竞赛实战:基于地瓜机器人的智慧医疗系统开发指南
  • Kubernetes 集群可视化管理:Kuboard 部署与实战指南
  • Python 兼职接单平台指南与技能提升建议
  • 使用 Ragas 为 RAG 生成测试集并评估特定文档表现
  • CoPaw 个人助理部署与定制实战:从零搭建专属 AI 数字搭档
  • 基于 LangChain 搭建最小化 RAG 系统实战
  • Claude Code 本地部署与核心功能解析
  • X-WRT 基于 OpenWrt 的开源路由器系统使用指南
  • Python 异步编程与协程实战
  • Manacher 算法详解:求解最长回文子串
  • GitHub Copilot 与 Claude Code 对比:功能、性能与适用场景分析
  • Android 开发者与大模型技术:机遇、挑战与学习路径
  • AI 时代初级开发者的创意生存指南:如何避免被工具化
  • Java 网络通讯核心协议详解:TCP、UDP 与 HTTP/HTTPS
  • Java 常用注解扩展对比
  • C 语言多线程与并发编程:提高程序执行效率

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online