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

12【进程间通信——管道】

1 IPC是什么 & 为什么要通信

1.1 什么是进程间通信

  进程间通信(Inter-Process Communication,IPC)是指两个或多个进程之间实现数据层面的交互。由于进程的天然独立性,通信的成本相对较高。

1.2 既然成本高为什么还要通信

因为很多场景下,进程之间必须协同工作,例如:

  1. 数据传输:一个进程需要将它的数据发送给另一个进程
  2. 资源共享:多个进程之间共享同样的资源
  3. 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
  4. 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有缺陷和异常,并能够及时知道它的状态改变

1.3 通信的本质:共享一块资源

  进程间通信的核心问题是:如何让两个进程看到同一块“资源”

  这块资源为什么不能由其中一个进程来提供?——> 因为这样会破坏进程独立性,这块资源必须由操作系统提供!!!

  本质上进程在访问 IPC,就是在访问操作系统,因为操作系统不信任用户进程,所以通信的整个生命周期 —— 创建、读写、释放 ——都必须通过系统调用接口来完成!

1.4 通信方式的分类

类型方式说明
基于文件的通信匿名管道、命名管道最基础、最常见的通信方式
System V IPC消息队列、共享内存、信号量较老但仍在广泛使用的传统机制
POSIX IPC消息队列、共享内存、信号量、互斥量、条件变量、读写锁接口更标准、更现代化、更易用

2 管道文件

2.1 管道的原理

  我们知道,一个文件可以被多个进程同时打开,这就具备了“公共资源”的特性。理论上,一个进程写、另一个进程读,就可以实现进程间通信。

  但是,如果我们用普通文件作为通信媒介,数据必须写入外设(如磁盘),这将带来严重的效率问题!

  于是,出现了“内存级文件”的概念——它像文件一样出现在文件系统中,但实际上只存在于内存,不会被刷写到磁盘。这类文件就是我们所说的:管道(pipe)。

  管道本质上是一种内核缓冲区(内存空间),通过“文件接口”封装出来,用户进程可以读写它,但数据只存在于内存中,从而避免频繁访问外设,提高通信效率。

2.2 匿名管道

问题1:为什么要用“父子进程”?
  在 Linux 中,子进程通过 fork() 从父进程复制而来,它会自动继承父进程的文件描述符表(fd表)。这就为“共享一块通信资源”提供了最简单的实现路径。

问题2:为什么不能用 open() 打开一个普通文件作为通信通道

  1. open()打开的是真实存在于磁盘上的文件,会有刷盘动作
  2. 普通文件不能只存在于内存中
  3. 操作系统无法区分你是“为了存储”还是“为了通信”

所以 Linux 提供了专门的接口 pipe(),用于创建一个 匿名管道(内存级文件)

匿名管道的特点:

  • pipe(int fd[2]) 创建,会自动生成两个文件描述符:
    • fd[0]: 读端(只读权限)
    • fd[1]: 写端(只写权限)
  • 是单向通信通道,默认不支持读写共用
  • 管道没有路径,不存在于磁盘中,只存在于内核内存
  • 通常在 fork() 之后,将一个端口交给子进程,一个端口保留在父进程,实现通信

直接看代码

#include <iostream>
#include <unistd.h>#define N 2using namespace std;int main()
{int pipefd[N] = {0};int n = pipe(pipefd);if(n < 0) return 1;cout << "pipefd[0]: " << pipefd[0] << ", pipefd[1]: " << pipefd[1] << endl;// VScode里面快速注释是ctrl + /// pipefd[0]: 3, pipefd[1]: 4return 0;
}

makefile文件

testPipe:testPipe.ccg++ -o $@ $^ -std=c++11
.PHONY:clean
clean:rm -f testPipe

main.c文件:

