System-V共享内存和基于管道通信实现的进程池

在这里插入图片描述

文章目录

  • 一.进程间通信:
    • 进程间通信的本质:
  • 二.Linux管道通信
    • 匿名管道:
    • 关于管道通信的要点:
    • 基于匿名管道构建进程池:
  • 三.System-V共享内存
    • 共享内存和命名管道协同通信

参考Linux内核源码版本------linux-2.4.3

一.进程间通信:

  • 操作系统中,为了保证安全性,进程之间具有严格的独立性(独立的PCB,独立的虚拟地址空间mm_struct和页表…等各种独立的系统资源),即便是父子进程之间也通过数据的写时拷贝保证了两者之间的的数据独立性.因此要实现进程间通信和任务协作,就要让不同进程的共同读写同一份信息资源.由于违背了进程独立性的原则,要实现进程间共享资源就需要一定的技术成本.
  • 进程间的独立性:
    在这里插入图片描述

进程间通信的本质:

  • 不同的进程同一块内存资源进行的一系列的读写操作.

二.Linux管道通信

  • 同一个管道文件的文件结构体指针分别填入两个进程的文件信息列表(通过父子进程的继承关系或者open接口实现),之后两个进程便可以对管道文件的内核级读写缓冲区(本质上是一块内存)进行读写操作实现通信.
  • 管道通信是一种单向通信手段,有固定的读端进程和写端进程.

匿名管道:

  • 匿名管道通信是父子进程间通信的一种方式.
  • 匿名管道通信机制图解:
    在这里插入图片描述在这里插入图片描述
    在这里插入图片描述
  • 内核视角下管道文件结构体内部结构:在这里插入图片描述

关于管道通信的要点:

  • 管道通信可用于进程间协同,提供访问控制(同步与互斥):
    • 管道读写端正常,如果管道中缓冲区为空,则读端进程进入阻塞状态
    • 管道读写端正常,如果管道中缓冲区被写满,则写端进程进入阻塞状态
    • 管道写端先关闭,管道读端read接口返回0,标识读取结束
    • 管道读端先关闭,操作系统会终止写端进程.

基于匿名管道构建进程池:

在这里插入图片描述

  • Task.hpp模拟任务列表
