Linux——进程间通信一(共享内存、管道、systrem V)

一、进程间通信介绍

1.1、进程间通信的概念和意义

进程间通信(IPC interprocess communication)是一组编程接口,让不同进程之间相互传递、交换信息(让不同的进程看到同一份资源)

数据传输:一个进程需要将它的数据发送给另外一个进程

资源共享:多个进程之间共享同样的资源

通知事件:一个进程向另一个或一组发送消息

进程控制:有些进程希望完全控制另一个进程的执行

为什么要进行进程间通信?

以上的行为往往需要多个进程协同、共同完成一些事情

两个进程之间是不能进行”数据”的直接传递的(进程具有独立性)

不要以为,进程独立了就是彻底独立,有时我们需要双方能够进行一定程序的信息交互。

1.2、如何进行进程间通讯及其本质

怎么办?

一般规律

1、交换数据的空间(内存)

2、不能由通信双方任何一个提供(那由谁提供,OS提供) 

具体做法

OS提供的"空间"有不同的样式,就决定了有不同的通信方式

1、管道(匿名、命名)

2、共享内存

3、消息队列
4、信号量

进程间通信的本质:让不同的进程看到同一份资源(一般由OS提供)

为了进程在通信的时候,既能满足进程之间的独立性,又能够到达通信的目的,那么进程之间通信的地点就不能在两个进程中。 一个进程将自己的数据交给另一个进程,并且还要等待另一个进程的应答,这样一来,这个进程将不独立了,受到了另一个进程的影响,这就与进程的独立性矛盾。所以,两个进程进行通信的地点必须是由第三方提供的,第三方只能是操作系统。操作系统提供的这个地点被我们称为:公共资源。公共资源有了,还必须让要通信的进程都看到这一份公共资源,此时要通信的进程将有了通信的前提。之后就是进程通信,也就是访问这块公共资源的数据。

之所以有不同的通信方式,是因为公共资源的种类不一,如果公共资源是一块内存,那么通信方式就叫做共享内存,如果公共资源是一个文件,也就是struct file结构体,那么就叫做管道。

二、管道 

2.1管道介绍

什么是管道?

open("log.txt",w);
open("log.txt",r);

一个文件打开两次,那么在操作系统中会有2个struct file 但是这两个struct file指向同一个缓冲区 

 若父进程3为读端,4为写端,子进程也一样。那么子进程写入,父进程读取缓冲区内容,这是父子进程看到了同一块资源。

这种基于文件的,让不同进程看到同一份资源的通信方式叫做管道

管道只能被设计成单向通信 

如:子进程为写(writer,关掉读端)                                                父进程为读(reader,关掉写端)    当子进程关掉读端/父进程关掉写端对应的struct file没有释放掉,说明 struct file有引用计数(记录多少指针指向我) 当引用计数为0才释放。struct file是允许多个进程通过指针指向的。

为什么父进程最开始用rw方式打开同一个文件呢?                                                                            如果只以r方式打开的话,子进程拷贝完后就也是r;父进程只以w打开,子进程拷贝完也只是w

3.2匿名管道

匿名管道:就是没有名字的文件

如何让不同的进程看到同一份资源?匿名管道的解决办法是:创建子进程,继承父进程的属性信息,也就是说匿名管道可以(只能)进行具有血缘关系的进程进行进程间通信(常用于父子)

为了支持我们进行管道通信,OS提供系统调用pipe()

原型:int pipe(int fd[2]);

头文件unistd.h
功能:创建一无名管道
参数          fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端
返回值:成功返回0,失败返回错误代码 

3.3匿名管道代码

通过系统调用接口创建一个匿名管道

#include <iostream>
#include <cerrno>
#include <cstring>
#include <unistd.h>using namespace std;int main()
{int pipefd[2];int ret = pipe(pipefd); // 一.创建管道if(ret < 0){cerr << errno << ": " << strerror(errno) << endl;}cout << "pipefd[0]: " << pipefd[0] << endl; // 3cout << "pipefd[1]: " << pipefd[1] << endl; // 4return 0;
}

然后就可以创建子进程,关闭不需要的读端或写端

