04.进程间通信

news/2024/5/21 5:34:45

进程间通信基本概念

IPC(Inter Process Communication)

进程间通信

进程通信就是不同进程之间进行信息的交换或传播

为什么进程之间实现通信和困难

因为进程之间具有独立性,数据独立,程序可能独立也可能不独立(父子进程的程序时一样的)
所以要想进行进程间的数据交换,必须借助一个第三方的资源,进程一可以对这个资源进行读写,进程二也可以对这个资源进行读写,如图所示

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这个第三方的资源实际上就时OS提供的内存区域

进程通信的本质

进程通信的本质就时让不同进程访问同一份第三方资源,这个第三方资源可以由OS的不同模块提供,因此出现在不同的进程间的通信方式

进程通信的目的

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

管道

一个进程连接到另一个进程的数据流称为管道

管道符(|)

管道符就时利用这种管道的通信方式
进程1 | 进程2
ls / | wc -l

匿名管道

用于有血缘关系的进程间的通信

本质

进程通信的本质就是让不同的进程访问同一份第三方资源,而管道中第三方资源就是同一份打开的文件,父子进程都可以对这份文件进行读写,从而实现进程间的通信。
然后就是这个打开文件的位置,这个文件不可能放在磁盘中,因为磁盘的IO效率太慢了,所以它应该是在内存之中

pipe函数

创建管道的函数
int pipe(int pipefd[2])
pipefd[0] pipefd[1] 管道的读写端,传出参数
返回值:0 或 -1

因为管道是实现父子进程间的通信的,所以一般要配合fork函数使用

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

实现子进程向父进程中写入hello

#include<stdio.h>
#include<string.h>                                                              
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<sys/stat.h>
int main(int argc, char* argv[])
{int pipefd[2];pipe(pipefd);//创建匿名管道,pipefd[0]读端,pipefd[1]写端int pid = fork();if(pid == 0){//子进程写close(pipefd[0]);//关闭读端// close(pipefd[1]);int write_count = write(pipefd[1],"hello",6);printf("write_count = %d\n",write_count);}else{//父进程读close(pipefd[1]);//关闭写端char buf[1024]={'\0'};int read_count = read(pipefd[0],buf,sizeof(buf));//当写段开着但管道中没有数据的时候,不会直接返回-1,而是阻塞等待管道中数据的出现//如果写段没开,不会阻塞等待,直接然后0 printf("read_count = %d,buf = %s\n",read_count,buf);}while(1);return 0;
}

管道中状态的描述

初始时管道中没有数据:写端write返回成功读入的字节数,读端read阻塞等待管道中数据的产生
管道中有数据但没满:写端write返回成功读入的字节数,读端read返回成功读入的字节数
管道中数据满了:写端write阻塞等待,读端read返回成功读入的字节数
写端关闭:读端read返回成功读入的字节数,如果没有写入,总会有读完的时候,这个时候不会阻塞等待直接返回0
读端关闭:写端write会异常终止进程(被SIGPIPE信号杀死)

模拟ls / | wc -l

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

