Linux探秘坊-------13.进程间通信

1.进程间通信⽬的

在这里插入图片描述

2.管道

在这里插入图片描述
在这里插入图片描述

2.1 匿名管道

-----通常用来实现 父子通信

创建子进程时,需要把父进程的进程内容全部拷贝一份,但文件管理是不需要拷贝的 但是我们把父进程的文件描述符表给拷贝下来了,文件描述符表里是一堆指针他们仍然指向父进程打开的那些文件

在这里插入图片描述

  • 这也是为什么之前运行子进程会在同一个屏幕上打印内容,因为父子进程用的是同一个显示器文件自然在同一个屏幕上打印咯,
  • 和c++中遇到的 浅拷贝 十分相似

在这里插入图片描述

在这里插入图片描述

2.2 原理

在这里插入图片描述

2.2 管道样例

#include <iostream>
#include <unistd.h>using namespace std;
int main()
{int fd[2]={0};//这里使用fd模拟文件描述符表,忽略了0,1,2即标准输入stdin,标准输出stdout,标准错误stderrint n=pipe(fd);//pipe函数需要头文件unistd.hif(n<0)//运行失败会是n<0{cout<<"error"<<endl;return 1;}cout<<"fd[0]:"<<fd[0]<<endl;cout<<"fd[1]:"<<fd[1]<<endl;return 0;
}

在这里插入图片描述

最终结果是:

在这里插入图片描述

  • 因为0,1,2即标准输入,标准输出,标准错误一直在被打开,所以只能分配3,4

完整父子进程管道代码:

#include <iostream>      // 标准输入输出(cout, endl)
#include <unistd.h>      // 提供 pipe(), fork(), close(), read(), write(), sleep() 等系统调用
#include <cstdio>        // 提供 printf() 等 C 标准 I/O 函数
#include <cstring>       // 提供字符串处理函数(如 memset)
#include <sys/types.h>   // 提供 pid_t 等数据类型定义
#include <sys/wait.h>    // 提供 waitpid() 函数using namespace std;// 子进程向管道写入数据的函数
void childwrite(int wfd) {char c = 0;          // 写入的字符(这里固定为 0)int cnt = 0;         // 计数器,记录写入次数while (true) {write(wfd, &c, 1); // 向管道写入 1 字节(实际写入的是 '\0')printf("child: %d\n", cnt++); // 打印写入次数}
}// 父进程从管道读取数据的函数
void fatherread(int rfd) {char buffer[1024];    // 读取缓冲区while (true) {sleep(100);       // 父进程休眠 100 秒(实际会被 read() 打断)buffer[0] = 0;    // 清空缓冲区(可选)// 从管道读取数据(最多读 sizeof(buffer)-1 字节,预留 1 字节给 '\0')ssize_t n = read(rfd, buffer, sizeof(buffer)-1);if (n > 0) {      // 读取成功buffer[n] = 0; // 手动添加字符串结束符 '\0'std::cout << "child say: " << buffer << std::endl; // 打印读取的内容} else if (n == 0) { // 管道写端关闭(子进程退出)std::cout << "n : " << n << std::endl;std::cout << "child 退出,我也退出";break;} else {          // 读取错误break;}break;            // 测试时提前退出循环(实际应去掉)}
}int main() {// 1. 创建管道int fd[2] = {0};      // fd[0]:读端,fd[1]:写端int n = pipe(fd);     // 调用 pipe() 创建匿名管道if (n < 0) {          // 创建失败cout << "error" << endl;return 1;}cout << "fd[0]:" << fd[0] << endl; // 打印读端 fdcout << "fd[1]:" << fd[1] << endl; // 打印写端 fd// 2. 创建子进程pid_t pid = fork();   // 调用 fork() 创建子进程if (pid == 0) {       // 子进程逻辑close(fd[0]);     // 关闭读端(子进程只写)childwrite(fd[1]); // 调用子进程写入函数close(fd[1]);     // 关闭写端(实际不会执行到这里)exit(0);          // 子进程退出}sleep(5);             // 父进程休眠 5 秒(等待子进程写入数据)close(fd[1]);         // 关闭写端(父进程只读)fatherread(fd[0]);    // 调用父进程读取函数close(fd[0]);         // 关闭读端// 等待子进程退出int status = 0;int ret = waitpid(pid, &status, 0); // 阻塞等待子进程结束if (ret > 0) {        // 子进程已退出// 打印子进程退出状态(高 8 位是退出码,低 7 位是终止信号)printf("exit code: %d, exit signal: %d\n", (status>>8)&0xFF, status&0x7F);sleep(5);         // 父进程再休眠 5 秒(观察用)}return 0;
}

