【linux】 Linux 匿名管道:从 pipe 调用到通信测试(可运行代码 + 原理剖析)
前言:欢迎各位光临本博客,这里小编带你直接手撕**,文章并不复杂,愿诸君**耐其心性,忘却杂尘,道有所长!!!!
IF’Maxue:个人主页
🔥 个人专栏:
《C语言》
《C++深度学习》
《Linux》
《数据结构》
《数学建模》
⛺️生活是默默的坚持,毅力是永久的享受。不破不立!
文章目录
为什么要通信
- 数据传输:进程间传递信息
- 资源共享:多个进程共用一份资源
- 通知事件:一个进程告知另一个进程发生了特定事件
- 进程控制:管理其他进程的运行状态

怎么通信
- 进程间通信的本质:让不同进程先“看到”同一份资源,有了这个基础才能通信。
- 这份“内存”不能由任何进程自己提供,必须由操作系统分配——我们通过系统调用来操作这份内存,同时还要约定好通信的标准。
- 写代码前,得先定好通信的“规矩”(标准)。
- 关于“标准”:
- 制定标准往往需要竞争:某个公司在技术上形成绝对领先,才能让其他公司认可并遵循。
- 新技术一定会催生新的行业形态。
- 从内卷、跟风到全员遵循,最终形成统一标准。
- 关于“标准”:
什么是通信
概念:

进程间通信,简单说就是运行在同一台或不同计算机上的多个进程,进行数据交换的技术。
每个进程都有自己的地址空间,无法进行彼此间的访问,我们借助一个机制来实现他们间的访问就叫做通信
这里我们只是提及一下具体通信概念,并不深入,后续网络部分会细讲这两个通信方式
- 具体通信方式
- 基于文件的:管道通信
- System V:本机内进程通信
匿名管道(基于血缘关系的通信)
背景:
我们把视角拉到通信的起源,当时各个工程师只是界定好了进程的概念,但是他们遇到一个棘手的事情,这些进程只能各自运行各自的,两个进程之间无法进行数据沟通,首先关注的进程就是父子进程之间的通信,经过机制的设计和商讨,诞生了匿名管道机制
- 早期的进程通信多基于文件,管道就是其中一种。
- 管道结构示意图:

- 匿名管道:主要用于父子进程间的通信和具有血缘关系的进程之间的通信,看下方的结构图,我们知道,子进程是以父进程为模板进行运行的,大部分的数据和代码拷贝于父进程。
- 父进程的mm_struct中有一块指针指向fd_array文件描述符表,从下标3开始每一块磁盘文件都有着自己的struct_file统一在fd_array中进行管理。
- 子进程的fd_array文件描述表都拷贝于父进程的,但是在文件管理中的文件内容不需要进行拷贝。

- 父子进程的文件描述表(记录打开文件信息的表)是相同的,它们共享同一个文件结构体(struct file)。

- 我们把让不同进程“看到”同一份资源的载体,称为管道。
- 虚拟示意图(注意:缓冲区不算管道本身):

- 管道是内存级的,和磁盘没有关系。不存在于磁盘当中
原理
- 匿名管道通常用于父子进程的单向通信。
- 我们对匿名管道进行讲解:
- 父进程创建管道,同时接入读写端
- fork出子进程,也接入子进程的读写端
- 根据需要关闭父子进程的读写端
- 创建管道
- 父子进程同时“接入”管道
- 关闭不需要的读写端
- 管道是操作系统单独设计的(可复用代码),通过专门的系统调用
pipe创建(pipe是输出型参数)。
在管道中我们就可以实现两者的交互,这就好比讲话的例子,一个人输出,一个人输入才能时间交互。管道就相当于空气—传播渠道

通信步骤:

匿名管道没有路径和文件名,只存在于内存中,因此称为“匿名”。
- 如何保证两个进程打开的是同一个管道?
子进程会继承父进程的文件描述符表,因此能共享同一个管道。
代码测试
核心目标:让不同进程(这里是父子进程)看到同一份资源(管道)。
先看一个简单的测试:创建管道后,打印管道的读写端文件描述符。
#include<iostream>#include<unistd.h>intmain(){int fds[2]={0};int n =pipe(fds);if(n <0){ std::cerr <<"pipe error"<< std::endl;return1;} std::cout <<"fds[0]: "<< fds[0]<< std::endl; std::cout <<"fds[1]: "<< fds[1]<< std::endl;return0;}预期结果:父进程和子进程打印的文件描述符都是3(读端)和4(写端)。
实际运行结果:

说明父子进程确实共享了同一个管道的文件描述符。
通信测试:子写父读
步骤解析:
- 创建管道:用
pipe函数,得到fds[0](读端)和fds[1](写端)。 - 创建子进程:用
fork,子进程继承父进程的文件描述符表。 - 关闭不需要的端:父进程关闭写端(
fds[1]),子进程关闭读端(fds[0])(管道是文件,不用的端要及时关闭,避免资源浪费)。
代码示例(核心逻辑):
#include<stdio.h>#include<unistd.h>#include<string.h>intmain(){int fds[2];// 1. 创建管道if(pipe(fds)<0){perror("pipe error");return1;}// 2. 创建子进程pid_t pid =fork();if(pid <0){perror("fork error");return1;}if(pid ==0){// 子进程:写数据// 关闭读端(子进程只写)close(fds[0]);char buf[128];snprintf(buf,sizeof(buf),"hello, father! I'm child, pid: %d",getpid());// 系统调用write不会自动加\0,需要确保数据长度正确write(fds[1], buf,strlen(buf));close(fds[1]);// 写完关闭写端}else{// 父进程:读数据// 关闭写端(父进程只读)close(fds[1]);char buf[128]={0};// 初始化,方便后续打印时自动识别字符串结尾ssize_t n =read(fds[0], buf,sizeof(buf)-1);// 留一个位置给\0if(n >0){ buf[n]='\0';// 手动加\0,确保字符串完整printf("father read: %s\n", buf);}close(fds[0]);// 读完关闭读端}return0;}注意点:
- C语言的
sizeof计算字符串长度时会包含\0,但系统调用(如write、read)不会自动处理\0,需要手动添加。 - 运行结果:

- 子进程写入的数据,父进程成功读取:
