linux下的进程通信

进程通信

  • 进程为什么需要通信呢?
    • 进程通信的技术背景
    • 进程通信本质
  • 进程通信分类
    • 管道
      • 匿名管道pipe
      • 匿名管道原理
      • 管道特点
    • 命名管道
      • 创建命名管道
      • 命名管道原理
    • System V IPC
      • 管道与 System V的区别
      • 共享内存函数
        • ftok()
        • shmget()
      • shmat()
      • shmdt()
      • shmctl()
      • 删除共享内存
      • System V 代码演示
      • 管道和共享内存总结

进程为什么需要通信呢?

虽然进程都有相对独立性,但是还是需要进行互相通信的,比如说QQ发消息、一个进程想要给另一个进程发送数据、几个进程之间想共享一份数据等等,这些都需要进程进行通信。、

进程通信的技术背景

  • 进程是有独立性的。虚拟地址空间+页表 保证进程运行的独立性(进程内核数据结构+进程的代码和数据)
  • 由于独立性的原因,通信成本会比较高

进程通信本质

  • 进程通信的前提,首先需要让不同的进程看到同一块“内存”(特定的结构组织的)
  • 同一块“内存”,不属于任意进程,是在进程的共享代码段。

进程通信分类

管道

我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”。

# | 命令就是连接两个进程之间的管道命令
# who命令输出两行数据,交给wc -l 进程,统计行数,输出。
who | wc -l 

在这里插入图片描述

匿名管道pipe

功能:创建一个无名管道
int pipe(int fd [2])
参数:
fd:文件描述符数组,其中fd[0],为读端;fd[1]为写端。
返回值:成功返回0,失败返回错误代码。

使用fork(创建父子进程进行通信)来验证管道的原理:
fork之后:子进程会创建一份新的PCB,并且会复制一份父进程的文件描述符数组;像打开文件的信息,管道等都只共享的一份。

下面这段代码实现了,子进程写入管道,父进程从管道读出这一功能:

#include<iostream>
#include<unistd.h>
//#include <string.h>
#include<cstring>
#include<cerrno>
#include<cassert>
#include<cstdlib>
#include<sys/wait.h>
#include<sys/types.h>
int main()
{//利用fork让两个不的进程看到同一份资源int pipefd[2] = {0};//1.创建匿名管道int n = pipe(pipefd);if(n<0)   //创建管道失败{std::cout<<"pipe error, "<<errno<<" : "<<strerror(errno)<<std::endl;return 1;}std::cout<<"pipefd[0]: "<<pipefd[0]<<std::endl;    //0读端std::cout<<"pipefd[1]: "<<pipefd[1]<<std::endl;   //1写端//2.  创建子进程pid_t id = fork();assert(id!=-1);if(id == 0)  //子进程{//3.关掉不需要得fd,实现让父进程读,子进程写得功能close(pipefd[0]);//4.开始通信 --结合某种场景int cnt = 0;while(true){char x = 'X';write(pipefd[1],&x,1);std::cout<<"Cnt: "<<cnt++<<std::endl;sleep(1);}close(pipefd[1]);exit(0);}//父进程//3. 关闭不需要的fd,让父进程读,子进程写close(pipefd[1]);//4.开始通信char buffer[1024];int cnt = 0;while(true){int n = read(pipefd[0], buffer, sizeof(buffer)-1);if(n>0){buffer[n]='\0';std::cout<<"我是父进程,child give me messages:"<<buffer<<std::endl;}else if(n==0){std::cout<<"我是父进程,读到了文件结尾"<<std::endl;break;}else{std::cout<<"我是父进程,读管道异常"<<std::endl;break;}sleep(2);if(cnt++>5) break;}close(pipefd[0]);int status = 0;waitpid(id, &status, 0);std::cout<<" sig: "<<(status &0x7f) << std::endl;sleep(20);return 0;
}

匿名管道原理