#include <iostream>
#include <unistd.h>    // pipe 、close、fork、write的头文件
#include <stdlib.h>    // exit 的头文件
#include <string.h>    // 字符串的头文件
#include <stdio.h>     // snprintf的头文件
#include <sys/types.h> // wait的头文件
#include <sys/wait.h>#define N 2
#define NUM 1024using namespace std;// child
void Writer(int wfd)
{string s = "hello, I am child";pid_t self = getpid();int number = 0;char buffer[NUM];   // 定义的缓冲区while(true){// 构建发送字符串buffer[0] = 0;  // 字符串清空,提醒阅读代码的人,我把这个数组当字符串了// int snprintf(char *str, size_t size, const char *format, ...);// 向哪个字符串写,写多大,什么形式写进去// s.c_str():将 std::string 转为 C 风格字符串(const char*)snprintf(buffer, sizeof(buffer),"%s-%d-%d", s.c_str(), self, number++);// cout << buffer << endl;// 发送/写入给父进程    man 2 write 查2号手册write// ssize_t write(int fd, const void *buf, size_t count);write(wfd, buffer, strlen(buffer));   // 这里不加一,\0不需要往里面写入sleep(1);}
}// father
void Reader(int rfd)
{char buffer[NUM];while(true){buffer[0] = 0;// ssize_t read(int fd, void *buf, size_t count);  查2号手册 man 2 readssize_t n = read(rfd, buffer, sizeof(buffer));if(n > 0){buffer[n] = 0;   // 我是当字符串用,必须\0结尾,这里0等于 '\0'cout << "father get a message[" << getpid() << "]# " << buffer << endl;}}
}int main()
{int pipefd[N] = {0};int n = pipe(pipefd);if(n < 0) return 1;// cout << "pipefd[0]: " << pipefd[0] << ", pipefd[1]: " << pipefd[1] << endl;pid_t id = fork();if(id < 0) return 2;// chile -> w  father -> rif(id == 0){// 子进程close(pipefd[0]);// IPC codeWriter(pipefd[1]);// 写完后,写窗口也要关掉close(pipefd[1]);exit(0);  // 子进程写完后退出}// fatherclose(pipefd[1]);// IPC codeReader(pipefd[0]);pid_t rid = waitpid(id, nullptr, 0);  // 回收子进程if(rid < 0) return 3;// 读完成后,读窗口也关闭close(pipefd[0]);return 0;
}

在这里插入图片描述

2.3 匿名管道的特征

  1. 血缘关系限定:只能用于父子(或兄弟)进程通信
  2. 单向通信:一个管道只能实现单向流动;要双向通信需建立两个管道
  3. 进程协同机制:Linux 自动实现同步和互斥,保证管道数据安全
  4. 字节流特性:管道是面向字节流的,系统不关心你写了几次,它只看缓冲区里的字节
  5. 原子性写入保证:系统会确保在写入量不超过 PIPE_BUF 时,父进程不会来读
  6. 生命周期与进程绑定:匿名管道与进程生命周期一致,进程退出后管道自动销毁。

2.4 管道中的四种关键状态

情况描述
① 读写正常,管道空读端阻塞,防止读到无效数据
② 读写正常,管道满写端阻塞,防止数据覆盖
③ 写端关闭,读端读读到返回值为 0,代表 EOF,不阻塞
④ 读端关闭,写端写写操作被操作系统发送 SIGPIPE 信号终止

2.5 命名管道

  匿名管道只能用于有血缘关系的进程,而两个毫无关系的进程通信就需要命名管道。Linux 提供的命名管道(Named Pipe / FIFO),创建方式如下:

mkfifo /tmp/myfifo

命名管道的特点:

  1. 具有路径名,出现在文件系统中
  2. 使用 open() 打开,进程通过同一路径访问同一个管道文件
  3. 同样是内存级通信,不会刷盘
  4. 适合非父子进程之间的通信
  5. 内核内部只开辟一块缓冲区,不论多少进程打开,只维护一份数据

问题1:两个无血缘进程如何确保访问的是同一个命名管道?
答:只要访问的路径+文件名相同,就会访问同一个命名管道。

问题 2:多个进程同时打开同一个命名管道,操作系统会创建多个副本吗?
答:不会。操作系统内部只维护一个文件对象 + 一个缓冲区

http://www.lqws.cn/news/596701.html

相关文章:

  • Vue 响应式数据传递:ref、reactive 与 Provide/Inject 完全指南
  • 基于 Three.js 与 WebGL 的商场全景 VR 导航系统源码级解析
  • 遥感云大数据在灾害、水体与湿地领域案例及GPT应用
  • 第八章:LeRobot摄像头配置与应用指南
  • 使用GeoServer发布地图shapefi(.shp)数据
  • Spring Bean的生命周期与作用域详解
  • Vue-17-前端框架Vue之应用基础集中式状态管理pinia(二)
  • AI智能体在用户行为数据分析中有哪些应用?
  • Android 网络全栈攻略(四)—— TCPIP 协议族与 HTTPS 协议
  • Linux基本命令篇 —— grep命令
  • 基于ApachePOI实现百度POI分类快速导入PostgreSQL数据库实战
  • opencv使用 GStreamer 硬解码和 CUDA 加速的方案
  • 【cesium】基于vue-cesium开发地理空间分析应用
  • 在 Vue 3 中,如果需要显示 HTML 标签,可以使用 v-html 指令
  • android stdio 创建 mediaplayertest
  • 零信任安全管理系统产品对比介绍
  • 小米YU7使用UWB技术,厘米级定位精准迎宾,安全防破解无感控车
  • .NET测试工具Parasoft dotTEST:全兼容RMS的测试解决方案
  • 538. 把二叉搜索树转换为累加树
  • 清理 Docker 缓存占用
  • Vue 3.x 使用 “prerender-spa-plugin ” 预渲染实现网站 SEO 优化
  • 透视变换、仿射变换
  • webpack的作用是什么,谈谈你对它的理解?
  • MySQL索引失效问题
  • vue-35(使用 Jest 和 Vue Test Utils 设置测试环境)
  • 折扣点餐对接api应该如何选择?
  • React Native 0.79.4 中 [RCTView setColor:] 崩溃问题完整解决方案
  • 在线租房平台源码+springboot+vue3(前后端分离)
  • 模型部署与推理--利用python版本onnxruntime模型部署与推理
  • C++面试题精讲系列之数组排序