Linux之进程间通信(下)

server/2024/9/23 11:19:11/

目录

命名管道

命名管道的创建

匿名管道和命名管道的区别

命名管道的代码实现

共享内存 

创建共享内存

关联共享内存

去关联共享内存 

删除共享内存 

共享内存特点 

共享内存代码实现 

IPC资源总结


命名管道

上期我们学习了匿名管道,匿名管道本质就是一个文件,且匿名管道适用于有血缘关系的进程进行通信,那如果有两个没有血缘关系的进程,那么这两个进程该如何进行通信呢?这就需要我们今天学习的命名管道,命名管道适用于没有血缘关系的两个进程进行通信。

我们知道,两个进程要进行通信的前提就是两个进程看到同一份资源,所以对于两个没有血缘关系的进程,通信的前提就是让这两个进行看到同一份资源。所以我们就需要创建命名管道,命名管道本质上也是文件。

命名管道的创建

命名管道有两种创建方法。

  1. 在命令行输入指令进行创建:mkfifo 管道名称
  2. 在程序中进行创建:int mkfifo(const char* filename,mode_t mode),第一个参数为创建的管道的路径,第二个为管道的访问权限,因为命名管道也是一个文件。

匿名管道和命名管道的区别

  1. 匿名管道使用pipe函数进行创建并打开
  2. 命名管道使用mkfifo函数进行创建,使用open函数打开。
  3. 因为命名管道和匿名管道本质都是文件,所以打开之后,后续的进程的读写操作都是类似的。

在这里声明一下,按照正常的操作流程,文件的读写如上图所示,写文件时,进程1先调用系统接口wirte()将要写的数据舒心到内核缓冲区,然后调用驱动中的接口write()将数据最终刷新到文件中,写完毕。进程2读文件时操作系统先把底层文件中的数据通过驱动中的read()接口读取到文件内核缓冲区,然后进程2调用read()接口从文件缓冲区中读取到数据,读完毕。

上面的操作步骤是一个正确完整的步骤,但是对于进程间通信而言,使用匿名管道和命名管道进行通信时,并不像上述的操作步骤一样进行读写,而是写时不去调用驱动中的接口刷新到底层文件,读时不从底层文件读取到文件内核缓冲区,省去了底层文件的交互,转化为从文件内核缓冲区中进行交互,这样可以大大的提升效率。

命名管道的代码实现

sever端口进行命名管道的创建以及数据的读取。代码如下

#include"comm.h"int main()
{//创建匿名管道if(mkfifo(PATH,0666)<0){perror("mkfifo");return 1;}//创建管道成功,打开管道int fd=open(PATH,O_RDONLY);if(fd<0){perror("open");return 2;}//进行读取操作char buff[64]={0};while(1){int num=read(fd,buff,sizeof(buff)-1);if(num>0){printf("%s\n",buff);}else if(num==0){perror("client quit");break;}else{perror("read");break;}}close(fd);return 0;
}

client端口进行命名管道中数据的写入。

#include"comm.h"
#include<string.h>int main()
{//sever端已经创建了命名管道,client只需要打开即可//打开文件int fd=open(PATH,O_WRONLY);if(fd<0){perror("open");return 1;}//client端进行写操作const char* msg="hello yjd";while(1){ write(fd,msg,strlen(msg));}close(fd);return 0;
}

comm.h中存放client和sever端需要的头文件以及生成的命名管道路径。

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>#define PATH "./fifo"

 运行结果如下。

我们发现sever端口确实读取到了client端口写入的数据。

共享内存 

共享内存是基于system v标准下的一个IPC资源。无论是匿名管道还是命名管道,本质上都是基于文件的共享资源。而共享内存则是基于内存的,相当于在内存中开辟了一块空间作为两个进程的公共资源。示意图如下。

总体来说,操作系统在内存中申请一块空间作为共享内存,然后通过页表映射到进程的虚拟地址空间中,此时站在进程的角度就可以通过使用虚拟地址空间中的内存达到了访问共享内存的目的。 

但是与此同时也产生了几个问题。站在用户的角度,怎么创建共享内存?两个进程怎样和共享内存关联起来,又怎样去除关联?怎样删除共享内存? 下来我们一一解答。这个四个问题刚好也是操作共享内存的整个流程。