#include <iostream>
#include <cerrno>
#include <cstring>
#include <unistd.h>using namespace std;int main()
{int pipefd[2];int ret = pipe(pipefd); // 一.创建管道if(ret < 0){cerr << errno << ": " << strerror(errno) << endl;}pid_t id = fork(); // 二.创建子进程assert(id != -1);if(id == 0){//子进程  关掉读端,只写close(pipefd[0]);exit(1);}//父进程//关掉写端,只读close(pipefd[1]);close(pipefd[0]); // 父进程,只写,关闭读return 0;
}

这时父子进程已经可以看到同一份资源,可以开始通信了

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<assert.h>void writer(int wfd)
{const char* str = "我是子进程,o.O,我在给你发消息";char buffer[128];int cnt = 0;pid_t pid = getpid();while(1){snprintf(buffer,sizeof(buffer),"message:%s,pid:%d,count:%d\n",str,pid,cnt);write(wfd, buffer, strlen(buffer));cnt++;sleep(1);}close(wfd);
}void reader(int rfd)
{char buffer[1024];int cnt = 10;while(1){size_t n = read(rfd,buffer,sizeof(buffer)-1);if(n>0)printf("父亲获得信息是: %s\n", buffer);else{printf("缓冲区读完了,文件也读完了\n");break;}cnt--;if(cnt==0)break;}close(rfd);
}int main()
{//创建管道int pipefd[2];int n = pipe(pipefd);if(n<0)return 1;pid_t id = fork();if(id == 0){//子进程  关掉读端,只写close(pipefd[0]);writer(pipefd[1]);exit(1);}//父进程//关掉写端,只读close(pipefd[1]);reader(pipefd[0]);int status = 0;pid_t rid = waitpid(id, &status, 0);if(rid == id)printf("退出码为:%d,信号为:%d\n",WEXITSTATUS(status), status & 0x7f);return 0;
}

匿名管道的一些读写现象以及对应的特性

按上面代码将子进程休眠上5s,那么在子进程休眠这段时间,父进程在等待子进程退出休眠(可以理解为管道内无数据)

写端一直写,读端一直不读或者很久读一次:若一次写入一个字符"A",每次写入时cnt++,执行后会发现当cnt=65536时不在写入(也就是写入65536个字节时)65536÷1024=64

在Ubuntu20.04操作系统下默认建立的管道大小为64KB;

管道内部被写满,父进程还没有读取的时候,那子进程要等到父进程来读它

对以上两种情况的总结:

1.管道内部没有数据且子进程不关闭自己的写端文件fd,读端就要阻塞等待直到pipe有数据

2.管道内部被写满且读端不关闭自己的fd,写端写满后就要阻塞等待

由此推断出管道的两种特性:

特性一:自带同步机制

特性二:血缘关系进程进行通信,常见父子

若把父进程休眠时间改短一点,每次父进程读完后,子进程又能继续写入,在此过程中我们不难发现:无论写端写多少个,读端都能一次读完,由此我们发现管道的另一个特性:

特性三:管道是面向字节流的(写多少次和读多少次没有直接关系,称为面向字节流)

当子进程写入10s后退出,而父进程一直读,且打印了返回值,10s后子进程关掉写文件描述符,此时返回值为0;若父进程退出,子进程会僵尸 

3.对于写端而言,不写且关闭pipe,读端会将管道中的数据读完,返回值为0,表示读结束,类是读到了文件的结尾

若写端一直在写,而读端读一会就结束,关闭读文件描述符

4.读端不读且关闭,写端在写,OS会直接终止写入的进程(通过信号13SIGPIPE杀死进程)

由此可以得出管道另外的特性

特性四:父子进程退出,管道自动释放,文件的生命周期是随进程的

特性五:管道只能单向通信,半双工的一种特殊情况(一方传信息时,另一方不能传,如:对讲机)

 5.当要写入的数据量不大于PIPE_BUF(4KB)时,linux将保证写入的原子性。
6.当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。

就是写入数据小于4kb,则次操作为安全的 

有时候公共资源有可能被两个执行流共同访问,访问时会出现信息交叉、数据混乱等问题;由此我们要有一种特性:一段数据、一块空间或一种资源我们要么不访问、要访问就把它改完了,这种特性叫原子性。

3.3进程池

processpool.cc

