操作系统:进程间通信 | 管道

目录

1.进程间通信介绍

1.1.简要介绍

1.2.进程间通信的目的

1.3.进程间通信的本质

2.管道

2.1.管道的通信原理

2.2.匿名管道 

2.3.命名管道 

2.4.基于匿名管道的进程池demo

2.4.1.进程池的相关引入

 2.4.2.整体框架的分析

2.4.3.代码的实现 


1.进程间通信介绍

1.1.简要介绍

进程间通信(Inter-Process Communication,简称IPC)是指在不同进程之间传播或交换信息

我们知道:进程之间是独立的,所以进程之间的进程间通信一定不是两个进程直接通信的,为了保证进程间的独立性和实现进程间通信,操作系统就设计了若干种进程间通信方式,来实现多进程之间的协同工作。 

1.2.进程间通信的目的

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

1.3.进程间通信的本质

进程间通信就是不同的进程通过 操作系统 这一个中间媒介,结合设计好的通信方式来进行通信的,本质上就是实现不同的进程能够访问到同一份资源,并从资源上获取信息。

  1. 比如存在进程A、B,其中A进程写入数据进入缓冲区,而B进程从这一块缓冲区中读取内容,这时A、B并没有之间接触,而是从中间商“缓冲区”处使得B进程获取信息。
  2. 同理,A、B进程可以从某一块缓冲区中读取数据。

2.管道

管道是一种基于文件系统的进程间通信方式,管道文件允许访问它的进程,通过它来对一块缓冲区的数据进行访问,通过进程对这一块缓冲区进行读写,来实现进程间数据的交换。  

2.1.管道的通信原理

生活中,我们见到的管道,当有流形成时,这个管道在某一个时刻或者时间段都是只允许单向流通的,比如水管中水的流动一般都是单向的,我们也没有见过管道发挥功能时,先向左流再从右往左流吧……

进程间的管道通信也是如此,一般来说:管道这种通信方式是单向流动的

因为进程具有独立性,所以进程是不能够直接进行通信的,比如A,B进行通信,只允许进行A--中间渠道--B或者B--中间渠道--A ,所以管道的通信原理就是作为中间渠道,在操作系统中,系统实现管道的功能是通过加载进内存的文件缓冲区实现的,并没有实际对管道文件进行操作,而是通过A/B往缓冲区读/写内容,然后B/A进行读/写……

另外管道的通信具有以下4种情形(规定)和三种特性:

四种情形:

  1. 正常情况下,如果管道中没有数据,也就是写端当前没有写入时,读端必须等待,直到写端提供数据。
  2. 如果管道中的数据写满时,如果需要继续写入,写端必须等待,直到读端读取完数据,写端才可以继续写入。
  3. 写端关闭时,读端直接接收到read()函数的返回值为0,表示读取结束,读到文件结尾。
  4. 读端关闭时,写端不会直接关闭,如果写端仍不断写入,操作系统会介入杀掉写端进程。

三种特性:

  1. 管道是单向通信的,是一种半双工通信
  2. 管道是面向字节流的,也就是对应C++IO流中的字符流,管道可以是整型流、字符流
  3. 管道的生命周期是伴随进程的,因为管道通信的本质就是通过文件系统在内存中开辟一块缓冲区,来间接实现进程间通信的

基于4种情形和三种特性,操作系统实现了两种管道通信方式:匿名管道和命名管道,前者只能用于具有血缘关系的进程,后者能用于所有进程……

2.2.匿名管道 

 C语言提供创建匿名管道的函数方法:

接下来我们通过匿名管道的测试来探究一下其原理: 