2.3 五种特性

在这里插入图片描述

2.4 四种通信情况

在这里插入图片描述

blog.csdnimg.cn/direct/eeef895593df4fd08b31442035e93198.png)

3.进程池的模拟

在这里插入图片描述

3.1 hpp文件的使用

#ifndef __PROCESS_POOL_HPP__  // 头文件保护宏(双下划线风格)
#define __PROCESS_POOL_HPP__#include <iostream>  // 系统头文件用尖括号<>// 函数声明/定义
void test() {std::cout << "test" << std::endl;  // 直接使用std::前缀
}#endif  // __PROCESS_POOL_HPP__

-函数的声明和定义可以放在一块写,注意头两行和末尾一行 是格式

3.2 进程池代码实现

ProcessPool.hpp:

#ifndef __PROCESS_POOL_HPP__  // 头文件保护宏,防止重复包含
#define __PROCESS_POOL_HPP__#include <iostream>      // 标准输入输出
#include <cstdlib>       // C标准库(替代stdlib.h的C++版本)
#include <vector>        // 动态数组容器
#include <unistd.h>      // POSIX API(pipe/fork/close等)
#include <sys/wait.h>    // 进程等待相关函数
#include "Task.hpp"      // 自定义任务管理头文件// Channel类:管理单个子进程的通信通道
class Channel
{
public:// 构造函数:初始化写端fd和子进程IDChannel(int fd, pid_t id) : _wfd(fd), _subid(id){// 生成通道名称(格式:channel-[fd]-[pid])_name = "channel-" + std::to_string(_wfd) + "-" + std::to_string(_subid);}// 析构函数(空实现,资源通过Close()显式释放)~Channel() {}// 向子进程发送任务码void Send(int code){int n = write(_wfd, &code, sizeof(code));(void)n; // 显式忽略返回值(避免编译器警告)}// 关闭写端文件描述符void Close(){close(_wfd); // 关闭管道写端}// 等待子进程退出,回收子进程,避免僵尸进程出现void Wait(){pid_t rid = waitpid(_subid, nullptr, 0); // 阻塞等待(void)rid; // 显式忽略返回值}// Getter方法int Fd() { return _wfd; }             // 获取写端fdpid_t SubId() { return _subid; }      // 获取子进程PIDstd::string Name() { return _name; }  // 获取通道名称private:int _wfd;            // 管道写端文件描述符pid_t _subid;        // 子进程PIDstd::string _name;   // 通道标识名称
};// ChannelManager类:管理所有子进程通道
class ChannelManager
{
public:ChannelManager() : _next(0) {}  // 初始化轮询索引// 添加新通道void Insert(int wfd, pid_t subid){_channels.emplace_back(wfd, subid); // 原地构造Channel对象,加入channel数组}// 轮询选择下一个通道(简单负载均衡)Channel &Select(){auto &c = _channels[_next];_next = (_next + 1) % _channels.size(); // 环形选择return c;}// 打印所有通道信息void PrintChannel(){for (auto &channel : _channels){std::cout << channel.Name() << std::endl;}}// 关闭所有子进程管道void StopSubProcess(){for (auto &channel : _channels){channel.Close();//关掉读std::cout << "关闭: " << channel.Name() << std::endl;}}// 回收所有子进程void WaitSubProcess(){for (auto &channel : _channels){channel.Wait();std::cout << "回收: " << channel.Name() << std::endl;}}~ChannelManager() {}  // 析构函数(vector自动释放)private:std::vector<Channel> _channels;  // 存储所有Channel对象int _next;                       // 轮询索引
};const int gdefaultnum = 5;  // 默认子进程数量// ProcessPool类:主进程池实现
class ProcessPool
{
public:// 构造函数:初始化进程数并注册任务ProcessPool(int num) : _process_num(num){_tm.Register(PrintLog);    // 注册日志任务_tm.Register(Download);    // 注册下载任务_tm.Register(Upload);      // 注册上传任务}//把这三个函数指针全部加入函数指针数组中// 子进程工作循环void Work(int rfd){while (true){int code = 0;ssize_t n = read(rfd, &code, sizeof(code));//从rfd中读任务吗,和channel的send函数相对应,正常一次读4字节if (n > 0)  // 成功读取{if (n != sizeof(code)) continue;  // 数据不完整则继续读取std::cout << "子进程[" << getpid() << "]收到任务码: " << code << std::endl;_tm.Execute(code);  // 执行对应任务,就是三个函数之一,上传,下载。。。。}else if (n == 0)  // 管道关闭(父进程终止){std::cout << "子进程退出" << std::endl;break;}else  // 读取错误{std::cerr << "读取错误" << std::endl;break;}}}// 启动进程池bool Start(){for (int i = 0; i < _process_num; i++){// 1. 创建管道int pipefd[2] = {0};if (pipe(pipefd) < 0) return false;  // 创建失败// 2. 创建子进程pid_t subid = fork();if (subid < 0) return false;  // fork失败if (subid == 0)  // 子进程分支{close(pipefd[1]);  // 关闭写端Work(pipefd[0]);    // 进入工作循环close(pipefd[0]);exit(0);            // 正常退出}else  // 父进程分支{close(pipefd[0]);            // 关闭读端_cm.Insert(pipefd[1], subid); // 记录通道信息}}return true;}// 调试用:打印所有通道void Debug() { _cm.PrintChannel(); }// 运行任务(主进程调用)void Run(){int taskcode = _tm.Code();          // 1. 获取任务码auto &c = _cm.Select();             // 2. 选择子进程std::cout << "选择子进程: " << c.Name() << std::endl;c.Send(taskcode);                   // 3. 发送任务std::cout << "发送任务码: " << taskcode << std::endl;}// 停止进程池void Stop(){_cm.StopSubProcess();  // 关闭所有管道_cm.WaitSubProcess();  // 回收所有子进程}~ProcessPool() {}  // 析构函数private:ChannelManager _cm;      // 通道管理器int _process_num;        // 子进程数量TaskManager _tm;         // 任务管理器
};#endif