#include <iostream>
#include <string>
#include <cstdlib>
#include <vector>
#include <unistd.h>
#include <ctime>
#include "task.hpp"using namespace std;enum
{UsageError= 1,ArgError,PipeError
};
void Usage(const std::string &proc)
{cout<<"Usage:"<<proc<<"sub_process_num"<<endl;
}
//用一个类封装管道
class Channel
{
public:Channel(int wfd,pid_t sub_id,const std::string &name)//构造:_wfd(wfd),_sub_process_id(sub_id),_name(name){}void PrintDebug(){cout << "_wfd: " << _wfd;cout << ",_sub_process_id: " << _sub_process_id;cout << ", _name: " << _name << endl;}string name() {return _name;}int wfd() {return _wfd;}pid_t pid() { return _sub_process_id; }~Channel()//析构{}private:int _wfd;//父进程通过此向channel写东西pid_t _sub_process_id;//记录子进程string _name;//channel名字
};//将冗长的创建子进程封装一下
class ProcessPool
{
public:ProcessPool(int sub_process_num) //构造: _sub_process_num(sub_process_num){}int CreateProcess(work_t work) // 回调函数{for (int number = 0; number < _sub_process_num; number++){int pipefd[2]{0};int n = pipe(pipefd);if (n < 0)return PipeError;pid_t id = fork();if (id == 0){// child -> rclose(pipefd[1]);// 执行任务dup2(pipefd[0], 0);work();exit(0);}string cname = "channel-" + to_string(number);// fatherclose(pipefd[0]);channels.push_back(Channel(pipefd[1], id, cname));}return 0;}int NextChannel(){static int next = 0;int c = next;next++;next %= channels.size();return c;}void SendTaskCode(int index, uint32_t code){cout << "send code: " << code << " to " << channels[index].name() << " sub prorcess id: " <<  channels[index].pid() << endl;write(channels[index].wfd(), &code, sizeof(code));}void Debug(){for (auto &channel : channels){channel.PrintDebug();}}~ProcessPool(){}private:int _sub_process_num;vector<Channel> channels;
};int main(int argc ,char* argv[])
{if(argc!=2){Usage(argv[0]);return UsageError;}int sub_process_num = std::stoi(argv[1]);//把进程数转整型if(sub_process_num == 0)return ArgError;//vector<Channel> channels;//把所有的channel(管道)push到vector中,那么对管道的管理就会变成对vector的增删查改//create process// for(int num=0;num<sub_process_num;num++)// {//     int pipefd[2]{0};//     int n = pipe(pipefd);//     if(n<0)//         return PipeError;//     pid_t id = fork();//     if(id == 0)//子进程//     {//         close(pipefd[1]);//         sleep(1);//         exit(0);//     }//     string cname = "channel-"+to_string(num);//     //父进程//     close(pipefd[0]);//     channels.push_back(Channel(pipefd[1],id,cname));// }ProcessPool *proc_ptr = new ProcessPool(sub_process_num);proc_ptr->CreateProcess(worker);//控制子进程// for(auto& e:channels)// {//     e.PrintDebug();// }while(1){// a. 选择一个进程和通道int channel = proc_ptr->NextChannel();// cout << channel.name() << endl;// b. 你要选择一个任务uint32_t code = NextTask();// c. 发送任务proc_ptr->SendTaskCode(channel, code);sleep(1);}//回收、等待子进程delete proc_ptr;return 0;
}

task.hpp

#include <iostream>
#include <unistd.h>using namespace std;typedef void(*work_t)();  //函数指针类型
typedef void(*task_t)();  //函数指针类型void PrintLog()
{cout << "printf log task" << endl;
}void ReloadConf()
{cout << "reload conf task" << endl;
}void ConnectMysql()
{cout << "connect mysql task" << endl;
}task_t tasks[3] = {PrintLog, ReloadConf, ConnectMysql};uint32_t NextTask()
{return rand() % 3;
}void worker()
{// 从0中读取任务即可!while(true){uint32_t command_code = 0;ssize_t n = read(0, &command_code, sizeof(command_code));if(n == sizeof(command_code)){if(command_code >= 3) continue;tasks[command_code]();}cout << "I am worker: " << getpid() << endl;sleep(1);}
}

makefile

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

三、命名管道

管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。
如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。
命名管道是一种特殊类型的文件

man mkfifo:

指令:mkfifo 文件名

功能:创建命名管道文件

命名管道可以从命令行上创建,命令行方法是使用下面这个命令

mkfifo filename

