当前位置: 首页 > news >正文

Linux系统:进程间通信-匿名与命名管道

本节重点

  • 匿名管道的概念与原理
  • 匿名管道的创建
  • 命名管道的概念与原理
  • 命名管道的创建
  • 两者的差异与联系
  • 命名管道实现EchoServer

一、管道

管道(Pipe)是一种进程间通信(IPC, Inter-Process Communication)机制,用于在不同进程之间传递数据。它是一种抽象的数据通道,允许一个进程的输出直接作为另一个进程的输入,类似于现实中的管道将水流从一个地方输送到另一个地方。管道是操作系统提供的一种高效、轻量级的通信方式,广泛应用于命令行工具、父子进程协作等场景。

在Linux中我们知道,当一个指令运行起来时就是一个进程。who命令用来显示当前登录系统的用户信息,而wc是一个统计工具其中-l选项表示统计行数。当两个指令分别运行时就是两个进程,我们可以通过 | (管道)将who指令运行的结果传递给wc,此时wc就可以帮我们统计当前登录系统的用户个数。

who | wc -l

可以用下图来表示这种关系:

 

二、匿名管道

匿名管道(Anonymous Pipe)是一种进程间通信(IPC, Inter-Process Communication)机制,用于在具有亲缘关系的进程(如父子进程或兄弟进程)之间传递数据。它是一种单向的、半双工的通信通道,数据只能在一个方向上流动(若需双向通信,需创建两个管道)。匿名管道没有名字标识,因此仅适用于具有亲缘关系的进程,无法用于无关进程间的通信。

半双工与全双工:

半双工通信允许数据在两个方向上传输,但同一时刻只能单向传输。即通信双方可以轮流发送和接收数据,但不能同时进行。

全双工通信允许数据在两个方向上同时传输。即通信双方可以同时发送和接收数据。

特性半双工全双工
传输方向同一时刻只能单向传输双向同时传输
效率较低(需切换方向)较高(无需切换方向)
延迟较高(方向切换可能引入延迟)较低(无方向切换)
资源占用较低(单方向占用资源)较高(双方向占用资源)
应用场景对讲机、老式电台、早期网络电话、现代网络、USB接口

2.1 管道的创建

在Linux系统中我们可以系统调用pipe来创建管道文件,返回值是两个文件描述符分别表示文件的读端与写端。

函数原型:

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

参数解析:

  • fd 是一个长度为2的整数数组,用来存储管道文件的读端与写端的文件描述符
  • fd[ 0 ]:管道的读端(用于读取数据)。
  • fd[ 1 ]:管道的写端(用于写入数据)。

返回值:成功时返回 0,失败时返回 -1 并设置 errno。

数据传输的特点:

  • 写操作:进程通过write(pipefd[1], data, len)将数据拷贝到内核缓冲区。若缓冲区满,写进程阻塞(默认阻塞模式)。
  • 读操作:进程通过read(pipefd[0], buffer, size)从内核缓冲区拷贝数据到用户空间。若缓冲区空,读进程阻塞(默认阻塞模式)

管道的关闭:

  •  读端先关闭:此时写入无意义,操作系统会向写进程发送SIGPIPE信号终止进程。
  • 写段先关闭:此时读进程会读到EOF,用于指示文件或数据流已到达末尾。