#include <iostream>
#include <cstdio>
#include <string>
#include <vector>
#include <sys/types.h>
#include <sys/wait.h>
#include <cstdlib>
#include <ctime>
#include <cassert>
#include <unistd.h>//重定义函数指针
typedef void (*task_t)();void task1()
{std::cout << "执行任务1:矩阵计算" << std::endl;
}
void task2()
{std::cout << "执行任务2:pid控制算法" << std::endl;
}
void task3()
{std::cout << "执行任务3:图像计算" << std::endl;
}
void task4()
{std::cout << "执行任务4:人脸识别算法" << 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);
}
#include "Task.hpp"
using namespace std;
#define ChildNum 5//子进程信息结构体
class Channel
{
public: Channel(){}Channel(const string & Name,pid_t Childpid,int Pipefd): _Childpid(Childpid),_Pipefd(Pipefd),_Name(Name){}pid_t getpid() const {return _Childpid;}string PrintName() const {return _Name;}int Getfd() const {return _Pipefd;}
private:pid_t _Childpid;int _Pipefd;    //保存管道的写入端string _Name;
};//子进程任务执行函数
void slaver(const vector<task_t>&Taskarr)
{//任务码int CommandCode = 0;while(true){int check = read(0,&CommandCode,sizeof(CommandCode));//若管道中没有数据且写入段没有关闭,子进程就会阻塞assert(check!=-1);if(check > 0){std::cout <<"slaver say@ get a command: "<< getpid() << " : CommandCode: " <<  CommandCode << std::endl;//子进程解析并执行命令if(CommandCode < 0 || CommandCode >= Taskarr.size()){cout << "CommandCode Error! slaver exit!" << endl;exit(0);}//子进程根据任务码执行任务Taskarr[CommandCode]();}else {//一旦父进程关闭管道写入端,check就会接收到0,子进程退出break;}}
}//父进程向子进程发送任务的接口
void ctrlSlaver(const std::vector<Channel> & channels,const vector<task_t>&Taskarr)
{int count = 10;while(count--){sleep(1);//随机选择子进程发送任务码int choseSlaver = rand()%channels.size();int Task = rand()%Taskarr.size();cout << "父进程向子进程" << channels[choseSlaver].PrintName() << "写入命令:" << Task << endl;write(channels[choseSlaver].Getfd(),&Task,sizeof(Task));}sleep(1);cout << "\n所有任务执行完毕,系统准备退出\n" << endl;sleep(2);
}//构建进程池接口
void InitProcessPool(vector<Channel>& ChildProc,const vector<task_t>&Taskarr)
{for(int i = 0; i < ChildNum; ++i){int pipefd[2];int check = pipe(pipefd);assert(!check); (void)check;pid_t pid = fork();assert(pid != -1);//父进程写 子进程读if(pid == 0){//子进程执行流close(pipefd[1]);//将stdin对应文件指针修改为管道的读入端dup2(pipefd[0],0);//将文件信息列表中对应的指针位置空close(pipefd[0]);slaver(Taskarr);close(0);exit(0);}close(pipefd[0]);//将管道的写入端存入channel对象中ChildProc.push_back(Channel(string("Process ") + to_string(pid),pid,pipefd[1]));      }
}//父进程轮询等待子进程退出
void WaitChildProc(const std::vector<Channel> & channels)
{//先关闭各个管道的写入端,相应的子进程会自动退出for(auto& e : channels){close(e.Getfd());}//等待各个子进程退出for(auto & e : channels){int Status = 0;waitpid(e.getpid(),&Status,0);cout << "写入端关闭,子进程:" << e.getpid() << "退出,退出码:"<< WIFEXITED(Status) << endl;}
}int main()
{vector<task_t>Taskarr;LoadTask(Taskarr);srand(time(nullptr)^getpid()^1023);vector<Channel> ChildProc;InitProcessPool(ChildProc,Taskarr);ctrlSlaver(ChildProc,Taskarr);WaitChildProc(ChildProc);return 0;
}

在这里插入图片描述

  • 命名管道和匿名管道的内核原理相同

三.System-V共享内存

  • 共享内存通信原理:
    在这里插入图片描述
  • 构建共享内存通信环境的系统接口:
    • int shmget(key_t key, size_t size, int shmflg);
      • key是用户自定义共享内存标识键,用ftok接口获取
      • size是申请共享内存的大小
      • shmflg:取IPC_CREAT时,接口可以申请共享内存并获取共享内存的key,若参数指定的共享内存已存在则直接返回共享内存的key;取IPC_CREAT | IPC_EXCL时,接口只能用于申请新的共享内存.
    • void *shmat(int shmid, const void *shmaddr, int shmflg);
      • 接口作用:在当前进程的虚拟地址空间的共享区中为指定的共享内存块编址,并建立页表映射.
    • int shmdt(const void *shmaddr);
      • 接口作用:共享内存块与当前进程的虚拟地址空间取消关联,进程将无法再访问指定的共享内存
    • int shmctl(int shmid, int cmd, struct shmid_ds *buf);
      • 接口作用:对共享内存块进行cmd码指定的控制操作(比如释放操作),也可以用于获取共享内存块在内核中的描述信息
  • 共享内存通信环境中,由于多个进程可以对同一个内存块直接进行读写操作,因此,共享内存通信缺少同步互斥机制,无法保证数据的读写安全,为此,可以借助命名管道为共享内存通信提供读写控制.

共享内存和命名管道协同通信

  • 构建通信环境的接口头文件:
#ifndef __COMM_HPP__
#define __COMM_HPP__#include "log.hpp"
#include <sys/sem.h>
#include <sys/ipc.h>
#include <sys/shm.h>using namespace std;
const int SIZE = 4096;
const string pathname = "/home/user1/LinuxLearning/sharedMEM";
const int pro_id = 0x123456;log LOG;//获取自定义共享内存key
key_t GetKey(const string pathname,const int pro_id)
{//KEY生成器key_t K = ftok(pathname.c_str(),pro_id);if(K < 0){LOG(Fatal,"GetKey Error, message: %s\n",strerror(errno));exit(-1);}LOG(Info,"key generated, message: %s\n",strerror(errno));return K;
}//调用系统接口申请共享内存
int GetShareMemHelper(int flag)
{key_t KEY = GetKey(pathname,pro_id);//系统调用接口shmget申请共享内存或返回已存在的共享内存idint shmid = shmget(KEY,SIZE,flag);if(shmid == -1){LOG(Fatal,"Get ShareMem failed, message: %s\n",strerror(errno));exit(-1);}LOG(Info,"Get ShareMem completed, message: %s\n",strerror(errno));return shmid;
}//申请新的共享内存
int CreateShm()
{return GetShareMemHelper(IPC_CREAT | IPC_EXCL | 0666);
}//获取已存在的共享内存的id
int GetShm()
{return GetShareMemHelper(IPC_CREAT); 
}#define FIFO_FILE "./myfifo"
#define MODE 0664enum
{FIFO_CREATE_ERR = 1,FIFO_DELETE_ERR,FIFO_OPEN_ERR
};class Init
{
public:Init(){// 创建管道int n = mkfifo(FIFO_FILE, MODE);if (n == -1){perror("mkfifo");exit(FIFO_CREATE_ERR);}}~Init(){//管道去链接,若引用计数为0则删除管道文件int m = unlink(FIFO_FILE);if (m == -1){perror("unlink");exit(FIFO_DELETE_ERR);}}
};#endif
  • 自制日志类log:
#pragma once
#include <time.h>
#include <stdarg.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <iostream>
#include <sys/types.h>
#include <sys/wait.h>
#include <string>
#include <cstdio>
#include <cstring>
#include <cassert>
#include <unistd.h>
#include <stdlib.h>//日志等级
#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4
//日志写入方式
#define Screen 1
#define Onefile 2
#define Classfile 3
#define LogFile "log.txt"using std :: string;
class log
{
public:log(){printMethod = Screen;path = "./log/";}void Enable(int method){printMethod = method;}string LeveltoString(int level){switch (level){case 0:return string("Info");break;case 1:return string("Debug");break;case 2:return string("Warning");break;case 3:return string("Error");break;case 4:return string("Fatal");break;default:break;}}void operator()(int level,char * format,...){//将时间格式化存入tm结构体中time_t t = time(nullptr);struct tm* ctime = localtime(&t);char leftbuffer[1024];snprintf(leftbuffer,sizeof(leftbuffer),"[%s][%d-%d-%d %d:%d:%d]",LeveltoString(level).c_str(),ctime->tm_year+1900,ctime->tm_mon+1,ctime->tm_mday,ctime->tm_hour,ctime->tm_min,ctime->tm_sec);//解析可变参数va_list vls;va_start(vls,format);char rightbuffer[1024];vsnprintf(rightbuffer,sizeof(rightbuffer),format,vls);va_end(vls);//合并时间和可变参数char logtxt[2048];snprintf(logtxt,sizeof(logtxt),"%s %s\n",leftbuffer,rightbuffer);//执行日志记录printLog(level,string(logtxt));}//日志信息写出接口void printLog(int level, const std::string &logtxt){switch (printMethod){case Screen://将日志信息打印到标准输出std::cout << logtxt << std::endl;break;case Onefile://将日志信息存入log.txtprintOneFile(LogFile, logtxt);break;case Classfile://将日志信息存入指定的分类日志文件printClassFile(level, logtxt);break;default:break;}}//日志信息写到log.txt中void printOneFile(const std::string &logname, const std::string &logtxt){//path-->日志保存路径  logname-->日志文件名std::string _logname = path + logname;//打开"log.txt"日志文件int fd = open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666); if (fd < 0)return;write(fd, logtxt.c_str(), logtxt.size());close(fd);}//日志信息写到log.txt.level中void printClassFile(int level, const std::string &logtxt){//对日志文件名进行修改,根据日志等级分出多个日志文件std::string filename = LogFile;filename += ".";// "log.txt.Debug(Warning)(Fatal)"filename += LeveltoString(level); printOneFile(filename, logtxt);}~log(){}
private:int printMethod;std::string path;
};
  • 读端进程示例:
#include "log.hpp"
#include "ShareMemBuild.hpp"extern log LOG;int main()
{//创建管道和共享内存Init pipeCreate;int shmid = CreateShm();//建立共享内存与进程虚拟地址空间之间的映射,并获取共享内存的虚拟地址char * shmaddr = (char *)shmat(shmid,NULL,0);//打开管道文件int fd = open(FIFO_FILE,O_RDONLY);if(fd == -1){LOG(Fatal, "error string: %s, error code: %d", strerror(errno), errno);exit(FIFO_OPEN_ERR);}while(true){//借助管道进行共享内存的读写控制,若写端没有给信号,则读端保持阻塞状态char c;int RSize = read(fd,&c,sizeof(c));if(RSize <=0) break;//直接访问共享内存,实现高效通信cout << "client say@ " << shmaddr << endl; sleep(1);}//进程与共享内存断开连接shmdt(shmaddr);//将共享内存标记为已销毁shmctl(shmid,IPC_RMID,nullptr);close(fd);return 0;
}
  • 写端进程示例:
#include "log.hpp"
#include "ShareMemBuild.hpp"extern log LOG;int main()
{   //获取共享内存标识int shmid = GetShm();//建立共享内存与进程虚拟地址空间之间的映射,并获取共享内存的虚拟地址char * shmaddr = (char *)shmat(shmid,NULL,0);//打开管道文件int fd = open(FIFO_FILE,O_WRONLY);if(fd == -1){LOG(Fatal, "error string: %s, error code: %d", strerror(errno), errno);exit(FIFO_OPEN_ERR);}while(true){cout << "Please Enter@ ";//将信息写入共享内存fgets(shmaddr, 4096, stdin);//管道写入信号,解除读端的阻塞状态write(fd, "c", 1); }//进程与共享内存断开连接shmdt(shmaddr);close(fd);return 0;
}

在这里插入图片描述

  • 多个进程直接通过各自的虚拟地址空间同一个内存块进行访问使得共享内存通信具有很高的通信效率.管道通信过程中,数据至少要经过两次拷贝(用户读写缓冲区和内核读写缓冲区之间的拷贝),而共享内存通信不存在通信数据拷贝问题
  • 共享内存,消息队列,信号量等通信内存资源(称为ipc资源)统一由操作系统描述为各种数据结构统一进行管理,在Linux内核中,描述共享内存,消息队列,信号量的结构体形成继承体系:(C语言实现的继承体系)在这里插入图片描述
    在这里插入图片描述

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

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

相关文章

Jquery ajax 进行网络请求,同步阻塞引起的UI线程阻塞 (loading图片不显示 )

jax重新获取数据刷新页面功能&#xff0c;因为ajax属于耗时操作&#xff0c;想在获取数据且加载页面时显示加载遮罩层&#xff0c;结果发现了ajax的好多坑。 ajax 执行http网络请示时时&#xff0c;让遮罩层显示&#xff0c;ajax加载完毕后遮罩层消失。 因为我想让loadChart()…

卷积神经网络(CNN)识别验证码

文章目录 一、前言二、前期工作1. 设置GPU&#xff08;如果使用的是CPU可以忽略这步&#xff09;2. 导入数据3. 查看数据4.标签数字化 二、构建一个tf.data.Dataset1.预处理函数2.加载数据3.配置数据 三、搭建网络模型四、编译五、训练六、模型评估七、保存和加载模型八、预测 …

Ubuntu18.04安装A-Loam保姆级教程

系统环境&#xff1a;Ubuntu18.04.6 LTS 1.A-Loam的安装前要求&#xff1a; 1.1 ROS安装&#xff1a;参考我的另一篇博客 Ubuntu18.04安装ROS-melodic保姆级教程_灬杨三岁灬的博客-CSDN博客还是那句话&#xff0c;有时候加了这行也不好使&#xff0c;我是疯狂试了20次&#…

十六、互斥量

互斥量的目的就是为了实现互斥访问。 1、概述 (1)举例说明&#xff1a; 怎么独享厕所&#xff1f;自己开门上锁&#xff0c;完事了自己开锁。 你当然可以进去后&#xff0c;让别人帮你把门&#xff1a;但是&#xff0c;命运就掌握在别人手上了。 …

Task.Run为什么会存在内存泄漏的风险?

由于值类型是拷贝方式的赋值&#xff0c;捕获的本地变量和类成员是指向各自的值&#xff0c;对本地变量的捕获不会影响到整个类。但如果把类中的值类型改为引用类型&#xff0c;那这两者最终指向的是同一个对象值&#xff0c;这是否意味着使用本地变量还是无法避免内存泄漏&…

【java】-D参数使用

在开发过程中我们使用开源工具经常会用到在启动命令时候加入一个 -Dxxx 类型的参数。到底-Dxxx是干什么用的了。 官方文档 地址&#xff1a;文档地址 java命令使用 下面是来源于官方文档&#xff1a; java [options] classname [args] java [options] -jar filename [args…

FFmpeg零基础学习(二)——视频文件信息获取

目录 前言正文一、获取宽高信息1、核心代码2、AVFormatContext3、avformat_alloc_context4、avformat_open_input5、avformat_find_stream_info6、av_dump_format7、av_find_best_stream End、遇到的问题1、Qt Debug模式avformat_alloc_context 无法分配对象&#xff0c;而Rele…

2023年汉字小达人市级比赛在线模拟题的使用顺序、建议和常见问题

今天是2023年11月25日&#xff0c;星期六&#xff0c;上午举办了2023年第八届上海小学生古诗文大会的复选活动&#xff08;复赛&#xff09;&#xff0c;结束了复选活动&#xff0c;很多学霸孩子们马上就开始投入到第十届汉字小达人的市级活动&#xff08;市级比赛&#xff09;…

PCL 计算两点云之间的最小距离

目录 一、 算法原理二、 代码实现三、 结果展示四、 相关链接本文由CSDN点云侠原创,原文链接。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的爬虫与GPT。 一、 算法原理 pcl::registration::CorrespondenceEstimation是确定目标和查询点集(或特征)之间对应关…

【Web】CmsEasy 漏洞复现

访问主页 到处点一点没啥发现 扫目录 访问/admin 账号密码都是admin admin,不知道为什么&#xff0c;这里就先当作是默认吧 &#xff08;其实都是信息检索&#xff0c;能在网上搜到就行hhh&#xff09; 登录成功 看到左边列表有模板&#xff0c;心里大概有数了哈 进行一波历…

StarRocks Evolution:One Data,All Analytics

在 11 月 17 日举行的 StarRocks Summit 2023上&#xff0c;StarRocks TSC Member、镜舟科技 CTO 张友东详细介绍了 StarRocks 社区的发展情况&#xff0c;并全面解析了 StarRocks 的核心技术与未来规划&#xff1b;我们特意将他的精彩演讲整理出来&#xff0c;以帮助大家更深入…

docker环境安装

环境 主机环境 1. 宿主机环境 ubuntu-22.04.3-live-server-amd64 &#xff0c;下载地址&#xff1a; https://mirrors.aliyun.com/ubuntu-releases/22.04.3/ubuntu-22.04.3-live-server-amd64.iso 2. apt 包管理器&#xff0c;镜像源修改 : 将 http://cn.archive.ubunt…

间接法加窗分析信号的功率谱

本篇文章是博主在通信等领域学习时&#xff0c;用于个人学习、研究或者欣赏使用&#xff0c;并基于博主对通信等领域的一些理解而记录的学习摘录和笔记&#xff0c;若有不当和侵权之处&#xff0c;指出后将会立即改正&#xff0c;还望谅解。文章分类在 通信领域笔记&#xff…

【算法萌新闯力扣】:卡牌分组

力扣热题&#xff1a;卡牌分组 一、开篇 今天是备战蓝桥杯的第22天。这道题触及到我好几个知识盲区&#xff0c;以前欠下的债这道题一并补齐&#xff0c;哈希表的遍历、最大公约数与最小公倍数&#xff0c;如果你还没掌握&#xff0c;这道题练起来&#xff01; 二、题目链接:…

【数据结构】树与二叉树(廿三):树和森林的遍历——层次遍历(LevelOrder)

文章目录 5.3.1 树的存储结构5. 左儿子右兄弟链接结构 5.3.2 获取结点的算法5.3.3 树和森林的遍历1. 先根遍历&#xff08;递归、非递归&#xff09;2. 后根遍历&#xff08;递归、非递归&#xff09;3. 森林的遍历4. 层次遍历a. 算法LevelOrderb. 算法解读c. 时间复杂度d.代码…

STM32 启动文件分析

STM32 启动文件分析 基于STM32F103VET6芯片的 startup_stm32f10x_hd.s 启动文件分析 设置栈&#xff0c;将栈的大小Stack_Size设置为0x00004900&#xff08;18688/102418KB&#xff09;&#xff0c;即局部变量不能大于18KB。&#xff08;EQU等值指令&#xff0c;将0x0000490…

Arduio开发STM32所面临的风险

据说micro_ros用到了arduino,然后用arduino搞stm32需要用到这个Arduino STM32的东西&#xff0c;然后这里申明了&#xff1a;这些代码没有经过严格测试&#xff0c;如果是向心脏起搏器&#xff0c;自动驾驶这样要求严格的的情况下&#xff0c;这个东西不能保证100%不发生问题&a…

尺度为什么是sigma?

我们先看中值滤波和均值滤波。 以前&#xff0c;我认为是一样的&#xff0c;没有区分过。 他们说&#xff0c;均值滤波有使图像模糊的效果。 中值滤波有使图像去椒盐的效果。为什么不同呢&#xff1f;试了一下&#xff0c;果然不同&#xff0c;然后追踪了一下定义。 12345&…

一体化污水处理设备各种材质的优缺点

一体化污水处理设备的材质有多种&#xff0c;包括不锈钢、玻璃钢、聚乙烯塑料、碳钢等。每种材质都有其独特的优点和缺点。 不锈钢材质的优点是防腐性能好&#xff0c;耐磨损&#xff0c;使用寿命长&#xff0c;且外观美观。其缺点是成本较高&#xff0c;不适合在一些特殊的环…

ESP32 ESP-IDF5.1 在Visual Studio Code中自定义分区表与调整Flash大小

好记心不如烂笔头 使用ESP-IDF开发ESP32的时候,要是同时用到蓝牙和WIFI的话,很多时候会提示Flash不够, 我是照着这样解决的,存档记录 来源 : zaixingxing2539 大佬的 ESP32 ESP-IDF5.0 在VSCODE中自定义分区表 用Visual Studio Code自定义分区表 # ESP-IDF Partition Table…