#include<stdio.h>
#include<string.h>                                                              
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
int main(int argc, char* argv[])
{int pipefd[2];pipe(pipefd);int pid = fork();if(pid > 0){//父进程//ls /内容由stdout转到管道的写端close(pipefd[0]);dup2(pipefd[1],1);execlp("ls","ls","/",NULL);}else{//子进程//wc -l读的由stdin到读端close(pipefd[1]);dup2(pipefd[0],0);execlp("wc","wc","-l",NULL);}while(1);return 0;
}

命名管道

创建管道文件有两种方式

mkfifo fifo文件
调用函数mkfifo(path,0644)

mkfifo函数

创建命名管道文件
int mkfifo(const char* pathname,mode_t mode)

wififo.c

#include<stdio.h>
#include<string.h>                                                              
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<sys/stat.h>int main(int argc, char* argv[])
{// mkfifo("fifo",0644);int fd=open("./fifo",O_WRONLY);int write_count = write(fd,"hello",6);printf("write_count = %d\n",write_count);return 0;
}

riffo.c

#include<stdio.h>
#include<string.h>                                                              
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>int main(int argc, char* argv[])
{int fd=open("./fifo",O_RDONLY);char buf[1024]={'\0'};int read_count = read(fd,buf,sizeof buf);printf("read_count = %d,buf = %s\n",read_count,buf);return 0;
}
如果写端没开,读端read会阻塞等待写端的打开(和匿名管道不同)
如果读端没开,写端write会阻塞等待读端的打开(和匿名管道不同)

命名管道的打开规则

在没有设置非阻塞属性的条件下,fifo读端和写端只要有一个没有打开,就会阻塞等待另一个的打开
在设置非阻塞属性的条件下,fifo的读端没有打开,立即返回失败,fifo写端没有打开,立即返回成功

user1和user2聊天实现

main.c
#include<stdio.h>
#include<string.h>                                                              
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<sys/stat.h>
int main(int argc, char* argv[])
{mkfifo("./u1r_u2w_fifo",0644);mkfifo("./u1w_u2r_fifo",0644);   return 0;
}
u1.c
#include<stdio.h>
#include<string.h>                                                              
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>int main(int argc, char* argv[])
{//首先发送和结束数据不应该有先后顺序,所以应该一个进程接收,一个进程发送int pid = fork();if(pid == 0){//子进程,发送数据//打开u1的写管道int fd = open("./u1w_u2r_fifo",O_WRONLY);while(1){char buf[1024];//从终端把输入读到buf里面int r_count = read(0,buf,sizeof(buf));//buf中的数据写到写管道里面write(fd,buf,r_count);}}else{//父进程接收数据//打开u1的读管道int fd = open("./u1r_u2w_fifo",O_RDONLY);while(1){char buf[1024];//从管道中把数据读到buf中int r_count = read(fd,buf,sizeof buf);//把buf中的数据写道终端中write(1,buf,r_count);}}return 0;
}
u2.c
#include<stdio.h>
#include<string.h>                                                              
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>int main(int argc, char* argv[])
{//首先发送和结束数据不应该有先后顺序,所以应该一个进程接收,一个进程发送int pid = fork();if(pid == 0){//子进程,发送数据//打开u2的写管道int fd = open("./u1r_u2w_fifo",O_WRONLY);while(1){char buf[1024];//从终端把输入读到buf里面int r_count = read(0,buf,sizeof buf);//buf中的数据写到写管道里面write(fd,buf,r_count);}}else{//父进程接收数据//打开u2的读管道int fd = open("./u1w_u2r_fifo",O_RDONLY);while(1){char buf[1024];//从管道中把数据读到buf中int r_count = read(fd,buf,sizeof buf);//把buf中的数据写道终端中write(1,buf,r_count);}}return 0;
}

死锁问题

前提:我们都知道命名管道假设没有设置非阻塞属性的话,读写俩端必须都打开才能进行数据的传输,只要有一个没有打开,另外一个就会阻塞等待。比如写端没有打开读端就会阻塞等待写端的打开
如果想实现进程间互相通信,必须有俩个命名管道文件,假设叫fifo1和fifo2,现在假如在进程1中fifo1的写端打开了,进程2中fifo2的读端打开了,这种情况就会造成死锁问题,因为进程1在阻塞等待fifo1读端的打开,fifo2在阻塞等待fifo2写端的打开,双方互相等待,造成死锁问题

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

解决方法:让fifo1的读端打开,fifo1的写端打开,fifo2的读端打开,fifo2的写端打开,放在四个进程中,单独执行,及时阻塞,但并不影响执行

管道通信总结

进程间的通信就如它表面上一样,即不同进程之间进行信息的交流,而本质上是因为进程与进程之间是独立的,也就是说修改进程1中的数据对进程2不会产生影响,所以为了实现进程之间的交流必须引入一个第三方资源,不同进程之间都可以对第三方资源进程读写操作,这样就完成了进程之间的通信。因为第三方资源可以由操作系统的不同模块提供,也就产生了各种各样的通信方式,管道就是其中之一,管理利用的第三方资源是内存中的文件(也就虚拟内存)。管道又分为匿名管道和命名管道。匿名管道只能用于有血缘关系进程间的通信,因为匿名管道相当于一个没有文件名的虚拟文件,只有在父子进程中,它打开该虚拟文件的fd才是一样的,这样就可以对同一个打开的虚拟文件进行读写操作,从而实现进程间的通信。而命名管道则可以实现任意两个进程间的通信,因为它是有名字的,不同进程中通过open它的路径就是找到该虚拟文件。
以上就是我对管道通信的一些理解。

内存映射

共享内存

俩个进程共享同一块虚拟内存

什么是内存映射?

内存映射指的是将磁盘文件中的数据映射到内存之中,这样修改内存就相当于修改了磁盘
image-20240421152206887

mmap函数

void char* mmap(void *addr, size_t length,int prot, int flags, int fd, off_t offset)

参数描述:
addr:磁盘文件映射到内存中的地址,如果不特殊指定,为NULL即可
length:要映射多少字节,一般为1024
prot:映射区域的保护方式是什么
PROT_READ//可读
PROT_WRITE//可写
flags:映射这段区域的属性是什么
MMAP_SHARED//创建共享映射,内存的写入即磁盘文件的写入
MMAP_PRIVATE//创建私有映射
MMAP_ANONYMOUS//创建匿名映射
fd:映射磁盘文件的文件描述符
offset:文件偏移量

munmap函数

解除映射区域
int munmap(void *addr, size_t length);

匿名映射

匿名映射相当于没有磁盘文件映射到内存之中,这段映射内存会被初始化为0
#include<stdio.h>
#include<string.h>                                                              
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<sys/mman.h>
int main(int argc, char* argv[])
{char *ptr = (char*)mmap(NULL,1024,PROT_READ | PROT_WRITE,MAP_SHARED | MAP_ANONYMOUS,-1,0);int pid = fork();if(pid == 0 ){//子进程strcpy(ptr,"hello world");printf("child ps ptr = %s\n",ptr);}else{//父进程sleep(1);printf("father ps ptr = %s\n",ptr);}return 0;
}

文件映射

磁盘中文件的数据映射到内存中,对内存的修改就相当于对磁盘文件的修改

wmmap.c

#include<stdio.h>
#include<string.h>                                                              
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<sys/mman.h>
#include<stdlib.h>
int main(int argc, char* argv[])
{int fd = open("./mmap.txt",O_RDWR | O_CREAT,0644);ftruncate(fd,1024);char *ptr=(char*)mmap(NULL,1024,PROT_WRITE | PROT_READ,MAP_SHARED,fd,0);if(ptr == MAP_FAILED){perror("mmap error:");exit(1);}int i = 0;while(1){sprintf(ptr,"-------------%d-----------\n",i++);sleep(1);}return 0;
}

rmmap.c

#include<stdio.h>
#include<string.h>                                                              
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<sys/mman.h>
#include<stdlib.h>
int main(int argc, char* argv[])
{int fd=open("./mmap.txt",O_RDWR);char *ptr=(char*)mmap(NULL,1024,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);if(ptr == MAP_FAILED){perror("mmap error:");exit(1);}while(1){printf("ptr = %s\n",ptr);sleep(1);}return 0;
}

网络问题

sudo nmcli network off

sudo nmcli network on

消息队列

message queue

消息队列的优点

如果要实现一条一条消息发送的这种通信方式,管道和内存映射没有办法满足,因为它俩本质就是文件,没办法区分这是第一条消息,那是第二条,除非你加上某种特定的方式

基本概念

消息队列是内核中维护的消息链表,是面向消息进行通信的,每次读取一条完整的消息。每条消息具有的属性为:
1.消息本身内容
2.消息数据部分长度
3.表示消息优先级的整数

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

头文件

#include <fcntl.h>
#include <sys/stat.h>
#include <mqueue.h>

mq_open函数

打开消息队列
mqd_t mq_open(const char* name,int flags)
mqd_t mq_open(const char* name,int flags,mode_t mode,struct mq_ *attr)
1.返回值是消息队列的fd,mqd
2.name消息队列的名字
3.flags,mq的访问模式,O_RDONLY,O_WRONLY,O_RDWR,O_NONBLOCK,O_CREAT,O_EXCL
4.mode,mq的访问权限,0644
5.attr,mq的属性

mq_close函数

关闭消息队列

int mq_close(mqd_t mqd);

mq_getattr函数

获取消息队列属性

int mq_getattr(mode_t mqd,struct mq_attr *attr);

mq_setattr函数

设置消息队列属性

int mq_setattr(mode_t mqd,struct mq_attr *newattr,struct mq_attr *oldattr);

mq_send函数

向消息队列中发送消息

int mq_send(mqd_t mqd,const char *buf,size_t msg_len,unsigned int msg_prio)

mq_receive函数

接收消息队列中传来的消息

int mq_receive(mqd_t mqd,const char *buf,size_t msg_len,unsigned int * msg_prio)

mq_unlink函数

删除消息队列队列名

int mq_unlink(const char* name);

消息队列的四个属性

struct mq_attr{long mq_flags;//是否阻塞long mq_maxmsg;//最大消息数long mq_msgsize;//最大消息的大小,1024*9long mq_curmsgs;//当前消息的个数
}
#include<stdio.h>
#include<string.h>                                                              
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<mqueue.h>
#include<stdlib.h>int main(int argc, char* argv[])
{mqd_t mqd=mq_open("/msgqueue",O_RDWR | O_CREAT,0644,NULL);int w_count = mq_send(mqd,argv[1],strlen(argv[1]),atoi(argv[2]));if(w_count == -1){perror("w_count:");exit(1);}return 0;
}
#include<stdio.h>
#include<string.h>                                                              
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<mqueue.h>
#include<stdlib.h>int main(int argc, char* argv[])
{mqd_t mqd=mq_open("/msgqueue",O_RDWR,0644,NULL);char buf[8192];int flags;int r_count = mq_receive(mqd,buf,sizeof buf,&flags);if(r_count == -1){perror("w_count:");exit(1);}printf("buf = %s\n",buf);return 0;
}
#include<stdio.h>
#include<string.h>                                                              
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<mqueue.h>
#include<stdlib.h>
#include<sys/stat.h>
int main(int argc, char* argv[])
{int mqd = mq_open("/msgqueue",O_RDONLY);if(mqd == -1){perror("open mq error:");exit(1);}struct mq_attr attr;int ret = mq_getattr(mqd,&attr);struct mq_attr attr_new;attr.mq_maxmsg=20;attr_new=attr;mq_setattr(mqd,&attr_new,&attr);if(ret == -1){perror("getattr error:");exit(1);}printf("mq_flags = %lu\n",attr_new.mq_flags);printf("mq_maxmsg = %lu\n",attr_new.mq_maxmsg);printf("mq_msgsize = %lu\n",attr_new.mq_msgsize);printf("mq_curmsgs = %lu\n",attr_new.mq_curmsgs);return 0;
}

http://www.mrgr.cn/p/84434630

相关文章

大数据------JavaWeb------Tomcat(完整知识点汇总)

Web服务器——Tomcat Web服务器定义 它是一个应用程序&#xff08;软件&#xff09;&#xff0c;对HTTP协议的操作进行封装&#xff0c;使得程序员不必直接对协议进行操作&#xff0c;让Web开发更便捷 Web服务器主要功能 封装HTTP协议操作&#xff0c;简化开发将Web项目部署到…

【Azure App Service】列举为App Service集成虚拟网络(VNET)操作时所需要的最小权限

问题描述 作为Azure资源管理人员,对每一种资源操作时,都需要考虑权限设置。否则,会遇见类似如下错误:The client *************** with object id ********-****-****-****-************ does not have authorization to perform action Microsoft.Network/virtualNetworks…

简单的神经网络

一、softmax的基本概念 我们之前学过sigmoid、relu、tanh等等激活函数&#xff0c;今天我们来看一下softmax。 先简单回顾一些其他激活函数&#xff1a; Sigmoid激活函数&#xff1a;Sigmoid函数&#xff08;也称为Logistic函数&#xff09;是一种常见的激活函数&#xff0c…

精准读取CSV/Excel数据 - 灵活指定行列范围的 Python 解决方案

文章目录 源代码项目简介导入相关库__file_exists 装饰器函数的签名和注释主要功能的实现运行演示读取 Excel 文件 源代码 https://github.com/ma0513207162/PyPrecip。pyprecip\reading\read_api.py 路径下。 项目简介 PyPrecip 是一个专注于气候数据处理的 Python 库&#xf…

【C语言】整数和浮点数在内存中的存储

大家可能在学习的时候会经常疑惑数据在内存中是怎样存储的&#xff0c;今天用一篇博客给你讲清楚&#xff01;&#xff01;&#xff01;从此不再疑惑&#xff01;&#xff01;&#xff01; 文章目录 1. 整数在内存中的存储2. 大小端字节序和字节序判断2.1 什么是大小端2.2 为什…

Makdown语法合集

目录页 1. Markdown使用平台1.1. VS Code 平台1.1.1. 安装链接 1.1.2. Markdown使用的插件安装1.1.2.1. Markdown All in On 1.1.2.2. Markdown Preview Enhanced2. 普通文本编写规则2.1. 标题使用样式 2.2. 标题目录的生成 2.3. 自动添加章节号码 2.4. 常规格式 2…

【Java】获取近六个月的年月

以当前月份为标准,向前获取近6个月的年月(year_month)形成列表数据库里面存储的字段类型就是varchar,数据格式就是类似2024-12这样的年月格式。 目标: 以当前月份为标准,向前获取近6个月的年月(year_month)形成列表// 获取近6个月的年月列表List<String> recentM…

esp32-cam 2. python opencv 拉取摄像头内容

0. 环境 - win10 python3 - pycharm - esp32-cam http://192.168.4.1 1. 创建工程 File -> Create Project -> -> Location: E:\Workspaces\PycharmProjects\esp32cam_opencv -> Create 2. opencv hello 2.1 添加脚本 File -> New -> Python f…

Java 集合-List

集合主要分为两组(单列集合, 双列集合) Connection 接口有两个重要的子接口LIst 和 Set, 它们的实现子类都是单列集合, Map 接口的实现子类是双列集合, 存放的是 K-V Connection 接口 Collection 接口和常用方法 下面以 ArrayList 演示一下 add: 添加单个元素remove: 删除指…

T2,3,4,5,9动态背包问题

本文主要介绍常见的四种背包问题前言 本文主要介绍常见的四种背包问题,思维导图如下:一、01背包💡 现有 N 件物品和一个最多能承重 M 的背包,第 i 件物品的重量是 wi​,价值是 vi​。在背包能承受的范围内,试问将哪些物品装入背包后可使总价值最大,求这个最大价值。因为…

luogu P4342[IOI1998]Polygon

题目大意 给定一个多边形,对应节点上标记有一个数字,每条边上标记有加(t)或乘(x)表示相邻两个节点可进行的操作,操作后两个节点将合并为一个节点,首先删去一条边(不进行操作),之后在若干次操作后使得该多边形只剩一个节点,且要求所剩节点标记的数最大化,询问最大的…

ES底层原理

1、倒排索引 Elasticsearch 使用一种称为倒排索引的结构,它适用于快速的全文搜索。 有倒排索引,肯定会对应有正向索引:正向索引(forward index) 反向索引(inverted index,实际就是倒排索引)所谓的正向索引,就是搜索引擎会将待搜索的文件都对应一个文件ID,搜索时将这个…

Spring Security基础教程:从入门到实战

作者介绍&#xff1a;✌️大厂全栈码农|毕设实战开发&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。 推荐订阅精彩专栏 &#x1f447;&#x1f3fb; 避免错过下次更新 Springboot项目精选实战案例 更多项目&#xff1a;CSDN主页YAML墨韵 学如逆水行舟&#xff0c…

Jenkins(超详细的Docker安装Jenkins教程!!!)

Jenkins Jenkins&#xff0c;原名 Hudson&#xff0c;2011 年改为现在的名字。它是一个开源的实现持续集成的软件工具。 官方网站&#xff1a;https://www.jenkins.io/ 中文文档&#xff1a;https://www.jenkins.io/zh/ 为什么需要Jenkins&#xff1f; 我们以前写完代码&a…

24-有参转录组实战10-差异基因KEGG富集分析

KEGG富集分析##########下面做KEGG######## emapper <- read.delim("out.emapper.annotations") emapper[emapper=="-"] <- NA#change "-" to "NA" emapper <- emapper[-(49584:49586),]#remove the final 3 rows DE <- r…

学习笔记:【QC】Android Q - IMS 模块

一、IMS init 流程图 高清的流程图参考&#xff1a;【高清图&#xff0c;保存后可以放大看】 二、IMS turnon 流程图 高清的流程图参考&#xff1a;【高清图&#xff0c;保存后可以放大看】 三、分析说明 1、nv702870 不创建ims apn pdp 2、nv702811 nv702811的时候才创建…

基于FPGA实现的HDMI TO MIPI扩展显示器方案

FPGA方案&#xff0c;HDMI IN接收原始HDMI 信号&#xff0c;输出显示到LCD 屏上 客户应用&#xff1a;扩展显示器 主要特性&#xff1a; 1.支持2K以下任意分辨率显示 2.支持OSD 叠加多个图层 3.支持MIPI/EDP/LVDS/RGB屏 4.支持放大缩小匹配屏分辨率 5.零延时&#xff0c;输…

docker部署nginx并配置https

1.准备SSL证书&#xff1a; 生成私钥&#xff1a;运行以下命令生成一个私钥文件。 生成证书请求&#xff08;CSR&#xff09;&#xff1a;运行以下命令生成证书请求文件。 生成自签名证书&#xff1a;使用以下命令生成自签名证书。 openssl genrsa -out example.com.key 2048 …

【js】将一维数组处理成树形数据并且实现模糊查询

项目中由于数据量不大&#xff0c;后台并未做处理&#xff0c;因此前端拿到返回的Table数据需要处理成树形数据再渲染到表格中 原始数据 const dataList[{"id": 44,"seedlingName": "测试2","seedlingType": "测试2",&quo…