Linux 进程间通信之匿名管道
💓博主CSDN主页:麻辣韭菜💓
⏩专栏分类:Linux知识分享⏪
🚚代码仓库:Linux代码练习🚚
🌹关注我🫵带你学习更多Linux知识
🔝
目录
前言
一. 进程间通信介绍
1.进程间通信目的
2.进程间通信发展
3.进程间通信分类
二.管道
用fork来共享管道原理
匿名管道
进程池
前言
从进程控制篇章,我们知道了进程是具有独立性,既然各进程具有独立性,它们之间是互不联系的,那它们是怎么通过一种方式取得联系?为什么要有进程间通信?进程间通信本质是什么?
一. 进程间通信介绍
1.进程间通信目的
- 数据传输:一个进程需要将它的数据发送给另一个进程
- 资源共享:多个进程之间共享同样的资源。
- 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
- 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另
- 一个进程的所有陷入和异常,并能够及时知道它的状态改变。
2.进程间通信发展
- 管道
- System V进程间通信
- POSIX进程间通信
3.进程间通信分类
管道
- 匿名管道pipe
- 命名管道
System V IPC
- System V 消息队列
- System V 共享内存
- System V 信号量
POSIX IPC
- 消息队列
- 共享内存
- 信号量
- 互斥量
- 条件变量
- 读写锁
二.管道
管道是Linux原生能提供的,管道有两种,匿名和命名。
进程间通信的前提,是需要让不同的进程看到同一块“内存”(特定的组织结构)
所以你所谓的进程看到同一块“内存” 其实是不隶属于任何一个进程,应该更强调共享。
那如何让两个进程看到同一块“内存”?
用fork来共享管道原理
在实现之前我们需要了解一个接口函数 pipe
创建管道需要使用
pipe
函数。pipe
函数会返回两个文件描述符,分别代表着管道的两端。这两个文件描述符可以用于在父进程和子进程之间传输数据。pipefd[0]:读下标
pipefd[1]: 写下标
#include <iostream>
#include <sys/types.h>
#include <sys/wait.h>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <string>
#include <unistd.h>
#define N 2void Write(int fd)
{std::string str = "hello, I am child process";pid_t pid = getpid();int number = 0;char buf[1024];while (1){sleep(1);buf[0] = '\0';snprintf(buf, sizeof(buf), "%s-%d- %d\n", str.c_str(), number++, pid);write(fd, buf, strlen(buf));// std::cout <<number << std::endl;// if(number > 5)// break;}
}void Read(int fd)
{char buf[1024];int cnt = 0;while (1){memset(buf, 0, sizeof(buf));size_t n = read(fd, buf, sizeof(buf));if (n > 0){std::cout << "father get a message:[" << getpid() << "]#" << buf << std::endl;}else if (n == 0){printf("father read file done\n");break;}else{std::cout << "father read error" << std::endl;break;}cnt++;if (cnt > 5)break;}
}int main()
{int pipefd[N];int n = pipe(pipefd);if (n < 0){std::cout << "pipe error" << std::endl;return 1;}pid_t pid = fork();if (pid < 0){std::cout << "fork error" << std::endl;}else if (pid == 0){// child processclose(pipefd[0]);Write(pipefd[1]);close(pipefd[1]);exit(0);}else{// parent processRead(pipefd[0]);close(pipefd[0]);// wait child processstd::cout << "father close read fd: " << pipefd[0] << std::endl;sleep(5); // wait child process exitpid_t status = 0;pid_t child_pid = waitpid(pid, &status, 0);if (child_pid < 0){std::cout << "waitpid error" << std::endl;return 2;}std::cout << "wait child success: " << child_pid << " exit code: " << ((status >> 8) & 0xFF)<< " exit signal: " << (status & 0x7F) << std::endl;}sleep(3); // wait father process exitreturn 0;
}
运行代码
管道的特征:
1.具有血缘关系的进程才能进行进程间通信
2.管道只能单向通信
3.父子进程是会进程协同的,同步与互斥——保护管道内数据。
4.管道是面向字节流的 ps:这个我们后面网络在讲
5.管道是基于文件的,而文件的生命周期是随进程的。
下面我们就来挖一挖细节,基于第3点特征衍生出来的管道内的4种情况
读端正常,管道内容为空,读端就要堵塞
读端正常,管道内容写满,读端就要堵塞
读端正常,写段关闭,读端就会读到0,表明读到了文件的结尾,不会阻塞
写段正常写入,读端关闭,OS就会杀掉正在写入的进程。
子进程写代码是有sleep1秒的 而父进程是没有sleep1秒 ,从视频我们可以得出父进程在等待子进程写入到管道中,上一次数据被读到,说明管道的内容空了,而子进程休眠1秒钟这期间对应父进程阻塞1秒钟。
第二种情况 我们让写段写快一点,读段慢一点休眠5秒钟 写段不休眠
读端正常,管道内容写满,读端就要堵塞
第三种情况 我们写代码的number等于5时直接break;
读端正常,写段关闭,读端就会读到0,表明读到了文件的结尾,不会阻塞
第四情况 我们让读端变量cnt == 5时,读端退出。
从第4个结论来说确实OS会杀掉进程,资源有限,都没有人读了,写入后还要写时拷贝,浪费资源。
匿名管道
从上面我们看到3个sleep的父进程是bash 那这样我们可以知道它们是有血缘关系的,
我们在shell打命令行,执行后,然后shell解释我们的命令看到两个|直接创建两个管道,然后再程序替换 然后3个sleep根据重定向原理重定向到管道中。
所以我们以前在命令行执行的管道 | 就是传说之中的匿名管道!!!
进程池
根据前面程序控制,和本节的管道知识,我们可以用fork创建多个子进程,父进程写入,子进程读取,根据读取的内容,子进程完成一些相应的事情。这些子进程就好比池子里的水,我们要用的时候直接就可以拿来用。
代码实现
#include "task.hpp"
#include <string>
#include <unistd.h>
#include <cstdlib>#define ProcessNum 5 // 进程个数// 先描述
class channle
{
public:channle(int cmdfd, int slaverid, const std::string &processname): _cmdfd(cmdfd), _slaverid(slaverid), _processname(processname){}public:int _cmdfd; // 发送任务的文件描述符pid_t _slaverid; // 子进程的PIDstd::string _processname; // 子进程的名字
};
对于一个进程池来说,进程多了,我们肯定是要管理起来的,所以定义一个对象方面我们管理,对象定义出来了后,我们就要创建管道和子进程。
void InitProcessPool(std::vector<channel> *channels)
{std::vector<int> oldfds;for (int i = 0; i < ProcessNum; i++){// 创建管道int pipefd[2];int n = pipe(pipefd);if (n < 0){perror("pipe");exit(1);}// 创建子进程pid_t id = fork();if (id < 0){perror("fork");exit(2);}else if (id == 0){ // 子进程for (auto fd : oldfds) //关闭之前继承下来的写端{close(fd);}close(pipefd[1]); // 子进程读,关闭写端。dup2(pipefd[0], 0); // 管道的读端替换成标准输入0close(pipefd[0]);slaver();exit(0);}else{// 父进程close(pipefd[0]); // 父进程写,关闭读端。// 添加channle字段std::string name = "process-" + std::to_string(i);channels->push_back(channel(pipefd[1], id, name)); // 利用零时对象初始化oldfds.push_back(pipefd[1]); // 子进程会继承父进程的写端 方便我们在fork之后关闭写端}}
}
我们再写个Debug测试一下。
void Debug(const std::vector<channel> &channels)
{for (auto &it : channels){std::cout << it._cmdfd << ' ' << it._slaverid << ' ' << it._processname << std::endl;}
}
int main()
{std::vector<channel> channels;InitProcessPool(channels);Debug(channels);return 0;
}
5个子进程创建完毕。那么下一步就是通过cmdfd这个文件描述符父进程写入,子进程读取
我们可以用dup2,我们从键盘读入输入的内容,从管道读取。这样做的好处就是slaver这个函数不用传参
else if (id == 0){ // 子进程close(pipefd[1]); // 子进程读,关闭写端。dup2(pipefd[0],0) //管道的读端替换成标准输入0slaver();exit(0);}
slaver这个函数就是获取任务的函数,怎么获取系统调用read获取,我们通过dup2原本是从标准输入读取,现在从管道里读取。 然后执行相应的任务
void slaver()
{int cmdcode = 0;while (true){int n = read(0, &cmdcode, sizeof(int));if (n == sizeof(int)){std::cout << "slaver say@ get a cmdcode: " << getpid() << " : cmdcode:" << cmdcode << std::endl;if (cmdcode > = 0 && cmdcode < task.size())task.[cmdcode]();}if (n == 0)break;}
}
#pragma once
#include <vector>
#include <iostream>typedef void (*task_t)();void task1()
{std::cout << "lol 刷新日志" << std::endl;
}
void task2()
{std::cout << "lol 更新野区,刷新出来野怪" << std::endl;
}
void task3()
{std::cout << "lol 检测软件是否更新,如果需要,就提示用户" << std::endl;
}
void task4()
{std::cout << "lol 用户释放技能,更新用户的血量和蓝量" << std::endl;
}void LoadTask(std::vector<task_t> *tasks)
{tasks->push_back(task1);tasks->push_back(task2);tasks->push_back(task3);tasks->push_back(task4);
}
现在还有没有任务,我们可以写一个简单的函数把函数的指针放进vector这个容器中然后根据cmdcode下标访问进行函数调用。
有了任务列表我们就要派发任务,这里博主选择随机派发任务,当然你下去实现的时候可以选择轮循方式派发任务。
void ctrlSlaver(const std::vector<channel> &channels)
{while (true){std::cout << "Please Enter@ ";// 1. 选择任务int cmdcode = rand() % tasks.size();// 2. 选择进程int processpos = rand() % channels.size();std::cout << "father say: "<< " cmdcode: " << cmdcode << " already sendto " << channels[processpos]._slaverid << " process name: "<< channels[processpos]._processname << std::endl;// 3. 发送任务write(channels[processpos]._cmdfd, &cmdcode, sizeof(cmdcode));// sleep(1);}
}
最后我们还再利用wiatpid这个函数回收子进程
void QuitProcess(const std::vector<channel> &channels)
{for (const auto &c : channels){close(c._cmdfd);waitpid(c._slaverid, nullptr, 0);}
}
相关文章:

Linux 进程间通信之匿名管道
💓博主CSDN主页:麻辣韭菜💓 ⏩专栏分类:Linux知识分享⏪ 🚚代码仓库:Linux代码练习🚚 🌹关注我🫵带你学习更多Linux知识 🔝 目录 前言 一. 进程间通信介绍 1.进程间通…...
数据结构与算法学习笔记六--数组和广义表(C语言)
目录 前言 1.数组 1.定义 2.初始化 3.销毁 4.取值 5.设置值 6.完整代码 前言 这篇博客主要介绍数据结构中的数组和广义表的用法。 1.数组 在数据结构中,数组是一种线性数据结构,它由一组连续的相同类型的元素组成,每个元素都有一个唯…...
图搜索算法详解
图搜索算法详解 摘要: 图搜索算法是解决路径规划和网络分析问题的关键技术。本文将详细介绍图搜索算法的基本概念、分类以及常见的算法,如广度优先搜索(BFS)、深度优先搜索(DFS)、A*搜索等。同时ÿ…...
安卓中常见的UI控件
TextView(文本视图)EditText(编辑文本)Button(按钮)ImageView(图像视图)ImageButton(图像按钮)CheckBox(复选框)RadioButtonÿ…...

基于Labelme的背部穴位关键点制作
一、穴位定位方法 穴位定位,自春秋时期以来,通过各代医学实践的继承与发展,形成了一套较为科学的定位体系。这套体系基于经络理论,采用“寸”作为测量单位,按照人体比例来进行精确的穴位定位,主要有依据体…...

go-mysql-transfer 同步数据到es
同步数据需要注意的事项 前提条件 1 要同步的mysql 表必须包含主键 2 mysql binlog 必须是row 模式 3 不支持程序运行过程中修改表结构 4 要赋予连接mysql 账号的权限 reload, replication super 权限 如果是root 权限则不需要 安装 go-mysql-transfer git clone…...

外包干了3天,技术就明显退步了。。。。。
先说一下自己的情况,本科生,19年通过校招进入广州某软件公司,干了接近4年的功能测试,今年年初,感觉自己不能够在这样下去了,长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测试…...

将要上市的自动驾驶新书《自动驾驶系统开发》中摘录各章片段 1
以下摘录一些章节片段: 1. 概论 自动驾驶系统的认知中有一些模糊的地方,比如自动驾驶系统如何定义的问题,自动驾驶的研发为什么会有那么多的子模块,怎么才算自动驾驶落地等等。本章想先给读者一个概括介绍,了解自动驾…...
String、StringBuilder、StringBuffer之间的区别是什么?
在Java中,String、StringBuilder 和 StringBuffer 是处理字符串的三个类,其中 String 是不可变对象,而 StringBuilder 和 StringBuffer 是可变对象。这些类在字符串操作方面具有不同的特性和用途。 String String 类表示不可变的字符序列&a…...

docker系列8:容器卷挂载(上)
目录 传送门 从安装redis说起 什么是容器卷挂载 操作系统的挂载 日志文件一般是"首恶元凶" 挂载命令 容器卷挂载 卷挂载命令 启动时挂载 查看挂载卷信息 容器卷管理 查看卷列表 创建容器卷 具名挂载与匿名挂载 具名挂载 传送门 docker系列1ÿ…...

痉挛性斜颈患者自己做哪些运动对脖子好?
痉挛性斜颈(Dystonia)是一种罕见的神经系统疾病,其特点是颈部肌肉痉挛,导致头部姿势异常倾斜或扭曲。而在治疗痉挛性斜颈中,运动疗法是非常重要的一部分。下面将介绍一些痉挛性斜颈患者可以自己进行的运动,…...

数据结构——二叉树链式结构的实现(上)
二叉树概念 再看二叉树基本操作前,再回顾下二叉树的概念, 二叉树是: 1. 空树 2. 非空:根节点,根节点的左子树、根节点的右子树组成的。 从概念中可以看出,二叉树定义是递归式的 二叉树构成࿱…...
数据结构内容概览
0. 绪论 绪论01——复杂度度量 绪论02——复杂度分析 绪论03——递归分析 绪论04——算法分析 绪论05——动态规划 算法设计与优化——前n项和计算 算法设计优化——对于任意非负整数,统计其二进制展开中数位1的总数 算法设计优化——Fibonacci数 算法设计优化——…...
当Linux系统运行时间长了之后,会出现磁盘空间不足提示,需要及时进行清理
Linux系统(CentOS 7)的磁盘空间不足时,可以采取以下步骤进行清理: 查找并删除大文件: 使用du和find命令可以找到并删除大文件。例如,要查找/目录下大于100MB的文件,可以运行: find /…...

【Flask 系统教程 4】Jinjia2模版和语法
Jinjia2 模板 模板的介绍 Jinja2 是一种现代的、设计优雅的模板引擎,它是 Python 的一部分,由 Armin Ronacher 开发。Jinja2 允许你在 HTML 文档中嵌入 Python 代码,以及使用变量、控制结构和过滤器来动态生成内容。它的语法简洁清晰&#…...

与 Apollo 共创生态:七周年大会心得
与 Apollo 共创生态:七周年大会心得 前言 4月19日,百度Apollo迎来七周年,历经七年的不懈追求与创新,Apollo开放平台已陆续推出了13个版本,汇聚了来自全球170多个国家与地区的16万名开发者及220多家合作伙伴。作为一名…...

『FPGA通信接口』DDR(4)DDR3内存条SODIMMs读写测试
文章目录 前言1.MIG IP核配置2.测试程序3.DDR应用4.传送门 前言 不论是DDR3颗粒还是DDR3内存条,xilinx都是通过MIG IP核实现FPGA与DDR的读写。本文区别于DDR颗粒,记录几个与颗粒配置不同的地方。关于DDR的原理与MIG IP的简介,请查看前面文章&…...
Element UI 快速入门指南
Element UI 快速入门指南 Element UI 是一个基于 Vue.js 的组件库,提供了丰富的 UI 组件和工具,可以帮助开发人员快速构建现代化的 Web 应用程序。本文将介绍如何快速入门使用 Element UI,并展示一些常用的组件和功能。 安装 Element UI 使…...

CentOS常用命令有哪些?
目录 一、CentOS常用命令有哪些? 二、不熟悉命令怎么办? 场景一:如果是文件操作,可以使用FileZilla工具来完成 场景二:安装CentOS桌面 一、CentOS常用命令有哪些? CentOS 系统中有许多常用命令及其用法…...
cmd查看局域网内所有设备ip
说明:最近碰到一个新问题,就是有一个安卓设备,安装了一个app导致死机了,app设置了开机重启,所以,无论重启还是关机,都是进来就白屏, 这可把人愁坏了,直接死循环了 无论…...

国防科技大学计算机基础课程笔记02信息编码
1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制,因此这个了16进制的数据既可以翻译成为这个机器码,也可以翻译成为这个国标码,所以这个时候很容易会出现这个歧义的情况; 因此,我们的这个国…...
R语言AI模型部署方案:精准离线运行详解
R语言AI模型部署方案:精准离线运行详解 一、项目概述 本文将构建一个完整的R语言AI部署解决方案,实现鸢尾花分类模型的训练、保存、离线部署和预测功能。核心特点: 100%离线运行能力自包含环境依赖生产级错误处理跨平台兼容性模型版本管理# 文件结构说明 Iris_AI_Deployme…...
SciencePlots——绘制论文中的图片
文章目录 安装一、风格二、1 资源 安装 # 安装最新版 pip install githttps://github.com/garrettj403/SciencePlots.git# 安装稳定版 pip install SciencePlots一、风格 简单好用的深度学习论文绘图专用工具包–Science Plot 二、 1 资源 论文绘图神器来了:一行…...

【SQL学习笔记1】增删改查+多表连接全解析(内附SQL免费在线练习工具)
可以使用Sqliteviz这个网站免费编写sql语句,它能够让用户直接在浏览器内练习SQL的语法,不需要安装任何软件。 链接如下: sqliteviz 注意: 在转写SQL语法时,关键字之间有一个特定的顺序,这个顺序会影响到…...

【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)
🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...
CRMEB 框架中 PHP 上传扩展开发:涵盖本地上传及阿里云 OSS、腾讯云 COS、七牛云
目前已有本地上传、阿里云OSS上传、腾讯云COS上传、七牛云上传扩展 扩展入口文件 文件目录 crmeb\services\upload\Upload.php namespace crmeb\services\upload;use crmeb\basic\BaseManager; use think\facade\Config;/*** Class Upload* package crmeb\services\upload* …...
大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计
随着大语言模型(LLM)参数规模的增长,推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长,而KV缓存的内存消耗可能高达数十GB(例如Llama2-7B处理100K token时需50GB内存&a…...
管理学院权限管理系统开发总结
文章目录 🎓 管理学院权限管理系统开发总结 - 现代化Web应用实践之路📝 项目概述🏗️ 技术架构设计后端技术栈前端技术栈 💡 核心功能特性1. 用户管理模块2. 权限管理系统3. 统计报表功能4. 用户体验优化 🗄️ 数据库设…...

PLC入门【4】基本指令2(SET RST)
04 基本指令2 PLC编程第四课基本指令(2) 1、运用上接课所学的基本指令完成个简单的实例编程。 2、学习SET--置位指令 3、RST--复位指令 打开软件(FX-TRN-BEG-C),从 文件 - 主画面,“B: 让我们学习基本的”- “B-3.控制优先程序”。 点击“梯形图编辑”…...
uniapp获取当前位置和经纬度信息
1.1. 获取当前位置和经纬度信息(需要配置高的SDK) 调用uni-app官方API中的uni.chooseLocation(),即打开地图选择位置。 <button click"getAddress">获取定位</button> const getAddress () > {uni.chooseLocatio…...