代码示例:父子进程通过匿名管道实现进程间通信

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
int main()
{int fd[2] = {0};int ret = pipe(fd);if (ret == -1){printf("pipe error!\n");}pid_t id = fork();if (id > 0){// 父进程读close(fd[1]);while (1){char read_buf[1024];int ret = read(fd[0], read_buf, sizeof(read_buf));if (ret > 0){read_buf[ret] = '0';printf("child say: %s", read_buf);}else if (ret == 0){// 写端退出:break;}else{printf("read error!\n");}}//退出前记得回收子进程waitpid(id,NULL,0);}else if (id == 0){// 子进程写close(fd[0]);int cnt=5;while (cnt){char buffer[1024];read(0, buffer, sizeof(buffer));int ret = write(fd[1], buffer, sizeof(buffer));cnt--;}exit(1);}else{// 子进程创建失败:printf("fork error!\n");}return 0;
}

代码说明:

当父进程调用pipe系统调用时,内核会创建一个管道文件(或开辟一段环形缓冲区)并暴露读端与写端。进程通过分配文件描述符来控制管道的读写操作,我们通过一个整数数组来记录两个文件描述符,通常下标为0的元素记录读端的文件描述符,下标为1的元素记录写端的文件描述符。

之后我们fork创建子进程时操作系统会拷贝一份文件描述符表继承给子进程,此时父子进程就可以看到同一个管道文件。之后父子进程根据读写工作的分配关闭不需要的文件描述符后就可以进行进程间通信了。

结束进程间通信时可以关闭写进程,此时读进程就会读到EOF也就是文件末尾,此时read的返回值为0,我们可以根据read的返回值判断写端是否关闭来结束读端。最后不要忘记父进程等待子进程的返回结果。

 2.2 原理与特性

2.2.1 工作原理

匿名管道本质是内核维护的环形缓冲区(或文件),用于临时存储待传输的数据。缓冲区大小由系统决定(如Linux默认通常为64KB),数据以先进先出(FIFO)方式处理。与以往的缓冲区不同的是这类缓冲区由内核提供且不会刷盘(将数据刷新到磁盘)。

2.2.2 基础特性

单向通信

默认是半双工(单向),若需双向通信需创建两个管道(如pipe1pipe2)。

示例:

  • 进程A通过pipe1写,进程B通过pipe1读(A→B)。
  • 进程B通过pipe2写,进程A通过pipe2读(B→A)。

阻塞行为

  • 默认阻塞:读写操作在缓冲区满/空时阻塞。
  • 非阻塞模式:通过fcntl(fd, F_SETFL, O_NONBLOCK)设置后,若缓冲区满/空,读写操作立即返回错误(EAGAINEWOULDBLOCK)。

亲缘关系限制

仅适用于父子进程或兄弟进程(通过fork()派生)。无关进程无法直接访问匿名管道(因无名字标识符)。

数据拷贝开销

数据需在用户空间和内核空间之间拷贝两次(写→内核缓冲区,内核缓冲区→读),可能影响性能。

缓冲区容量限制

若写进程速率远高于读进程,缓冲区可能填满,导致写进程阻塞。

三、命名管道

命名管道(Named Pipe),也称为FIFO(First In First Out,先进先出),是一种进程间通信(IPC,Inter-Process Communication)机制,允许不相关的进程通过文件系统中的一个特殊文件(即命名管道文件)进行数据交换。与匿名管道(Anonymous Pipe)不同,命名管道具有一个明确的名称,因此可以在不同进程之间、甚至不同用户或不同主机之间(通过网络命名管道实现)进行通信。

3.1 命名管道的创建

在Linux系统中我们可以通过系统调用mkfifo来创建和设置命名管道,具体的函数原型以及参数解析如下:

函数原型:

#include <sys/types.h>
#include <sys/stat.h>int mkfifo(const char *pathname, mode_t mode);

参数解析:

  • pathname:指定命名管道的路径名,可以是相对路径或绝对路径,默认在当前路径。
  • mode:用于指定管道的权限,是一个八进制数,表示文件的权限掩码。

 通常在设置管道文件权限之前,我们将文件掩码暂时设为0。

 返回值与错误处理:

  • 成功时返回 0,失败时返回 -1 并设置 errno 以指示错误原因。
  • 常见错误码
    • EACCES:参数 pathname 所指定的目录路径无可执行的权限。
    • EEXIST:参数 pathname 所指定的文件已存在。
    • ENAMETOOLONG:参数 pathname 的路径名称太长。
    • ENOENT:参数 pathname 包含的目录不存在。
    • ENOSPC:文件系统的剩余空间不足。
    • ENOTDIR:参数 pathname 路径中的目录存在但却非真正的目录。
    • EROFS:参数 pathname 指定的文件存在于只读文件系统内。

代码示例:客户端与服务端通过命名管道实现进程间通信

//Common.hpp
#include <iostream>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>class Name_Fifo
{
public:Name_Fifo(const std::string path, const std::string name): _Name(name), _Path(path){umask(0);_fifoname = _Path + "/" + _Name;int ret = mkfifo(_fifoname.c_str(), 0666);if (ret == 0){std::cout << "mkfifo success!\n"<< std::endl;}else{std::cout << "mkfifo fail!\n"<< std::endl;exit(1);}}~Name_Fifo(){unlink(_fifoname.c_str());}private:std::string _Path;std::string _Name;std::string _fifoname;
};// 对指定命名管道进行的操作
class oper_fifo
{
public:oper_fifo(const std::string path, const std::string name): _Path(path), _Name(name), _fd(-1){_fifoname = _Path + "/" + _Name;}~oper_fifo(){}void OpenForRead(){_fd = open(_fifoname.c_str(), O_RDONLY);if (_fd < 0){std::cout << "open fifo failed!" << std::endl;exit(2);}else{std::cout << "open fifo success!" << std::endl;}}void OpenForWrite(){_fd = open(_fifoname.c_str(), O_WRONLY);if (_fd < 0){std::cout << "open fifo failed!" << std::endl;exit(2);}else{std::cout << "open fifo success!" << std::endl;}}void Read(){// 具体的读取操作在这里定义while (true){char rd_buffer[1024];int ret = read(_fd, rd_buffer, sizeof(rd_buffer) - 1);if (ret == 0){// 读到了文件末尾表示写入端退出:std::cout << "write exit me too!" << std::endl;break;}else if (ret < 0){std::cout << "read failed!" << std::endl;}else{rd_buffer[ret] = '0';std::cout << "Client say:" << rd_buffer << std::endl;}}}void Write(){// 具体的写入操作在这里定义while (1){std::cout<<"Please Enter:"<<std::endl;std::string messager;int cnt = 1;pid_t id = getpid();std::getline(std::cin, messager);messager += (", message number: " + std::to_string(cnt++) + ", [" + std::to_string(id) + "]");int ret=write(_fd,messager.c_str(),messager.size());}}void Close(){if(_fd>0){close(_fd);}}
private:std::string _Path;std::string _Name;std::string _fifoname;int _fd; // 命名管道的文件描述符
};
//Server.cc
#include"Common.hpp"int main()
{//创建管道:Name_Fifo Fifo(".","myfifo");oper_fifo my_read(".","myfifo");my_read.OpenForRead();my_read.Read();my_read.Close();return 0;
//Client.cc
#include"Common.hpp"int main()
{oper_fifo my_write(".","myfifo");my_write.OpenForWrite();my_write.Write();my_write.Close();return 0;
}

代码说明:

在我们的代码中,Common.hpp中定义了两个类一个是命名管道Name_Fifo,一个是对命名管道的操作oper_fifo。在服务端我们创建命名管道后进行读操作,此时由于写端还没有将管道文件打开服务端会阻塞于my_read.OpenForRead();代码处。

这时我们运行客户端代码,需要注意的是客户端不再需要进行mkfifo而是直接对创建的命名管道进行写操作。此时当客户端以写方式打开命名管道时并等待用户输入时,读端才会解除阻塞状态。

3.2 原理与特性

3.2.1 核心特性

命名管道在文件系统中以特殊文件形式存在,进程通过路径名访问,无需亲缘关系(如父子进程)。

单个命名管道默认是单向的,但可通过创建两个管道(一个读、一个写)实现双向通信。

读操作:若管道无数据,读取进程会阻塞,直到有数据写入。写操作:若管道缓冲区满,写入进程会阻塞,直到有空间可用。同步:通过内核的等待队列管理阻塞进程,确保数据有序传输。

命名管道独立于创建它的进程,即使进程退出,管道仍存在,直到被显式删除(如Linux的unlink)。

3.2.2 工作原理

创建与初始化

通过mkfifomknod创建,内核在文件系统中生成一个inode,但不占用磁盘空间,仅维护管道的元数据(如缓冲区大小、等待队列)。

数据传输流程

写入端:进程调用write,数据被复制到内核缓冲区。若缓冲区满,写入进程阻塞。

读取端:进程调用read,从内核缓冲区复制数据到用户空间。若缓冲区空,读取进程阻塞。

内核缓冲:数据在内核中临时存储,确保读写操作的原子性。

进程阻塞与唤醒

内核通过等待队列管理阻塞的读写进程。当数据可用或缓冲区有空间时,内核唤醒对应的进程。

关闭与清理

所有读写端关闭后,管道被销毁(Linux)或标记为无效(Windows)。

四、区别与联系 (命名&匿名)

4.1 区别

特性命名管道(Named Pipe)匿名管道(Anonymous Pipe)
命名方式通过文件系统路径命名(如/tmp/pipe\\.\pipe\name无名称,仅通过文件描述符(如fd[0]fd[1])引用
进程关系不相关进程可通过路径名通信仅限亲缘进程(如父子进程、兄弟进程)
持久性独立于进程存在,需显式删除(如unlink随进程退出而自动销毁
网络通信支持(Windows可通过网络路径访问)不支持,仅限本地进程
安全性支持访问控制(如Windows的ACL)无内置安全机制,依赖进程间信任
实现复杂度需创建管道文件(mkfifoCreateNamedPipe简单,通过pipe()系统调用自动创建
数据方向默认半双工(可通过双向管道模拟全双工)半双工,需两个管道实现双向通信
可见性在文件系统中可见(如ls命令可列出)不可见,仅在内核中维护
典型应用场景跨进程、跨网络通信(如日志服务、远程服务)本地进程间短生命周期通信(如父子进程协作)

命名与路径

命名管道通过文件系统路径标识,类似普通文件,但仅用于通信。匿名管道无路径,通过文件描述符传递,通常由pipe()系统调用创建。

进程关系

命名管道允许无亲缘关系的进程通信(如服务进程与客户端)。匿名管道仅限有亲缘关系的进程(如fork()创建的子进程)。

持久性与清理

命名管道需手动删除(如Linux的unlink或Windows的CloseHandle)。匿名管道随进程退出自动销毁,无需额外清理。

 4.2 共同联系

特性命名管道与匿名管道的共同点
内核机制均依赖内核缓冲区存储数据,支持阻塞式I/O
数据传输方式均为字节流(默认,可通过消息模式扩展)
同步机制均通过内核的等待队列管理阻塞的读写进程
进程通信模型均属于进程间通信(IPC)机制
性能开销均涉及用户态与内核态的上下文切换
适用场景均适用于进程间数据交换(匿名适合短生命周期,命名适合长生命周期或跨进程)

4.3 总结

命名管道更灵活,适合跨进程、跨网络通信,但需手动管理生命周期。

匿名管道更简单,适合亲缘进程间短生命周期通信,无需额外清理。
根据场景选择合适的管道类型,可平衡复杂度与功能需求。

相关文章:

  • ZYNQ学习记录FPGA(二)Verilog语言
  • MCU ADC硬件设计注意事项
  • vulnyx Blogger writeup
  • Linux学习
  • 机器学习×第五卷:线性回归入门——她不再模仿,而开始试着理解你
  • 如何手撸一个最小化操作系统:从 0 到 1 掌握汇编-文件管理-内存页表-文件系统-上下文切换算法 MIT 经典教程 结合豆包ai
  • win操作系统安装C++语言开发环境之一, vscode +MinGW ,流程
  • 【1】跨越技术栈鸿沟:字节跳动开源TRAE AI编程IDE的实战体验
  • Visual Studio Code 扩展
  • 图表类系列各种样式PPT模版分享
  • 使用 C# 将 Word、Excel、PDF 和 PPT文档转换为 Markdown 格式
  • 如何使用 Ansible 在 Ubuntu 24.04 上安装和设置 LNMP
  • 莫兰迪高级灰总结计划简约商务通用PPT模版
  • VmWare Ubuntu 16.04 搭建DPDK 19.08.2
  • 【无标题】湖北理元理律师事务所:债务优化中的生活保障与法律平衡之道
  • 从“安全密码”到测试体系:Gitee Test 赋能关键领域软件质量保障
  • React 第五十八节 Router中StaticRouterProvider的使用详解及案例
  • React第五十七节 Router中RouterProvider使用详解及注意事项
  • React核心概念:State是什么?如何用useState管理组件自己的数据?
  • 大模型智能体核心技术:CoT与ReAct深度解析
  • 武汉想做网站/百中搜优化软件靠谱吗
  • 网站建设与维护岗位职责/最新做做网站
  • 网页设计与制作教程 刘瑞新/广东网络seo推广公司
  • 公司网站建设重要性/推广有什么好方法
  • 网站建设好后的手续交接/google国外入口
  • 网站和网页的目的/网站优化培训学校