fork之后,子进程会复制一份父进程的文件描述符,指向父进程已经打开的文件资源等等。而用父进程创建匿名管道后,会在内存中,开辟一份空间,为父子进程之间提供通信,而父进程创建的这份资源会以fd的形式存在,父进程的打开文件表中。子进程复制后,子进程也能访问。
如下图:
在这里插入图片描述

管道特点

  1. 管道是用来进行具有血缘关系的进程进行进程见通信。—常用于父子通信
  2. 管道具有通过让进程间协同,提供了访问控制。
  • 写快,读慢,写满了不能再写了。
  • 写慢,读快,管道中没有数据的时候,读必须等待
  • 写关,读0,标识读到了文件结尾。
  • 读关,写继续写,OS终止写进程。
  1. 管道提供的是面向流式的通信服务,----面向字节流
  2. 管道是基于文件的,文件的生命周期是随进程的,管道的生命周期是随进程的
  3. 管道是单向通信,属于半双工通信的特殊情况,如果要进行全双工通信,需要创建两个管道。

命名管道

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

创建命名管道

  • 可以从命令行中创建管道:p开头即为管道文件

mkfifo [filename]

在这里插入图片描述

  • 可以使用函数创建命名管道:

int mkfifo(const char* filename, mode_t mode)
第一个参数是创建管道的路径
第二个参数是管道读写的权限,一般是0666

命名管道原理

首先命名管道是一个特殊的文件,因此它可以被打开到内存,但是不会将内存数据刷新到磁盘,该文件在系统中具有唯一路径,因此进程可以通过该路径找到管道文件,进行通信。
下面实现一段功能:父进程创建管道文件,然后从管道读消息,子进程从管道写消息。