// 匿名管道的测试
void test1()
{// 设置管道的文件描述符数组int pipefd[2] = {0};// 将fd传入pipe接收返回值int n = pipe(pipefd);// 返回值为3,4表示占用了文件指针数组第3个、第4个文件cout << pipefd[0] <<" "<< pipefd[1] << endl;int pipefd1[2] = {0};int m = pipe(pipefd1);cout << pipefd1[0] << " " << pipefd1[1] << endl;
}

 通过这段代码的测试,我们发现除了0(stdin),1(stdout),2(stderr),我们在创造一个匿名管道时,会占用两个文件fd,在实际应用时,这两个文件分别负责读写功能,为什么需要这样设计呢?

首先我们要知道匿名管道只有拥有血缘关系的进程才可以使用的!!!

我们先从最简单的管道通信----父子进程通信出发:

如图这就是:匿名管道通信的原理,通过管道函数,在进程中开辟两个文件来实现读写功能,再通过进程的拷贝,实现对同一个文件的读写,最终各自释放一个读/写端,实现单向通信。

 下面是一个父子进程的匿名管道通信样例:

// 父子通过匿名管道通信demo
// 只要能把文件描述符继承下去,就能够实现匿名管道通信
// 也就是可以进行兄弟、爷孙进程的管道通信
// 没有任何继承体系的进程之间无法使用匿名管道
void test2()
{int pipefd[2] ={0};// 将fd传入pipe接收返回值int n = pipe(pipefd);// 父子进程关闭各自不使用的fd// 实现单向通信的管道pid_t id = fork();if(id < 0){perror("error fork");}else if(id == 0){// child// 关闭读的指向close(pipefd[0]);int count = 3;cout << "writing data into the buffer" << endl;while(count--){char mesg[BUFFSIZE];cin >> mesg;// 通过系统接口 将写入的数据通过 写 的文件接口进入文件缓冲区中write(pipefd[1], mesg, strlen(mesg));}exit(0);}// father// 关闭写的指向close(pipefd[1]);char buffer[BUFFSIZE];while(true){// 通过读接口把文件缓冲区的内容写入buffer中// 读取buffer大小减1预留 \0 字符ssize_t n = read(pipefd[0], buffer, sizeof(buffer) - 1);if(n > 0){buffer[n] = 0;cout << "child wrote: " << buffer << " to father process " <<endl;}}}

在这段代码demo中我们只实现了由子进程向缓冲区进行写功能,父进程向缓冲区进行读功能,那么我们能不能够实现双向通信呢?

答案是可以的,但是我们又要保证一个管道的流向是单向的,注意这里我们用的是“一个”,所以我们可以通过pipe函数再次创建一个管道文件,然后把子进程的写端关闭,父进程的读端关闭,再进行链接。即通过这个demo,加上逻辑相反的代码就可以实现了

2.3.命名管道 

我们在匿名管道的学习中,了解到它的可行性是通过具有血缘关系的进程会拷贝同一个file_struct结构体的指针,来实现读写文件指向同一块区域的。但是对于不具有血缘关系,也就是完全不相干的两个进程我们该如何通信呢?

这时我们可以通过命名管道,创建FIFO文件来实现在不相关的进程之间进行通信……

// 创建命名管道fifo
int n = mkfifo(文件名, 文件权限);// 文件返回值
int r_open = open(文件名, 文件打开方式);

一般来说我们使用命名管道,首先先创建命名管道,然后两个不同的进程再通过系统调用接口通过不同的打开方式(读/写)来打开这个管道文件。

 下面我们用两个进程的交互来演示一下:

进程一:

#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#define FILENAME "fifo"
using namespace std;int main()
{// 创建命名管道fifoint n = mkfifo(FILENAME, 0666);// 文件返回值int r_open = open(FILENAME, O_RDONLY);char buffer[1024];while (1){ssize_t r_read = read(r_open, buffer, sizeof(buffer) - 1);if (r_read > 0){buffer[r_read] = 0;cout << "recieve the message from client: " << buffer << endl;}}close(r_open);
}

 在这段代码中:

  1. 我们先创建了命名管道,接着通过只读方式打开文件
  2. 在死循环中,我们不断的读取打开文件返回的文件fd的内容,当r_read = 0时表示读端关闭,r_read > 0 时正常读取
  3. 读取后加载进我们设定好的buffer中,然后再打印出来

 进程二:

#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#define FILENAME "fifo"
using namespace std;int main()
{int r_open = open(FILENAME, O_WRONLY);string message;while (1){cout << "client send: ";getline(cin, message);ssize_t r_write = write(r_open, message.c_str(), message.size());}close(r_open);
}

 这段代码中:

  1. 我们在上一个进程中调用创建好的管道文件,获得相同的文件fd,这里因为我们默认创建两个进程都在当前目录下,这里的本质就是最终找到fifo这个文件
  2. 我们往fifo形成的缓冲区中写入数据,当我们完成写入时,对应的上一个进程就会打印相同的内容

XShell中的现象: 

这样我们就实现了两个互不相干的进程间的通信了…… 

2.4.基于匿名管道的进程池demo

2.4.1.进程池的相关引入

进程池是一种常见的多进程编程技术,用于优化资源使用和提高性能。它可以在程序启动时预先创建一定数量的进程,并将这些进程保存在池中以备后续使用。当有任务需要处理时,程序会从进程池中取出一个空闲的进程来处理任务,任务处理完毕后,该进程会被放回进程池中,等待下一个任务的到来。

如图我们通过父进程,创建五个子进程,在子进程创建的同时我们创建管道文件,进行父进程和子进程通过匿名管道的通信

当我们抽象出这一个模型图后,我们开始着手开辟5个管道和实现这个进程池……

// 创建5个子进程和实现5个管道
for (int i = 0; i < pipe_num; i++)
{// 1.定义并创建管道int pipefd[2];int n = pipe(pipefd);cout << "成功创建管道:" << i << endl;assert(n == 0);// 2.创建进程pid_t id = fork();assert(id != -1);// 3.构建单向信道if (id == 0){// child// 子进程关闭当前的 写 端close(pipefd[1]);exit(0);}// fatherclose(pipefd[0]);
}

 这段代码,我们循环5次,创建管道,并链接父子进程,但是这一段代码实际上在进行循环时会出现bug,具体如图:

按照这个思路:最终我们发现在创造了5个子进程之后,最后一个子进程对应的file_struct会继承4个父进程的写端!!!这个bug虽然不影响我们的通信,但是会影响我们后续对写端的回收,终止这个进程池,这在我们后面的代码模块有具体讲解!!!

 2.4.2.整体框架的分析

在上面部分内容,我们完成了进程池的创建,接下来就是代码对进程池逻辑的实现了,首先进程池通过父进程来管理5个子进程,当获取到任务时,首先通过父进程接收然后分配给子进程。接着子进程各自处理自己分配到的任务,任务完成后继续接收新的任务。

结合我们通过匿名管道来实现,我们初步设计成父进程作为写端通过匿名管道传输任务给子进程,然后子进程通过读端读取任务来进行任务的调用。那么我们就将整个框架设计为:

进程的创建 --- >管道的搭建--->管道间进程通信的管理--->任务内容的创建--->任务的发布--->子进程进行任务的处理--->资源释放

2.4.3.代码的实现 

这一部分主要是代码的实现,因为篇幅过长并且代码中注释较为详细,我们通过2.4.2.这个篇章在结合代码内容就能大概理解这个demo

work.h

#pragma once
#include<iostream>
#include<functional>
#include<vector>
#include<ctime>using namespace std;// using task_t function<void()>;
typedef function<void()> task_t;void Download()
{cout << "执行下载任务" << " 通过子进程: "<< getpid() <<endl;
}void PrintLog()
{cout << "执行打印日志任务" << " 通过子进程: "<< getpid()<< endl;
}void PushStream()
{cout << "执行传输数据流任务" << " 通过子进程: "<< getpid()<< endl;
}class Init
{
public:Init(){tasks.push_back(Download);tasks.push_back(PushStream);tasks.push_back(PrintLog);srand(time(nullptr) ^ getpid());}// 判断任务的可行性bool CheckSafe(int code){if(code >= 0 && code < tasks.size()) return true;else return false;}void RunTask(int code){// tasks数组中存放着可调用对象,通过()调用return tasks[code]();}int SelectTask(){return rand() % tasks.size();}private:// 任务列表vector<task_t> tasks;// 任务码 (在代码中并没有用上)const int download_code = 0;const int print_code = 1;const int push_stream_code = 2;
};
// 定义全局对象
Init init;

main.cc:

#include <iostream>
#include <assert.h>
#include <vector>
#include <string>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "work.hpp"using namespace std;
#define BUFFSIZE 1024
// aconst int pipe_num = 5;
// 判断是第几个管道
static int name_flag = 1;
class channel
{
public:channel(int fd, pid_t id): _ctrlfd(fd), _workid(id){_name = "channel-" + to_string(name_flag);name_flag++;}int GetFd() const{return _ctrlfd;}pid_t GetId() const{return _workid;}string GetName() const{return _name;}private:int _ctrlfd;pid_t _workid;string _name;
};void ChildWork()
{while (1){int code = 0;// 子进程只读,当父进程没有写入指令时,子进程无法工作// 父进程写4个字节的数据 子进程读取4个字节ssize_t n = read(0, &code, sizeof(code));// 对应任务码if (n == sizeof(code)){// n值正常if (!init.CheckSafe(code))continue;init.RunTask(code);}else if (n == 0){break;}}cout << "子进程已退出" << endl;
}void CreatChannels(vector<channel> &channels)
{vector<int> fd_write;for (int i = 0; i < pipe_num; i++){// 1.定义并创建管道int pipefd[2];int n = pipe(pipefd);cout << "成功创建管道:" << i << endl;assert(n == 0);// 2.创建进程pid_t id = fork();assert(id != -1);// 3.构建单向信道if (id == 0){// 对于子进程来说 只要出现拷贝了父进程的写// 就需要进行关闭,才能实现单向传输的管道if (!fd_write.empty()){for (size_t j = 0; j < fd_write.size(); j++){// 关闭我们插入数组内容close(fd_write[j]);cout << "process: " << getpid() << " close: " << fd_write[j] << endl;}}// child// 子进程关闭当前的 写 端close(pipefd[1]);// 重定向到标准输入dup2(pipefd[0], 0);ChildWork();exit(0);}// fatherclose(pipefd[0]);// 存储写对应的下标相对值fd_write.push_back(pipefd[1]);// 传入这个 写 对应的下标文件给channelchannels.push_back(channel(pipefd[1], id));// 测试父进程的写文件// cout<< pipefd[1] <<endl;}cout << "管道已全部创建,开始执行任务" << endl;
}
void SendCommand(const vector<channel> &channels, int flag = -1)
{int position = 0;while (1){if (flag == 0){break;}sleep(1);// 开始选择任务// 本质上就是获取任务码int command = init.SelectTask();// 分配进程channel c = channels[position++];position %= channels.size();cout << "send command: " << command << " in " << c.GetName() << " by father:" << getpid() << endl;// 发送任务write(c.GetFd(), &command, sizeof(command));flag--;}cout << "任务已完成" << endl;
}
void ReleaseChannel(const vector<channel> &channels)
{for (const auto &e : channels){// 关掉 父进程开辟的写端,注意这里的子进程close(e.GetFd());pid_t rid = waitpid(e.GetId(), nullptr, 0);}
}int main()
{vector<channel> channels;// 创建管道CreatChannels(channels);// 发送任务并执行SendCommand(channels, 5);// 解决子进程回收问题ReleaseChannel(channels);
}

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

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

华为认证FAQ | 考试预约、考券购买常见问题

●考试预约常见问题● Q : 如何进行考试预约&#xff1f; A : 登录“华为人才在线官网” >>参考考试预约操作指引在线预约考试>>检查考试预约记录&#xff0c;确认预约成功 (私信获取考试预约操作指引文档&#xff09;。&#xff08;注&#xff1a;非本人预约…

程序员学CFA——数量分析方法(四)

数量分析方法&#xff08;四&#xff09; 常见概率分布基本概念离散型随机变量与连续型随机变量离散型随机变量连续型随机变量 分布函数概率密度函数&#xff08;PDF&#xff09;累积分布函数&#xff08;CDF&#xff09; 离散分布离散均匀分布伯努利分布二项分布定义股价二叉树…

程序的表示、转换与链接:三、运算电路基础

目录 一、整数加减运算理论二、数字逻辑电路基础和整数加减运算部件三、如何启用逻辑电路&#xff1a;从C表达式到逻辑电路四、C语言中的各类运算 一、整数加减运算理论 整数加减运算 无符号整数加减运算&#xff1a;指针、地址等通常被说明为无符号整数&#xff0c;因而在进行…

pycharm远程连接server

1.工具–部署–配置 2.部署完成后&#xff0c;将现有的项目的解释器设置为ssh 解释器。实现在远端开发 解释器可以使用/usr/bin/python3

Opencv_10_自带颜色表操作

void color_style(Mat& image); Opencv_10_自带颜色表操作&#xff1a; void ColorInvert::color_style(Mat& image) { int colormap[] { COLORMAP_AUTUMN, COLORMAP_BONE , COLORMAP_JET , COLORMAP_WINTER, COLORMAP_RAINBOW , COLOR…

Ts支持哪些类型和类型运算(下)

目录 1、条件判断 &#xff08;extends &#xff1f;&#xff09; 2、推导 infer 3、联合 | 4、交叉 & 5、映射类型 1、条件判断 &#xff08;extends &#xff1f;&#xff09; ts里的条件判断&#xff0c;语法为 T extends XXX ? true : false &#xff0c;叫做…

【Qt 学习笔记】Qt常用控件 | 按钮类控件 | Check Box的使用及说明

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Qt 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ Qt常用控件 | 按钮类控件 | Check Box的使用及说明 文章编号&#xff…

智能时代 | 合合信息Embedding模型荣获C-MTEB榜单第一

目录 前言 1. MTEB与C-MTEB 2. acge模型的优势 3. Embedding模型应用 4. 大模型发展的关键技术 结语 前言 随着人工智能的不断发展&#xff0c;大语言模型吸引着社会各界的广泛关注&#xff0c;支撑模型应用落地的Embedding模型成为业内的焦点&#xff0c;大模型的发展给…

解放生产力:项目管理软件的神奇作用大揭秘!

对于刚刚进入项目管理领域的新人首先要了解的概念就是项目管理软件是什么&#xff1f;项目管理软件的作用&#xff0c;如今的项目管理软件已经非常成熟&#xff0c;融合了一整套的项目管理理论&#xff0c;在管理项目进度、管理工时、团队协同方面发挥着重要作用。 一、项目管理…

vue 关键字变红

1.html <div v-html"replaceKeywordColor(item.title)" ></div> 2.js //value为搜索框内绑定的值 replaceKeywordColor(val) {if (val?.includes(this.value) && this.value ! ) {return val.replace(this.value,<font color"red&…

游戏黑灰产识别和溯源取证

参考&#xff1a;游戏黑灰产识别和溯源取证 1. 游戏中的黑灰产 1. 黑灰产简介 黑色产业&#xff1a;从事具有违法性活动且以此来牟取利润的产业&#xff1b; 灰色产业&#xff1a;不明显触犯法律和违背道德&#xff0c;游走于法律和道德边缘&#xff0c;以打擦边球的方式为“…

【C++】类和对象④(类的默认成员函数:取地址及const取地址重载 | 再谈构造函数:初始化列表,隐式类型转换,缺省值)

&#x1f525;个人主页&#xff1a;Forcible Bug Maker &#x1f525;专栏&#xff1a;C 目录 前言 取地址及const取地址操作符重载 再谈构造函数 初始化列表 隐式类型转换 explicit关键字 成员变量缺省值 结语 前言 本篇主要内容&#xff1a;类的六个默认成员函数中…

Stable Diffusion 模型分享:_CHEYENNE_(欧美漫画)CHEYENNE_v16.safetensors

本文收录于《AI绘画从入门到精通》专栏,专栏总目录:点这里,订阅后可阅读专栏内所有文章。 文章目录 模型介绍生成案例案例一案例二案例三案例四案例五案例六案例七案例八下载地址模型介绍<

吉林省教育学院学报杂志社吉林省教育学院学报编辑部2024年第3期目录

特稿《吉林省教育学院学报》投稿&#xff1a;cn7kantougao163.com 吉林省2023年初中毕业学业水平考试评价与分析报告 Junior High School Teaching Research and Training Department, Jilin Provincial Institute of Education; 1-25 基于吉林省图书馆专利数据资源的吉…

刷题训练之二分查找

> 作者&#xff1a;დ旧言~ > 座右铭&#xff1a;松树千年终是朽&#xff0c;槿花一日自为荣。 > 目标&#xff1a;熟练掌握二分查找算法 > 毒鸡汤&#xff1a;学习&#xff0c;学习&#xff0c;再学习 ! 学&#xff0c;然后知不足。 > 专栏选自&#xff1a;刷题…

解决“找不到MSVCP120.dll”或“MSVCP120.dll丢失”的错误方法

在计算机使用过程中&#xff0c;遇到诸如“找不到MSVCP120.dll”或“MSVCP120.dll丢失”的错误提示并不罕见。这类问题往往会导致某些应用程序无法正常运行&#xff0c;给用户带来困扰。本文旨在详细阐述MSVCP120.dll文件的重要性、其丢失的可能原因&#xff0c;以及解决方法&a…

C++ //练习 12.32 重写TextQuery和QueryResult类,用StrBlob代替vector<string>保存输入文件。

C Primer&#xff08;第5版&#xff09; 练习 12.32 练习 12.32 重写TextQuery和QueryResult类&#xff0c;用StrBlob代替vector保存输入文件。 环境&#xff1a;Linux Ubuntu&#xff08;云服务器&#xff09; 工具&#xff1a;vim 代码块 /*****************************…

Jammy@Jetson Orin - Tensorflow Keras Get Started: 000 setup for tutorial

JammyJetson Orin - Tensorflow & Keras Get Started: 000 setup for tutorial 1. 源由2. 搭建环境2.1 安装IDE环境2.2 安装numpy2.3 安装keras2.4 安装JAX2.5 安装tensorflow2.6 安装PyTorch2.7 安装nbdiff 3. 测试DEMO3.1 numpy版本兼容问题3.2 karas API - model.compil…

STC15L2K60S2-28I-LQFP44 单片机芯片 STC宏晶

STC15L2K60S2-28I-LQFP44 规格信息&#xff1a; 产品类型STC(宏晶) UART/USART2 额定特性- SPI1 USB Device0 USB Host/OTG0 PWM3 I2C&#xff08;SMBUS/PMBUS&#xff09;0 LCD0 工作电压2.4V ~ 3.6V EEPROM 尺度1KB Ethernet0 A/D8x10bit CAN0 D/A3x10bit CPU…

【VI/VIM】基本操作备忘录

简介 新建/打开文件 工作模式 常用命令 补全命令 命令模式输入&#xff1a;ctrl p 移动命令 文本选中 撤销、删除 复制粘贴 替换 缩排 查找 替换 插入 分屏 练习