创建共享内存

形参含义:key为在操作系统层面识别唯一共享内存的一个值。size为创建的共享内存的大小一般为一页4KB,shmflg为一个标记值,当shmflg为IPC_CREATE时,为创建一个共享内存,如果当前-+key值对应的共享内存不存在则创建,存在则返回存在的共享内存。当shmflg为IPC_CREATE|IPC_EXCL时,为如果key值所对应的共享内存不存在则创建,存在在返回错误码。

返回值:如果创建成功则返回一个id,这个id为一个标识码,可用于用户识别唯一共享内存。 失败返回-1。

其中的key值我们一般使用ftok函数进行获取。

形参含义:pathname为我们随意指定的一个路径,proj_id为我们随意制定的一个整型。

返回值:成功则返回一个key值,用于操作系统标识唯一共享内存。失败则返回-1。 

关联共享内存

形参含义: shmid为创建共享内存成功时返回的值,即共享内存对于用户的唯一标识。剩下两个参数不用了解,无脑NULL和0即可。

返回值:若进程关联共享内存成功,返回值为共享内存映射到进程虚拟地址空间中的首地址,失败则返回-1。

去关联共享内存 

形参含义:shmaddr为关联共享内存函数shmat的返回值。

返回值:成功返回0,失败返回-1。 

删除共享内存 

形参含义:shmid为创建共享内存时的返回值。cmd设置为IPC_RMID即可,最后一个buf默认设置为空即可。

返回值:成功则返回0,失败返回-1。 

共享内存特点 

  1. 共享内存的生命周期是随着操作系统的内核的。而之前的命名和匿名管道都是文件,文件都是由进程打开的,所以命名和匿名管道的周期都是随进程的。

  2. 共享内存中数据的读写不需要任何缓冲区,写端一写,读端就立即可以读。所以共享内存是进程间通讯速度最快的一种通信方式。

共享内存代码实现 

sever端口创建共享内存,并进行数据的读取。

#include"comm.h"
#include<unistd.h>int main()
{//获取keykey_t key=ftok(PATH,NUM);if(key<0){perror("ftok");return 1;}printf("ftok success\n");//创建共享内存 int id = shmget(key,SIZE,IPC_CREAT|IPC_EXCL|0666);if(id<0){perror("shmget");return 2;}printf("shmget success\n");//创建成功,进程1绑定共享内存 char* mem=(char*)shmat(id,NULL,0);printf("shmat success\n");//逻辑操作while(1){sleep(3);printf("%s\n",mem);}//去关联共享内存shmdt(mem);//删除共享内存shmctl(id,IPC_RMID,NULL);return 0;
}

client端进行数据的写入。

#include"comm.h"
#include<unistd.h>int main()
{//得到一个key值,这个key必须与sever端的key值一样,才能保证得到相同的共享内存key_t key=ftok(PATH,NUM);if(key<0){perror("ftok");return 1;}//得到一个与sever端相同的共享内存int id=shmget(key,SIZE,IPC_CREAT);if(id<0){perror("shmget");return 2;}//进程2绑定共享内存char* mem=(char*)shmat(id,NULL,0);printf("shmat success\n");//处理业务逻辑char ch='A';while(ch <= 'Z'){mem[ch-'A'] = ch;ch++;mem[ch-'A'] = 0;sleep(2);}//去关联共享内存shmdt(mem);//共享内存的删除进程2不用参与,因为进程1创建并删除共享内存return 0;
}

comm.h中存放client端和sever端需要的头文件。

#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>#define PATH "./"
#define NUM  0x6666
#define SIZE 4096

运行结果如下。

运行结果符合预期。

以上便是共享内存的所有内容。

IPC资源总结

共享内存,消息队列和信号量,其实他们都是system V标准下的IPC资源,只要是IPC资源,那么操作这些资源的接口以及描述这些资源的数据结构就都一定是类似的。

在内存中,操作系统创建共享内存可能有多个,操作系统为了讲这些共享内存管理起来,按照先描述,后组织的六子真言,操作系统创建了shmid_ds类型的结构体用来描述共享内存,这个结构体的第一个成员变量struct ipc_perm也是一个结构体,第一个成员变量就是操作系统表示唯一共享内存的一个值。