//1.comm.hpp
#ifndef _COMM_H_
#define _COMM_H_#include<iostream>
#include<string>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<stdlib.h>
#include<fcntl.h>
#include<cstring>
#include "Log.hpp"
using namespace std;#define MODE 0666
#define SIZE 128string ipcPath = "./fifo.ipc";#endif//2. Log.hpp
#ifndef _LOG_H_
#define _LOG_H_#include<iostream>
#include<ctime>#define Debug 0
#define Notice 1
#define Warning 2
#define Error 3const std::string msg[] = {"Debug","Notice","Warning","Error"
};std::ostream &Log(std::string message, int level)
{std::cout<<" | "<<(unsigned)time(nullptr)<<" | "<<msg[level]<<" | "<<message;return std::cout;
}#endif//3.client.cxx
#include "comm.hpp"int main()
{//1.获取管道文件int fd = open(ipcPath.c_str(), O_WRONLY);if(fd<0){perror("open");exit(1);}//2.ipc过程string buffer;while (true){cout<<"Please Enter Message Line :>";std::getline(std::cin, buffer);write(fd, buffer.c_str(), buffer.size());}//3.关闭文件close(fd);return 0;
}//4. server.cxx
#include "comm.hpp"
#include<sys/wait.h>static void getMessage(int fd)
{char buffer[SIZE];while(true){memset(buffer, '\0', sizeof(buffer));ssize_t s = read(fd, buffer, sizeof(buffer)-1);if(s > 0){cout<<"[ "<<getpid()<<" ]"<<"client sat>:"<<buffer<<endl;}else if(s == 0){//end of filecerr<<"[ "<<getpid()<<" ]"<<"read end of file, client quit, server quit too!"<<endl;break;}else{//read errorperror("read");break;}}
}int main()
{//1.创建管道文件if(mkfifo(ipcPath.c_str(), MODE)<0){perror("mkfifo");exit(1);}Log("管道文件创建成功", Debug)<<" step1"<<endl;//2.正常文件操作int fd = open(ipcPath.c_str(), O_RDONLY);if(fd<0){perror("open");exit(2);}Log("打开管道文件成功",Debug)<<" step 2"<<endl;int nums = 3;for(int i = 0; i < nums; i++){pid_t id = fork();if(id == 0){//3.编写正常的通信代码getMessage(fd);exit(1);}}for(int i = 0; i<nums;i++){waitpid(-1, nullptr, 0);}//4.关闭文件close(fd);Log("关闭管道文件成功",Debug)<<" step 3"<<endl;unlink(ipcPath.c_str());   //通信完毕,删除管道文件Log("删除管道文件成功", Debug)<<" step 4"<<endl;return 0;
}

System V IPC

共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程地址空间,这些进程间的数据传递不再涉及到内核,换句话说,进程不再通过执行进入内核的系统调用来传递彼此的数据。
如下图是 System V的通信方式:
在这里插入图片描述
该方式直接在内存里建立一块空间,供进程通信(进程AB都会在自己的页表上,建立好虚拟地址到物理地址的映射),因此只需访问自己空间的地址,就可以实现进程间通信。

管道与 System V的区别

管道对应的公共资源是文件,而文件是OS内核对应的数据结构,需要操作系统维护,因此需要系统调用来实现。而System V只需要在物理内存上申请一块空间,而内存是用户空间里的内容,用户可以不经过系统调用直接进行访问,直接进行内存级的读写即可。
但是共享内存的提供者是OS,因为OS要管理共享内存,因为OS要先描述再组织->共享内存 = 共享内存快+对应的共享内存的内核数据结构。 申请需要OS管理,但是申请完了之后,用户可以直接访问。

共享内存函数

ftok()

第0步
功能:生成一个唯一的key,供shmget使用生成共享内存段。
int ftok(const char* pathname, int proj_id);
参数:
pathname:必须是存在的、可访问的文件路径
proj_id:至少是8bit的非0数字(自己给定)
返回值:若生成成功,返回唯一的key值。失败返回-1

shmget()

1 .第一步
功能:用来创建共享内存
int shmget(key_t key, size_t size, int shmflg);
参数:
key:这个共享内存段的名字(唯一id)
size:共享内存的大小
shmfg:由九个标志权限构成,他们的用法和创建文件open使用的mode模式标志一样
返回值:成功返回一个非负整数(该段共享内存段的标识码,类似于fd);失败返回-1

  • 说明shmfg参数的具体解释:
  • IPC_CREAT:创建共享内存,如果底层已经存在,获取已存在的id,并且返回;如果不存在,创建共享内存,并返回
  • IPC_EXCL: 单独使用无意义,和上面一起使用:若底层不存在,创建它并返回;若底层存在,出错返回。–>若返回成功的一定是一个全新的shm

shmat()

2.第二步
功能:将共享内存段连接到进程地址空间(在页表上生成映射)
void shmat(int shmid, const void shmaddr, int shmflg)
参数:
shmid:共享内存标识
shmaddr:指定连接的地址(一般填nullptr)
shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回一个指针,指向共享内存的第一个地址;失败返回-1

  • 说明shmflg:
  • shmaddr:为NULL,核心自动选择一个地址
  • shmaddr:不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址
  • shmadd:不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。公式:shmaddr - (shmaddr % SHMLBA)
  • shmflg=SHM_RDONLY,标识该连接操作用作只读共享内存

shmdt()

第3步
功能:将共享内存点与当前进程脱离
int shmdt(const void* shmaddr);
参数:
shmaddr:由shmat返回的指针
返回值:成功返回0, 失败返回-1

注意:将共享内存段与当前进程脱离不等于删除共享内存段!

shmctl()

第四步
功能:用来控制共享内存
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数:
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1

  • 说明:cmd
  • IPC_STAT:把shmid_ds结构中的数据结构设置为共享内存的关联值
  • IPC_SET:在进程有足够权限的前提下,把共享内存的当前关联值设置为shmid_ds数据结构中的值
  • IPC_RMID:删除共享内存段

删除共享内存

首先这种内存共享方式,当我们程序结束后,如果代码不主动删除,那么该内存不会被释放!
我们可以使用代码删除、也可以手动删除,手动删除使用如下命令即可。
注意:key只有在创建的时候才有,其他时候访问共享内存都是用shmid

在这里插入图片描述

System V 代码演示

该功能为:Server创建共享内存,并删除。Cilentt使用该共享内存传输数据。


// Log.hpp
#ifndef _LOG_H_
#define _LOG_H_#include<iostream>
#include<ctime>#define Debug 0
#define Notice 1
#define Warning 2
#define Error 3const std::string msg[] = {"Debug","Notice","Warning","Error"
};std::ostream &Log(std::string message, int level)
{std::cout<<" | "<<(unsigned)time(nullptr)<<" | "<<msg[level]<<" | "<<message;return std::cout;
}#endif//comm.hpp
#pragma once#include<iostream>
#include<cstdio>
#include<unistd.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<cassert>
#include"Log.hpp"using namespace std; //不推荐#define PATH_NAME "/home/xty"
#define PROJ_ID 0x66
#define SHM_SIZE 4096    //共享内存大小,最好是4096的整数倍//shmClient.cc
#include "comm.hpp"int main()
{key_t k = ftok(PATH_NAME, PROJ_ID);if(k<0){Log("creat key failed", Error)<<" client key : "<< k <<endl;exit(-1);}Log("creat key done", Debug)<<" client key : "<< k <<endl;//获取共享内存int shmid = shmget(k, SHM_SIZE, 0);if(shmid < 0){Log("create shm failed", Error) <<" client key : "<< k <<endl;exit(2);}Log(" create shm success", Debug) <<" client key : " << k <<endl;sleep(10);char* shmaddr = (char *)shmat(shmid, nullptr, 0);if(shmaddr == nullptr){Log(" attach shm failed", Error) <<" client key : "<< k<<endl;exit(3);}Log(" attach shm success", Debug) <<" client key : "<< k<<endl;sleep(10);// 使用//去关联int n = shmdt(shmaddr);assert(n!=-1);Log("detach shm success", Debug) << "client key : "<< k <<endl;sleep(10);// client 要不要chmctl删除呢?不需要return 0;
}//shmServer.cc
#include "comm.hpp"string TransToHex(key_t k)
{char buffer[32];snprintf(buffer, sizeof(buffer), "0x%x", k);return buffer;
}int main()
{//1.创建公共的key值key_t k = ftok(PATH_NAME, PROJ_ID);assert(k!=-1);Log("create key done", Debug)<<" server key : "<< TransToHex(k) <<endl;//2.创建共享内存  --建议要创建一个全新的共享内存--通信的发起者int shmid = shmget(k, SHM_SIZE, IPC_CREAT|IPC_EXCL|0666);if(shmid == -1){perror("shmget");exit(1);}Log(" create shm done", Debug) << " shmid : "<< shmid<<endl;sleep(10);//3.将指定的共享内存,挂接到自己的地址空间char * shmaddr = (char *)shmat(shmid, nullptr, 0);Log("attach shm done", Debug) << " shmid : " << shmid <<endl;sleep(10);//通信的逻辑...//4.将指定的共享内存,从自己的地址空间中 去 关联int n = shmdt(shmaddr);assert(n!=-1);(void)n;Log("deatch shm done", Debug) << " shmid : " << shmid <<endl;sleep(10);//5. 删除共享内存, IPC_RMID即便是有进程和当下的shm挂接,依旧删除共享内存n = shmctl(shmid, IPC_RMID, nullptr);assert( n!=-1);(void)n;Log("delete shm done", Debug) << " shmid : " <<shmid<<endl;return 0;
}//makefile
.PHONY:allall:shmClient shmServershmClient:shmClient.ccg++ -o $@ $^ -std=c++11
shmServer:shmServer.ccg++ -o $@ $^ -std=c++11.PHONY:clean
clean:rm -f shmClient shmServer

管道和共享内存总结

管道通信的过程:由键盘->自己定义的缓冲区->进程A->write给内核缓冲区->内核缓冲区给管道文件->管道给内核缓冲区->read读到进程B处->自己定义的缓冲区->打印到屏幕。

共享内存通信:由键盘->自己定义的缓冲区->进程A->直接写入共享内存->进程B读贡献内存->到自己定义的缓冲区->屏幕。

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

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

相关文章

探索数据结构(让数据结构不再成为幻想)

目录 什么是数据结构 数据与结构 什么是算法 复杂度分析 时间复杂度与空间复杂度 时间复杂度 思考&#xff1a; 空间复杂度 常数阶O(1) 对数阶O(logn) 线性阶O(n) 以下为空间复杂度为O(n) 线性对数阶O(nlogn) 平方阶O(n) 指数阶O(2^n) 什么是数据结构 数据结构…

WHAT - CSS Animationtion 动画系列(二)

目录 一、循环波浪二、关键帧呼应三、关键帧顺接四、利用 transform-origin 做拉伸五、大元素可拆分多个小元素联动六、预留视觉缓冲七、随机感&#xff1a;动画周期设置八、抛物线&#xff1a;两个内外div实现x和y向量运动 今天我们主要学习动画实现要素。 一、循环波浪 利用…

类加载机制(双亲委派机制)

文章目录 JVM的作用是什么双亲委派机制加载流程 JVM的作用是什么 我们运行Java程序时&#xff0c;要安装JDK&#xff0c;JDK包含JVM&#xff0c;不同环境的JDK都是不同的。 Java 代码在编译后会形成 class 的字节码文件&#xff0c;该字节码文件通过 JVM 解释器&#xff0c;生…

音视频-H264编码封装- MP4格式转Annex B格式

目录 1&#xff1a;H264语法结构回顾 2&#xff1a;H264编码补充介绍 3&#xff1a;MP4模式转Annex B模式输出到文件示例 1&#xff1a;H264语法结构回顾 在之前文章里介绍过H264的语法结构。 传送门: 视音频-H264 编码NALU语法结构简介 2&#xff1a;H264编码补充介绍 H…

泽众财务RPA机器人常见五个应用场景

泽众RPA&#xff08;即机器人流程自动化&#xff0c;Robotic Process Automation, RPA&#xff09;解决方案是依托于各类先进信息技术手段的虚拟劳动力 &#xff08;数字劳动力&#xff09;&#xff0c;根据预先设定的程序操作指令对任务进行自动化处理&#xff0c;实现业务流程…

网络安全等级保护的发展历程

1994年国务院147号令第一次提出&#xff0c;计算机信息系统实行安全等级保护&#xff0c;这也预示着等保的起步。 2007年《信息安全等级保护管理办法》的发布之后。是等保在各行业深耕落地的时代。 2.0是等保版本的俗称&#xff0c;不是等级。等保共分为五级&#xff0c;二级…

JeeSite V5.7.0 发布,Java快速开发平台,Vite5、多项重构重磅升级

JeeSite V5.7.0 发布&#xff0c;Java快速开发平台&#xff0c;Vite5、多项重构重磅升级 升级内容 新增 参数配置 IP 地址黑白名单过滤器动态参数 新增 侧边栏是否展开第一个菜单的开关 first-open 新增 AesTypeHandler 处理字段数据加密解密或脱敏 新增 JsonTypeHandler …

AI换脸原理(7)——人脸分割参考文献TernausNet: 源码解析

1、介绍 这篇论文相对来说比较简单,整体是通过使用预训练的权重来提高U-Net的性能,实现对UNet的改进。该方法也是DeepFaceLab官方使用的人脸分割方法。在介绍篇我们已经讲过了UNet的网络结构和设计,在进一步深入了解TernausNet之前,我们先简单回顾下UNet。 U-Net的主要结构…

网络基础(三)——网络层

目录 IP协议 1、基本概念 2、协议头格式 2.1、报头和载荷如何有效分离 2.2、如果超过了MAC的规定&#xff0c;IP应该如何做呢&#xff1f; 2.3、分片会有什么影响 3、网段划分 4、特殊的ip地址 5、ip地址的数量限制 6、私有ip地址和公网ip地址 7、路由 IP协议 网络…

Docker尚硅谷_高级篇

Docker尚硅谷 高级篇一、Dockerfile1.1 Dockerfile1.2 构建过程1.3 Dockerfile保留字1.3 构建镜像1.4 虚悬镜像 二、Docker发布微服务2.1 搭建SpringBoot项目2.2 发布微服务项目到Docker容器 三、Docker网络3.1 Docker网络3.2 docker网络命令3.3 Docker网络模式3.4 docker03.5 …

sql注入之bool盲注

目录 盲注步骤 1、进入靶场 2、如下图所示输入&#xff1f;id1‘ 判断此时存在注入点 3、判断列数 ​编辑 4、开始盲注 普通的python脚本 代码思想 结果 二分查找python脚本 二分查找算法思想简介 二分查找与普通查找的主要差距 代码思想 代码 结果​编辑 下面以…

后端项目开发笔记

Maven打包与JDK版本不对应解决方法 我这里使用jdk8。 <build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.8.1</version><configurat…

趣味软件-吃什么(Eat What)?

&#x1f354;&#x1f35c;&#x1f355; 你是否也有这样的日常烦恼&#xff1f; 每天的“世纪难题”——今天吃什么&#xff1f; &#x1f570;️ 饭点到了&#xff0c;脑袋空空&#xff0c;选择困难症大爆发&#xff01; &#x1f46b; 和女朋友约会&#xff0c;却不知道她的…

HackMyVM-Minimal

目录 信息收集 arp nmap nikto whatweb WEB web信息收集 gobuster 文件包含漏洞 提权 web信息收集 main方法 question_1 question_2 question_3 prize.txt 软连接 信息收集 arp ┌──(root?0x00)-[~/HackMyVM] └─# arp-scan -l Interface: eth0, type: E…

.NET_NLog

步骤 1. 添加依赖 ①Microsoft.Extensions.DependencyInjection ②NLog.Extensions.Logging&#xff08;或Microsoft.Extensions.Logging.___&#xff09; Tutorial NLog/NLog Wiki GitHub 2.添加nlog.config文件(默认名称, 可改为其他名称, 但需要另行配置) 文件的基础…

基于Java+SpringBoot+Mybaties-plus+Vue+elememt 驾校管理 设计与实现

一.项目介绍 系统角色&#xff1a;管理员、驾校教练、学员 管理员&#xff1a; 个人中心&#xff1a;修改密码以及个人信息修改 学员管理&#xff1a;维护学员信息&#xff0c;维护学员成绩信息 驾校教练管理&#xff1a;驾校教练信息的维护 驾校车辆管理&…

【数据结构初阶】直接插入排序

最近浅学了直接插入排序&#xff0c;写个博客做笔记&#xff01;笔记功能除外若能对读者老爷有所帮助最好不过了&#xff01; 直接插入排序是插入排序的一种&#xff0c;那么介绍直接插入排序之前先介绍一下常见的排序算法&#xff01; 目录 1.常见的排序算法 2.直接插入排…

57. 【Android教程】相机:Camera

相机现在已经不仅仅是手机必备神器了&#xff0c;甚至相机的拍照质量已经是很多人买手机的首选条件了。而对于相机而言主要有两大功能&#xff1a;拍照片和拍视频。Android 为此两种方式&#xff1a; 相机 intent相机 API 本节我们就一起来看看相机的具体用法。 1. 打开 Camer…

C# Linq中的自定义排序

1.开发过程中&#xff0c;会遇到OrderBy/OrderByDescending排序无法满足的情况&#xff0c;此时就需要自定义排序&#xff0c;按照想要的排序规则取排序&#xff0c;比如订单的状态等等。 2.自定义泛型比较器代码如下&#xff1a; /// <summary>/// 自定义泛型比较器(用…

train_gpt2_fp32.cu - layernorm_forward_kernel3

源码 __global__ void layernorm_forward_kernel3(float* __restrict__ out, float* __restrict__ mean, float* __restrict__ rstd,const float* __restrict__ inp, const float* __restrict__ weight,const float* __restrict__ bias, int N, int C) {cg::thread_block bl…