此时就成功地建立了一个命名管道,可以发现它的(文件类型)权限前面的字母是p(pipe),而目录的文件类型是d(directory)。命名管道文件类型是p,而且该文件还有inode,说明在磁盘上是真实存在的。 

 当磁盘中有了命名管道文件以后,两个进程将可以通过这个管道文件进行通信了,步骤和匿名管道非常相似。一个进程以写方式打开管道文件,另一个进程以读端方式打开管道文件。

直接写入的话可以发现会阻塞在这里

它需要被另一个进程读取 

 可以通过unlink或者rm删掉命名管道

系统调用mkfifo以及unlink

第一个形参:管道文件的名字

第二个形参:创建管道文件的权限

返回值:0表示创建成功,-1表示创建失败。

man 2 unlink 

 

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

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

相关文章

如何选择一家适合自己的商城源码?

商城源码的选择取决于多个因素&#xff0c;包括商城的功能需求、稳定性、易用性、可定制性以及价格等。启山智软作为在市场上被广泛认可且表现优异的商城源码提供商&#xff0c;具有以下的特点和优势&#xff1a; 特点①&#xff1a;国内知名的B2B2C开源商城源码系统&#xff…

Golang | Leetcode Golang题解之第213题打家劫舍II

题目&#xff1a; 题解&#xff1a; func _rob(nums []int) int {first, second : nums[0], max(nums[0], nums[1])for _, v : range nums[2:] {first, second second, max(firstv, second)}return second }func rob(nums []int) int {n : len(nums)if n 1 {return nums[0]}…

python conda查看源,修改源

查看源 conda config --show-sources 修改源 可以直接vim .condarc修改源&#xff0c;

vue事件处理v-on或@

事件处理v-on或 我们可以使用v-on指令&#xff08;简写&#xff09;来监听DOM事件&#xff0c;并在事件触发时执行对应的Javascript。用法&#xff1a;v-on:click"methodName"或click"hander" 事件处理器的值可以是&#xff1a; 内敛事件处理器&#xff1…

如何让代码兼容 Python 2 和 Python 3?Future 库助你一臂之力

目录 01Future 是什么? 为什么选择 Future? 安装与配置 02Future 的基本用法 1、兼容 print 函数 2、兼容整数除法 3、兼容 Unicode 字符串 03Future 的高级功能 1. 处理字符串与字节 2. 统一异常处理…

基于SpringBoot的校园台球厅人员与设备管理系统

本系统是要设计一个校园台球厅人员与设备管理系统&#xff0c;这个系统能够满足校园台球厅人员与设备的管理及用户的校园台球厅人员与设备管理功能。系统的主要功能包括首页、个人中心、用户管理、会员账号管理、会员充值管理、球桌信息管理、会员预约管理、普通预约管理、留言…

LRU缓存算法设计

LRU 缓存算法的核⼼数据结构就是哈希链表&#xff0c;双向链表和哈希表的结合体。这个数据结构⻓这样&#xff1a; 创建的需要有两个方法&#xff0c;一个是get方法&#xff0c;一个是put方法。 一些问题&#xff1a;为什么需要使用双向链表呢&#xff1f;因为删除链表的本身&…

第一节 网络安全概述

一.网络空间安全 网络空间&#xff1a;一个由信息基础设施组成相互依赖的网络。 ---- 海陆空天&#xff08;大海、陆 地、天空、航天&#xff09; 通信保密阶段 ---- 计算机安全 ----- 信息系统安全 ----- 网络空间安全 计算机安全&#xff1a;开始秉持着“严于律己&#x…

《安富莱嵌入式周报》第339期:单片机运行苹果早期Mac系统模拟器,2GHz示波器有源探头,下一代矩阵开关面包板,卡片式声音分贝器,HP经典示波器,ReRAM

周报汇总地址&#xff1a;嵌入式周报 - uCOS & uCGUI & emWin & embOS & TouchGFX & ThreadX - 硬汉嵌入式论坛 - Powered by Discuz! 视频版 https://www.bilibili.com/video/BV1Kf421Q7Lh 《安富莱嵌入式周报》第339期&#xff1a;单片机运行苹果早期Ma…

Linux开发讲课33---线程实现与线程控制步骤简析

线程概述 进程是系统中程序执行和资源分配的基本单位。 每个进程都拥有自己的数据段、代码段和堆栈段&#xff0c;这就造成了进程在进行切换等操作时都需要有比较负责的上下文切换等动作。为了进一步减少处理机的空转时间支持多处理器和减少上下文切换开销&#xff0c;进程在演…