消息队列的结构体如上图,我们发现与共享内存相似。 

信号量的结构体如上图,与共享内存也相似,所以这也验证了IPC资源的接口与结构体类似的结论。至于为什么类似,是因为底层有一个指针数组,是struct ipc_perm* arr[N]类型的指针数组,首先把每个IPC资源的结构体对象的地址强转为struct ipc_perm*类型赋值给指针数组的元素,最终访问IPC资源所对应的结构体时,指针数组的元素会被强转为对应结构体类型的指针变量去访问对应类型的结构体的成员变量。

以上便是进程间通信的所有知识点。

本期内容到此结束^_^


http://www.ppmy.cn/server/103121.html

相关文章

【初阶数据结构】算法复杂度

目录 一、算法效率 1.1 为什么要衡量算法的好坏 1.2 算法的复杂度 1.3 复杂度在校招中的考察 二、时间复杂度 2.1 时间复杂度的概念 Func1 执行的基本操作次数 &#xff1a; 2.2 大O的渐进表示法 常见复杂度对比 一般算法常见的复杂度如下&#xff1a; ​编辑 2.3常…

华硕飞行堡垒键盘全部失灵【除电源键】

华硕飞行堡垒FX53VD键盘全部失灵【除电源键】 前言一、故障排查二、发现问题三、使用方法总结 前言 版本型号&#xff1a; 型号 ASUS FX53VD&#xff08;华硕-飞行堡垒&#xff09; 板号&#xff1a;GL553VD 故障情况描述&#xff1a; 键盘无法使用&#xff0c;键盘除开机键外…

bootchart抓Android系统启动各阶段性能数据

最近在做Android系统启动优化&#xff0c;首要任务是找到启动过程中各阶段耗时点&#xff0c;进而有针对性地进行优化。主要用bootchart抓开机数据&#xff0c;本文主要记录下工具的使用方法。 1.抓开机数据 adb root adb shell ‘touch /data/bootchart/enabled’ adb rebo…

SQLserver中的exists

在 SQL Server 中&#xff0c;EXISTS 是一个布尔子句&#xff0c;用于检查子查询是否返回任何行。如果子查询返回至少一行数据&#xff0c;EXISTS 将返回 TRUE&#xff1b;如果子查询没有返回任何行&#xff0c;EXISTS 将返回 FALSE。EXISTS 通常用于 WHERE 或 HAVING 子句中&a…

人脸识别设计

总体思路 人脸识别使用的算法思路为&#xff1a;首先&#xff0c;定位一张图像中所有的人脸位置&#xff1b;其次&#xff0c;对于同一张脸&#xff0c;当光线改变或者朝向方位改变时&#xff0c;算法还能判断是同一张脸&#xff1b;然后找到每一张脸不同于其他脸的独特之处&a…

【Jquery】jquery的简单使用

目录 1.常用的选择器学习 2.操作元素的属性 3.操作元素的内容 4.操作元素的样式 5.Jquery的事件机制 6.Ajax的使用 1.常用的选择器学习 获取要操作的HTML元素对象&#xff0c;jquery会将符合要求的HTML元素放入到数组中返回。 &#xff08;1&#xff09;ID选择器 $("…

STM32后备区域:读写BKP备份寄存器与使用RTC实时时钟详解

目录 STM32后备区域&#xff1a;读写BKP备份寄存器与使用RTC实时时钟详解 1 什么是STM32的后备区域 分割线* 2.1 BKP备份寄存器简介 2.2 BKP备份寄存器基本结构 2.3 BKP外设头文件 bkp.h介绍 2.4 读写 BKP备份寄存器 操作步骤 2.5 编写 读写备份寄存器 5.1 文件介绍 …

【网络安全】在403页面实现RCE

未经许可,不得转载。 文章目录 正文正文 进行子域名测试时,通常使用以下几种工具进行子域名枚举: Amass Assetfinder Haktrails Subdominator Subfinder接着,我找到一个子域名:admin.redacted.org,其状态码为403: 因此我想到找出所有状态码为403的子域名,然后使用Dir…