Task.hpp:

// 防止头文件被重复包含的编译器指令(现代C++替代#ifndef的方式)
#pragma once// 标准输入输出库(用于cout等)
#include <iostream>
// 动态数组容器(用于存储任务函数指针)
#include <vector>
// 时间相关函数(用于随机数种子初始化)
#include <ctime>// 定义函数指针类型:无参数、无返回值的函数,名字是task_t!!!!!
typedef void (*task_t)(); 调试用任务函数 
// 打印日志任务函数
void PrintLog()
{std::cout << "我是一个打印日志的任务" << std::endl;
}// 下载任务函数 
void Download()
{std::cout << "我是一个下载的任务" << std::endl;
}// 上传任务函数
void Upload()
{std::cout << "我是一个上传的任务" << std::endl;
}
//// 任务管理类
class TaskManager
{
public:// 构造函数:初始化随机数种子TaskManager(){srand(time(nullptr)); // 用当前时间初始化随机数生成器}// 注册任务函数:将函数指针存入vectorvoid Register(task_t t){_tasks.push_back(t); // 添加到任务列表末尾}// 生成随机任务码:返回[0, 任务数量-1]的随机数int Code(){return rand() % _tasks.size(); // 取模保证不越界}// 执行任务:根据code调用对应的函数void Execute(int code){// 检查code是否合法(防御性编程)if(code >= 0 && code < _tasks.size()){_tasks[code](); // 通过函数指针调用任务,就是上面三个打印,上传,下载函数}// 注意:未处理非法code的情况(可添加错误处理)}// 析构函数(当前为空实现)~TaskManager(){}private:std::vector<task_t> _tasks; // 存储所有注册的任务函数指针
};

makefile:

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

main.cc:

#include "ProcessPool.hpp"int main()
{// 创建进程池对象ProcessPool pp(gdefaultnum);// 启动进程池pp.Start();//刚开始就是建立5个子进程和通道,但通道内没有内容即任务码,所以子进程的work会被卡住。// 自动派发任务int cnt = 10;while(cnt--){pp.Run();//往子进程里去发放任务码,子进程开始work,也就是开始调用manager的Execute函数,就是在三个上传,下载函数中随机选一个来执行sleep(1);}// 回收,结束进程池pp.Stop();// 关闭所有管道-即回收父进程的wfd---使用close函数关掉所有channel中的wfd//回收所有子进程----调用waitpid函数return 0;
}

小问题:
在这里插入图片描述