IDEA安装IDE Eval Reset插件,30天自动续期,无限激活

第一步&#xff1a; 下载idea 注意&#xff1a;版本要是2021.2.2以下 第二步&#xff1a;快捷键CtrlAlts打开设置 第三步&#xff1a;打开下图中蓝色按钮 第四步&#xff1a;点击弹窗的 “” &#xff0c;并输入 plugins.zhile.io 点击 “ok” 第五步&#xff1a;搜索IDE Ea…

【文献解析】一种像素级的激光雷达相机配准方法

大家好呀&#xff0c;我是一个SLAM方向的在读博士&#xff0c;深知SLAM学习过程一路走来的坎坷&#xff0c;也十分感谢各位大佬的优质文章和源码。随着知识的越来越多&#xff0c;越来越细&#xff0c;我准备整理一个自己的激光SLAM学习笔记专栏&#xff0c;从0带大家快速上手激…

搭建论坛和mysql数据库安装和php安装

目录 概念 步骤 安装mysql8.0.30 安装php 安装Discuz 概念 搭建论坛的架构&#xff1a; lnmpDISCUZ l 表示linux操作系统 n 表示nginx前端页面的web服务 m 表示 mysql 数据库 用来保存用户和密码以及论坛的相关内容 p 表示php 动态请求转发的中间件 步骤 &#xff…

Explore Synapse

rm -r dp-203 -f git clone https://github.com/MicrosoftLearning/dp-203-azure-data-engineer dp-203 cd dp-203/Allfiles/labs/01 ./setup.ps1 -- This is auto-generated code SELECTTOP 100 * FROMOPENROWSET(BULK https://datalakexxxxxxx.dfs.core.windows.net/fil…

分布式共识算法

分布式的基石 分布式共识算法 前置知识&#xff1a;分布式的 CAP 问题&#xff0c;在事务一章中已有详细介绍。 正式开始探讨分布式环境中面临的各种技术问题和解决方案以前&#xff0c;我们先把目光从工业界转到学术界&#xff0c;学习两三种具有代表性的分布式共识算法&…

Python 编程快速上手——让繁琐工作自动化(第2版)读书笔记01 Python基础快速过关

Python 编程快速上手——让繁琐工作自动化&#xff08;第2版&#xff09;读书笔记01 Python基础快速过关 1 python基础概念 Python提供了高效的高级数据结构&#xff0c;还能简单有效地面向对象编程。 python运算符顺序 **——%——//——/——*——-——python中常见的数据…

Linux防火墙使用(firewalld与iptables)

防火墙概述 防火墙是一种由硬件和软件组合而成&#xff0c;在内部网和外部网之间、专有网和公共网之间构造的保护屏障&#xff0c;用以保护用户资料和信息安全的一种技术 防火墙作用在于及时发现并处理计算机网络运行时可能存在的安全风险、数据传输等问题&#xff0c;从而实现…

【游戏客户端】大话版本slg玩法正式上线~~

【游戏客户端】制作率土之滨Like玩法 大家好&#xff0c;我是Lampard家杰~~ 好久好久没有更新博客了&#xff0c;有不少大佬都在后台私信我催更&#xff0c;但是很悲伤这段时间都忙的不行QAQ 那在忙什么呢&#xff1f;就是在制作一个SLG类的玩法【帮派纷争】啦 &#xff0c;布…

数据结构+算法-实现一个计算器

在学习栈的数据结构的时候讲到可以用栈来实现一个计算器的功能&#xff0c;那么这个功能是如何实现的呢&#xff1f; 采用栈模拟得方式来实现一个计算器 要实现如下的功能: 字符串如何转为整数 2.处理加减法 如何处理加减法呢&#xff1f; 5-128 给第一个数字前面放一个号…

Web漏洞扫描工具AppScan与AWVS测评及使用体验

AppScan和AWVS业界知名的Web漏洞扫描工具&#xff0c;你是否也好奇到底哪一个能力更胜一筹呢&#xff1f;接下来跟随博主一探究竟吧。 1. 方案概览 第一步&#xff1a;安装一个用于评测的Web漏洞靶场&#xff08;本文采用最知名和最广泛使用的靶场&#xff0c;即OWASP Benchma…