如果我每关一个wfd,回收一个子进程会怎样? 会在第一个子进程回收时阻塞!!!-------------------------------------- read()没有返回0

  • 第一次产生子进程,父进程文件描述符表分配3,4(三是读,四是写),子进程先是拷贝父类的内容,所以子进程也是(三是读,四是写),然后关闭不需要的fd,父进程关闭3,子进程关闭4
  • 第二次产生子进程,父进程分配文件描述符3,5(因为4已经在上次的过程中被占用三是读,五是写),同理,子进程也是(三是读,五是写),然后再次关闭不需要的fd,父进程关闭3,子进程关闭5要注意的是,第二次产生的子进程会继承父进程的4,即二号子进程的4是指向第一个子进程管道的写端的!!!!!!
  • 所以第一次,父进程的wfd被关闭后,写端并没有完全关闭(因为剩余的四个子进程都继承了4号写端计数器还有4,read函数就不会返回0,自然就没办法结束第一个子进程,自然就没办法使用wait函数回收,导致阻塞

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

  • 解决办法-----倒着关,因为只有最后一个子进程的写端是由父进程一人持有的,父进程关了那就是真的关了,可以让read直接返回0,完成回收----以此类推
  • 也可以在子进程创立时,遍历channel数组,把里面的wfd都关了,说白了就是把继承下来的写端全关了,这样所有的写端都只由父进程持有

4.命名管道

在这里插入图片描述
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/913085.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/913085.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深入理解Vapnik-Chervonenkis(VC)维度:机器学习泛化能力的理论基础

引言 通过本篇阅读,从理论上去理解为什么: 要选择复杂度低的模型 过拟合的时候,增加样本量有用 以及如何根据样本量选择特征个数 PAC机器学习框架, VC 维是机器学习最重要的基础理论之一 在机器学习领域&#xff0c;模型泛化能力是衡量算法性能的核心指标…

redis持久化-纯缓存模式

redis持久化-纯缓存模式 文档 redis单机安装redis常用的五种数据类型redis数据类型-位图bitmapredis数据类型-基数统计HyperLogLogredis数据类型-地理空间GEOredis数据类型-流Streamredis数据类型-位域bitfieldredis持久化-RDBredis持久化-AOFredis持久化-RDBAOF混合模式 官…

HTML DOM 访问

HTML DOM 访问 引言 HTML DOM&#xff08;文档对象模型&#xff09;是现代Web开发中不可或缺的一部分。它允许开发者通过JavaScript操作HTML文档中的元素&#xff0c;从而实现丰富的交互效果。本文将详细介绍HTML DOM的访问方法&#xff0c;包括如何获取元素、如何修改元素属…

双系统如何做接口认证-V1

现有A系统&#xff0c;B系统&#xff0c;A系统启动的时候调用B系统的注册接口API1&#xff08;把A系统配置信息注册到B系统&#xff09;&#xff0c;A系统定时向B系统接口AP2发送心跳信息&#xff0c;B系统根据业务情况&#xff0c;调用A系统的业务接口AP3&#xff0c;请设计两…

Wireshark TS | 诡异的光猫网络问题

前言 来自于朋友分享的一个案例&#xff0c;最后定位的原因是光猫问题&#xff0c;而类似这类的设备所产生的网络问题&#xff0c;也曾碰到过两三次&#xff0c;但这一次的数据包现象挺特别&#xff0c;分析思路和过程也有所不同&#xff0c;故记录分享一下。 问题背景 用户所反…

mac mini m4安装node.js@16以下版本方法

设备&#xff1a;mac mini m4 目的&#xff1a;使用nvm 安装 node.js14.x 版本 结果&#xff1a;安装不上 原因&#xff1a;Node.js 14 发布时&#xff0c;Apple Silicon&#xff08;M1/M2&#xff09;尚未普及&#xff0c;因此 没有官方预编译的 macOS ARM64 版本 处理方案&am…

系统安全设计方案,软件系统安全设计方案

1.1 总体设计 1.1.1 设计原则 1.2 物理层安全 1.2.1 机房建设安全 1.2.2 电气安全特性 1.2.3 设备安全 1.2.4 介质安全措施 1.3 网络层安全 1.3.1 网络结构安全 1.3.2 划分子网络 1.3.3 异常流量管理 1.3.4 网络安全审计 1.3.5 网络访问控制 1.3.6 完整性检查 1.…

Python入门Day3

Python的基础数据类型 1.Python中提供了六种内置的数据类型&#xff0c;一般用于存储数据&#xff1a; –数值Number –字符串String –列表List –元组Tuple –字典Dictionary –集合Set 2.Python中的数据类型可以做以下几个分类&#xff1a; –有序&#xff1a;可以使用下标…

前端富文本添加录音功能方案

为富文本编辑器添加录音功能可以增强内容创作的多样性。以下是几种实现方案&#xff1a; 方案一&#xff1a;基于Web Audio API原生实现 实现步骤获取用户麦克风权限 navigator.mediaDevices.getUserMedia({ audio: true }).then(stream > { /* 处理音频流 */ }).catch(err …

解锁阿里云Hologres:开启实时数据分析新时代

引言在当今这个数字化浪潮汹涌澎湃的大数据时代&#xff0c;数据就如同企业和组织的 “数字石油”&#xff0c;成为了最具价值的资产之一。随着信息技术的飞速发展&#xff0c;各行业所产生和收集的数据量正以指数级的速度增长&#xff0c;从社交媒体上的用户互动信息&#xff…

python学习打卡day59

DAY 59 经典时序预测模型3 知识点回顾&#xff1a; SARIMA模型的参数和用法&#xff1a;SARIMA(p, d, q)(P, D, Q)m模型结果的检验可视化&#xff08;昨天说的是摘要表怎么看&#xff0c;今天是对这个内容可视化&#xff09;多变量数据的理解&#xff1a;内生变量和外部变量多变…

java中agent的作用

一 java中agent1.1 agent-javaagent 是 Java 虚拟机 (JVM) 提供的一个启动参数&#xff0c;用于在 Java 程序 main 方法执行之前&#xff0c;加载一个特殊的 Java 代理程序&#xff08;Java Agent&#xff09;。它的核心作用是对运行中的 Java 程序进行字节码层面的动态修改、监…

[C/C++内存安全]_[中级]_[如何避免数组访问越界]

场景 C/C的标准在C26以前还没支持内存安全的访问连续内存的类或特性。在开发分析内存数据或文件数据的程序时&#xff0c;经常需要把一段内存数据复制到另一个堆空间里。 这时目标内存空间由于起始地址的移动&#xff0c;剩余大小的计算错误&#xff0c;经常会导致访问越界错误…

rabbitmq 与 Erlang 的版本对照表 win10 安装方法

win10 64位系统 安装的版本 otp_win64_27.3.3.exe rabbitmq-server-4.1.1.exe rabbitmq 与 Erlang 的版本对照表 Erlang Version Requirements This guide covers Erlang/OTP version requirements https://www.rabbitmq.com/docs/which-erlang Erlang 28 is not currently…

kali安装教程

kali教程 我下载的是kali的集成环境&#xff0c;可以直接进行打开&#xff0c;无需进行安装。 Get Kali | Kali Linux&#xff0c; 官网下载路径 直接按enter键 安装完成 生成一个小皮安装链接 会给你生成一个外网和内网地址&#xff0c; 可以进行浏览 点击我同意这个协议…

微信小程序入门实例_____快速搭建一个快递查询小程序​

&#x1f337;&#x1f337;之前几篇博文我们一起开发了天气查询、单词速记和待办事项小程序&#xff0c;这次我们来对生活中常用的功能 —— 快递查询来探索相关的小程序。网购已经成为大家生活的一部分&#xff0c;有了自己的快递查询小程序&#xff0c;不用切换多个应用&…

【防火墙基础之传统墙到 UTM 到 NGFW 再到 AI 的变化】

防火墙技术演进与未来趋势&#xff1a;从传统防御到AI驱动的智能安全 防火墙技术历经数十年发展&#xff0c;已从早期的简单包过滤演进为融合AI的智能安全平台。当前&#xff0c;传统爬虫防护技术如频率限制和人机校验已无法应对现代攻击&#xff0c;而全面风控体系通过多维协同…

【仿muduo库实现并发服务器】Poller模块

仿muduo库实现并发服务器 1.Poller模块成员变量创建epoll模型对于一个描述符添加或修改事件监控对于一个描述符移除事件监控启动epoll事件监控&#xff0c;获取所有活跃连接 1.Poller模块 Poller模块主要是对任意的描述符进行IO事件监控。 它是对epoll的封装&#xff0c;可以让…

小程序学习笔记:使用 MobX 实现全局数据共享,实例创建、计算属性与 Actions 方法

在小程序开发过程中&#xff0c;组件间的数据共享是一个常见且关键的问题。今天&#xff0c;我们就来深入探讨一下如何在小程序中实现全局数据共享&#xff0c;借助 MobX 相关的包&#xff0c;让数据管理变得更加高效便捷。 什么是全局数据共享 全局数据共享&#xff0c;也被…

观测云 × AWS SSO:权限治理可观测实践

AWS IAM Identity Center 介绍 AWS IAM Identity Center&#xff08;原 AWS Single Sign-On&#xff09;是 AWS 提供的一项云原生身份与访问管理&#xff08;IAM&#xff09;服务&#xff0c;旨在集中简化多 AWS 账户、多业务应用的安全访问控制。 观测云 观测云是一款专为 …