Linux进程间通信(2)--System V共享内存 | 消息队列 | 信号量

目录

实现原理

使用系统调用创建共享内存

使用shmget函数创建共享内存:

使用shmat函数将共享内挂接到进程地址空间的共享区

使用shmdt函数取消共享内存与进程地址空间的关联

​编辑

 使用shmctl函数释放共享内存

共享内存的属性

System V消息队列  (了解)

System V信号量

理解信号量


这里我们知道进程间通信的本质是: 先让不同的进程看到同一份资源!!!

实现原理

这里我们知道每一个进程都要有地址空间,通过页表映射到内存中。

这里是由操作系统创建一块内存,然后通过页表将其映射到进程地址空间中的共享区上。然后给应用层返回这块内存的起始地址(映射到共享区中的起始地址)。当我们拿到内存的起始地址,就可以在上面进行读/写操作。

如果要释放共享内存,我们要将它与所有的进程去关联(将共享内存在页表里的映射关系进行删除),然后由操作系统释放内存。

因此这里我们在使用共享内存实现进程间通信的步骤大致为:

  • 在内存中开辟一块共享内存。
  • 将共享内存通过页表映射到进程地址空间中的共享区上,获取共享内存的起始地址,然后就可以对共享内存进行读/写操作,实现进程间的通信。
  • 当没有一个进程与共享内存相关联时,共享内存就会被操作自动释放掉。

多个进程之间可能会有多个进程通信,因此在内存中会存在多个共享内存,操作系统为了管理共享内存创建一个struct XXX的结构体,里面存储着各种属性等。将他们用链表的方式管理起来。

注:上述所有都是内核级别的操作,因此要有操作系统完成,因此我们就需要调用一系列有关的系统调用接口。

共享内存时最快的IPC形式,一旦这样的内存空间映射到地址空间中,这些进程间传递的数据就可以直接写道共享内存中。而不是像管道一样先写到语言的缓冲区里,再从语言缓存区里调用系统调用拷贝到内存中。

使用系统调用创建共享内存

使用shmget函数创建共享内存


作用:

在内存中申请一块共享内存。

第一个参数:

  • key是一个数字,这个数字是几并不重要,关键在于它必须在内核中具有唯一性,能让不同的进程进行唯一标识。

key可以唯一标识的共享内存的原因:

这里我们知道共享内存会被struct XXX的结构体用链表管理起来,在结构体里有一个属性就是来记录key的,当我们使用shmget函数时,它会拿着key到管理的链表中找是否存在key,若不存在就创建共享内存,然后赋值给结构体里的key。若找与其相同的key值,看第三个参数,是直接获取该共享内存还是报错返回。

  • 第一个进程可以通过key创建一个共享内存,第二之后的进程,只要拿着同一个key就可以和第一个进程看到同一个共享内存了
  • 对于一个已经创建好的共享内存,key在共享内存的描述对象中。
  • key是具有唯一性的,但是key是我们手动传参的,那么这个key我们是怎么生成的呢?

这里如果我们由我们给key赋值可能会导致各种问题,因此这里设置了一个系统调用来给key赋值:

这里,它是一个算法,它拿着pathname和proj_id进行数值计算,得到一个冲突概率特别小的key值,(理论上pathname和proj_id可以由用户随便写)如果发生冲突,只需要调整一下即可。

  • 通过用户自主设定key值,让通信的进程使用同一个key连接一个共享内存(实现不同进程看见同一份资源)

第二参数:指定共享内存的大小,单位:字节。

第三个参数:这里操作系统指定了具体要填什么,这里我们介绍最主要的三个。

  • IPC_CREATE:当进程创建一个共享内存时,若不存在直接创建;若存在,直接获取。一般单独使用。
  • IPC_EXCL:常与IPC_CREATE一起使用,即:IPC_CRETE | IPC_EXCL :  作用为:如果申请的的共享内存不存在,就创建;存在,报错返回。这样可以确保我们获取的共享内存一定是一个新的。
  • flags :设置共享内存的权限。

这里我们虽然知道了共享内存实现的原理,但是我们并不了解其本身,这里大家可能会疑问:

怎么保证让不同的进程看到同一个共享内存呢?

怎么知道这个共享内存是存在还是不存在呢?

参考上面对key的理解。

返回值:创建成功,返回共享内存的标识符。若失败返回-1,错误码表示失败的原因。

key与shmid的返回值是一样的东西吗?

不是的。key是操作系统内表标定唯一性的。

shmid的返回值只在你的进程内,用来标识资源的唯一性。

在用key创建完之后就不会再用key了,接下来都是用的shmid。

comm3.hpp文件

#pragma once#include<iostream>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<unistd.h>
#include<string>
using namespace std;const int size=4096;
const char* pathname="/home/wzy/进程间的通信/共享内存";const int proj=0x6668;key_t Getkey()
{key_t key=ftok(pathname,proj);if(key<0){cout<<"Getkey failed"<<endl;exit(1);}return key;
}int GetShm()
{key_t key=Getkey();//获得key值int shmid=shmget(key,size,IPC_CREAT|IPC_EXCL|0664);//创建新的共享内存if(shmid<0){cout<<"create share memory failed"<<endl;exit(2);}cout<<"create share memory is success"<<endl;return shmid;
}

processa.cc文件

#include"comm3.hpp"int main()
{int shmid=GetShm();cout<<shmid<<endl;return 0;
}

我们可以发现第一次创建共享内存成功,第二次失败了。

这是因为第一次我们创建了共享内存,当进程退出时,共享内存没有被操作系统自动释放。由于我们在使用shmget函数的第三个参数中由IPC_EXCL这个选项,所以报错。

这里想要查看内存中的共享内存,我们可以使用指令:

ipcs -m

这里perms为共享内存的权限默认为0,需要我们在自己设置;bytes为共享内存的大小,建设置为4096字节的整数倍(因为在Linux上,是以4096字节为单位开空间),如果你设置大小为4097,这里会给你开辟4096*2的空间,但是你只能使用4097;nattch为共享内存与进程的关联数默认。

如果想要删除共享内存,可以使用指令:

ipcrm -m shmid(共享内存标识符)

这里如果我们想要程序再次运行,我们必须要删除第一次创建的共享内存:


因此这里我们可以得出结论:

共享内存的声明周期时随内核的!

用户不主动关闭,共享内存会一直存在(除非内核重启或用户手动释放)。

使用shmat函数将共享内挂接到进程地址空间的共享区

  • 这里第一个参数:为共享内存标识符shmid,为shmget函数成功调用的返回值。
  • 第二个参数:为你想让共享内存挂接到共享区的那个位置。这里由于我们不清楚,所以我们通常将它置为nullptr,交由操作系统决定。
  • 第三个参数:这里我们知道我们给共享内存设置了权限,这里是为了对进程进一步设置权限,这里我们并不关心,我们设置为默认的0.
  • 返回值:成功:为共享内存挂接到地址空间共享区里的起始地址。

        失败为(void) -1。

这里返回值与malloc函数的返回值类似,在未来你想怎么样使用共享内存,就强转成什么类型。如你想使用int,就强转成int *。

#include"comm3.hpp"int main()
{int shmid=GetShm();sleep(5);char* s=(char*)shmat(shmid,nullptr,0);cout<<"共享内存成功挂接到地址空间"<<endl;sleep(5);return 0;
}

使用shmdt函数取消共享内存与进程地址空间的关联

这里参数为shmat函数的返回值,即:共享内存在地址空间共享区的起始地址。

 使用shmctl函数释放共享内存

这里第三个参数是:一个结构体,里面记录着共享内存的各种属性,类似于内核中共享内存的struct 结构体。

第二个参数:操作系统帮我们设定了好:

这里我们讲一下:IPC_STAT ,IPC_SET与IPC_RMID。


这里当我们使用这些系统调用函数后,使进程可以看到共享内存。那么如何使用共享内存进行通信呢?
 

在使用shmat函数时,我们获取了在共享区的起始地址,因此这里我们可以使用解引或数组下标的方式对共享内存进行读写操作,从而实现了进程间的通信。

使用共享内存,实现进程键的通信:一个进行发送信息,一个进程接受信息。

comm3.h

#pragma once#include<iostream>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<unistd.h>
#include<string>
using namespace std;const int size=4096;
const char* pathname="/home/wzy/进程间的通信/共享内存";const int proj=0x6668;key_t Getkey()
{key_t key=ftok(pathname,proj);if(key<0){cout<<"Getkey failed"<<endl;exit(1);}return key;
}int GetShm()
{key_t key=Getkey();//获得key值int shmid=shmget(key,size,IPC_CREAT|IPC_EXCL|0664);//创建新的共享内存if(shmid<0){cout<<"create share memory failed"<<endl;exit(2);}cout<<"create share memory is success"<<endl;return shmid;
}


processa.cc

#include"comm3.hpp"int main()
{int shmid=GetShm();char* s=(char*)shmat(shmid,nullptr,0);cout<<"共享内存成功挂接到地址空间"<<endl;while(true){cout<<"processa get # "<<s<<endl;sleep(1);}shmdt(s);cout<<"解除共享内存与地址空间的关联"<<endl;sleep(5);shmctl(shmid,IPC_RMID,nullptr);cout<<"删除共享内存"<<endl;return 0;
}

processb.cc

#include"comm3.hpp"int main()
{key_t key=ftok(pathname,proj);int shmid=shmget(key,size,IPC_CREAT|0666);cout<<"获取共享内存"<<endl;char* s=(char*)shmat(shmid,nullptr,0);cout<<"将共享内存进行挂接"<<endl;while(true){cout<<"processb say# ";fgets(s,sizeof(s),stdin);}return 0;
}

在我们执行代码的时候发现进行读操作的进程并不会像管道那样同步。这里如果我们想要实现共享内存的同步,我们可以利用管道的同步去实现:

创建一个命名管道,然后在写进程中向管道内写入一个数据,在读进程中读一个数据。这样在读进程中就会阻塞到读操作,一直等待写进程向管道中写入文件。

注意:写进程中向管道中写入数据,在向共享内存中写入数据之后。

读进程中读取数据,要在读共享内存之前。  这里为了好看我对函数对了一定的封装。

comm.hpp

#pragma once#include<iostream>
#include<string>
#include<cstring>
#include<cstdlib>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<sys/ipc.h>
#include<sys/shm.h>const std::string pathname="/home/wzy/进程间的通信/共享内存";
const int proj_id=0x11223344;const std::string filename="fifo";//共享内存的大小,强烈建议设置为n*4096
const int size=4096;key_t Getkey()//获得key值
{key_t key=ftok(pathname.c_str(),proj_id);if(key<0){std::cerr<<"errno:"<<errno<<", errstring: "<<strerror(errno)<<std::endl;exit(1);}return key;
}std::string ToHex(int id)
{char buffer[1024];snprintf(buffer,sizeof(buffer),"0x%x",id);return buffer;
}int CreateShmHelper(key_t key,int flag)
{int shmid=shmget(key,size,flag);if(shmid<0){std::cout<<"errno"<<errno<<"errstring"<<strerror(errno)<<std::endl;exit(2);}return shmid;
}int CreateShm(key_t key)//创建共享内存
{return CreateShmHelper(key,IPC_CREAT|IPC_EXCL|0644);
}int GetShm(key_t key)//获取共享内存
{return CreateShmHelper(key,IPC_CREAT);
}bool MakeFifo()//创建管道
{int n=mkfifo(filename.c_str(),0666);if(n<0){std::cerr<<"errno: "<<errno<<"errstring"<<strerror(errno)<<std::endl;return false;}std::cout<<"mkfifo success...read"<<std::endl;return true;
}

server.cc

#include<iostream>
#include"comm.hpp"
#include"unistd.h"class Init
{
public:Init(){bool r=MakeFifo();if(!r){return ;}key_t key = Getkey();std::cout << "key: " << ToHex(key) << std::endl;shmid=CreateShm(key);std::cout<<"开始将shm映射到进程的地址空间中"<<std::endl;s=(char*)shmat(shmid,nullptr,0);fd=open(filename.c_str(),O_RDONLY);}~Init(){close(fd);shmdt(s);std::cout<<"开始将shm从进程的地址空间中移除"<<std::endl;std::cout<<"开始将shm从OS中删除"<<std::endl;}
public:int shmid;int fd;char* s;};int main()
{Init init;//类的默认初始化会创建共享内存与管道,记录shmid,记录管道的文件描述符,和共享内存在共享区的首地址。while(true){int code=0;ssize_t n=read(init.fd,&code,sizeof(code));//读管道内容if(n>0){std::cout<<"共享内存的内容: "<<init.s<<std::endl;//读共享内存sleep(1);}else if(n==0){break;}}sleep(10);return 0;
}

client.cc

#include<iostream>
#include<cstring>
#include<unistd.h>
#include"comm.hpp"int main()
{//关联共享内存与管道key_t key=Getkey();int shmid=GetShm(key);char* s=(char*)shmat(shmid,nullptr,0);std::cout<<"attach shm done"<<std::endl;int fd=open(filename.c_str(),O_WRONLY);char c='a';for(;c<'z';c++){s[c-'a']=c;std::cout<<"write: "<<c<<"done"<<std::endl;//写共享内存sleep(1);//通知对方int code=1;write(fd,&code,sizeof(code));//写管道}shmdt(s);std::cout<<"detach shm done"<<std::endl;close(fd);return 0;
}

这样就做到了共享内存之间实现同步性。


共享内存的属性

这里使用指令:

man shmctl

我们可以看到两个数据结构:

获取共享内存属性:

#include"comm3.hpp"int main()
{int shmid=GetShm();char* s=(char*)shmat(shmid,nullptr,0);cout<<"共享内存成功挂接到地址空间"<<endl;struct shmid_ds shmds;shmctl(shmid,IPC_STAT,&shmds);cout<<"shm size: "<<shmds.shm_segsz<<endl;cout<<"shm nattch: "<<shmds.shm_nattch<<endl;cout<<"shm _key: "<<shmds.shm_perm.__key<<endl;cout<<"shm mode: "<<shmds.shm_perm.mode<<endl;while(1)sleep(1);shmdt(s);cout<<"解除共享内存与地址空间的关联"<<endl;sleep(5);shmctl(shmid,IPC_RMID,nullptr);cout<<"删除共享内存"<<endl;return 0;
}

注:这里struct shmid_ds操作系统存在这个结构体,不需要我们定义。


System V消息队列  (了解)

  • 消息队列(先进先出)提供了从一个进程向另一个进程发送一块数据的方法。
  • 每个数据块都被认为是一个类型,接收者进程接收的数据块可以由不同类型。
  • IPC资源必须删除,否则不会自动清楚,除非重启,所以System V IPC资源的声明周期随内核。
  • 常用系统调用ftok,msgget(创建消息队列),msgctl(控制消息队列),msgrcv(从消息队列中读取数据),msgsnd(向消息队列发送数据)
ipcs -q (查看消息队列)ipcrm -q 消息队标识符  (删除指定的消息队列)

System V信号量

这里我们知道进程通信的本质是让不同进程看见同一份资源。

但是不同这里也存在着些许问题:在管道中A进程写入一部分,就被B进程拿走,导致双方发和收数据不完整。

这是我们就要通过互斥来解决问题。(互斥:任何时刻,只允许一个执行流访问共享资源)

这里我们把任何时刻只允许一个执行流访问的资源叫做临界资源。

访问临界资源的代码,叫做临界代码。

这里我们可以通过信号量来实现互斥。

理解信号量

这里以电影票为例:

很多人都会看见电影,所以电影院宏观意义上就是临界资源。

里面的座位就是划分成的小资源,你买了票这个作为就是你的了,就不会在属于别人的。

这里买票的本质就是对资源的预定。

每个进程进入临界区,访问临界资源中的一部分资源,不能直接就去访问,而是先申请资源。信号量本质就是一个计数器,类似于int count=你,但也不太准确。

进程获取资源的步骤:

  • 申请信号量,申请成功,就会在临界区为该进程预留一份资源,然后把信号量减1。信号量为0时,进程无法申请成功,只能阻塞等待其他进程退出释放资源,才能再次申请。
  • 访问临界资源,就是执行临界区代码。
  • 释放资源,信号量加1.

信号量就是对临界资源的预定机制!!!

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

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

相关文章

文件上传-Webshell

Webshell简介 webshell就是以aspphpjsp或者cgi等网页文件形式存在的一种命令执行环境&#xff0c;也可以将其称做为一种网页木马后门。 攻击者可通过这种网页后门获得网站服务器操作权限&#xff0c;控制网站服务器以进行上传下载文件、查看数据库、执行命令等… 什么是木马 …

docker 基于容器创建本地web容器化镜像

一、docker 基于容器创建本地web容器化镜像 1、启动指定buysbox 镜像 docker run --name b1 -it busybox:latest 2、创建目录&#xff0c;并创建html mkdir -p /data/html vi index.html 内容自定义例如&#xff1a;<h1>welcome to busybox<h1> 3、新增窗口&am…

类和对象 第六部分第二小节:继承方式

前情提要&#xff1a;继承的语法&#xff1a;class 子类 继承方式&#xff1a;父类 继承方式一共有3种&#xff1a; 代码案例&#xff1a; 前置代码 #include<iostream> using namespace std; class Base1 { public:int m_A; protected:int m_B; private:int m_C; };公共…

linux C编程入门

Ubuntu 下也有一些可以进行编程的工具&#xff0c;但是大多都只是编辑器&#xff0c; 也就是只能进行代码编辑&#xff0c;如果要编译的话就需要用到 GCC 编译器&#xff0c;使用 GCC 编译器肯定就 要接触到Makefile。 1&#xff1a;hello world!!! 我们所说的编写代码包括两部…

Qt网络编程-TCP与UDP

网络基础 TCP与UDP基础 关于TCP与UDP的基础这里就不过多介绍了&#xff0c;具体可以查看对应百度百科介绍&#xff1a; TCP&#xff08;传输控制协议&#xff09;_百度百科 (baidu.com) UDP_百度百科 (baidu.com) 需要知道这两者的区别&#xff1a; 可靠性&#xff1a; TC…

day35 数组map和join方法(字符串拼接)

目录 map方法join方法 map方法 使用场景&#xff1a;map 可以遍历数组处理数据&#xff0c;并且返回新的数组map 也称为映射。映射是个术语&#xff0c;指两个元素的集之间元素相互“对应”的关系。map重点在于有返回值&#xff0c;forEach没有返回值&#xff08;undefined&am…

使用 Docker 镜像预热提升容器启动效率详解

概要 在容器化部署中,Docker 镜像的加载速度直接影响到服务的启动时间和扩展效率。本文将深入探讨 Docker 镜像预热的概念、必要性以及实现方法。通过详细的操作示例和实践建议,读者将了解如何有效地实现镜像预热,以加快容器启动速度,提高服务的响应能力。 Docker 镜像预热…

蓝桥杯(Web大学组)2022国赛真题:水果消消乐

思路&#xff1a; 记录点击次数&#xff0c;点击次数为1时&#xff0c;记录点击下标&#xff08;用于隐藏or消除&#xff09;、点击种类&#xff0c;点击次数为2时&#xff0c;判断该下标所对应种类与第一次是否相同 相同&#xff1a;两个都visibility:hidden &#xff08;占…

阿里云资源包管理

-我守在梦的出口&#xff0c;任凭人来人走 阿里云对象存储中的下行流量包是什么&#xff1f; 阿里云对象存储&#xff08;OSS&#xff09;中的下行流量包是一种预付费的流量套餐&#xff0c;用于支付从阿里云 OSS 存储桶下载数据产生的流量费用。当你的应用程序或用户从 OSS 存…

JS中常用占位符使用方法详解_ |%s|%d|%f|%o|%O|%c|

在 JavaScript 中&#xff0c;%s 是一种字符串格式化占位符&#xff0c;用于将字符串插入到另一个字符串中的指定位置。这种方法基于 C 语言的 printf() 函数&#xff0c;但在 JavaScript 中有一些变化。 在 JavaScript 中&#xff0c;%s 可以接受任何类型的值&#xff0c;并将…

复制和粘贴文本时剥离格式的5种方法(MacWindows)

您可能每天复制和粘贴多次。虽然它是一个非常方便的功能&#xff0c;但最大的烦恼之一就是带来了特殊的格式。从网络上获取一些文本&#xff0c;您经常会发现粘贴到文档中时&#xff0c;它保持原始样式。 我们将展示如何使用一些简单的技巧在不格式化的情况下复制和粘贴。 1.…

95.网游逆向分析与插件开发-游戏窗口化助手-窗口化助手显示与大小调整

内容参考于&#xff1a;易道云信息技术研究院VIP课 上一个内容&#xff1a;地图数据获取的逆向分析与C代码还原 码云地址&#xff08;游戏窗口化助手 分支&#xff09;&#xff1a;https://gitee.com/dye_your_fingers/sro_-ex.git 码云版本号&#xff1a;e85c0fc8b85895c8c…

ZigBee学习——在官方例程上实现串口通信

Z-Stack版本为3.0.2 IAR版本为10.10.1 文章目录 一、添加头文件二、定义接收缓冲区三、编写Uart初始化函数四、编写串口回调函数五、函数声明六、函数调用七、可能遇到的问题(function “halUartInit“ has no prototype) 以下所有操作都是在APP层进行&#xff0c;也就是这个文…

【汇编】简单的linux汇编语言程序

一、Linux系统汇编语言 Linux系统上的汇编语言可以使用不同的语法风格&#xff0c;主要包括Intel语法和AT&T语法。这两种语法有各自的特点和风格区别&#xff0c;尽管它们表示的底层机器指令相同。下面分别对两种语法进行简要说明&#xff1a; Intel语法 Intel语法是由I…

github拉取项目,pycharm配置远程服务器环境

拉取项目 从github上拉取项目到pycharmpycharm右下角选择远程服务器上的环境 2.1. 如图 2.2. 输入远程服务器的host&#xff0c;port&#xff0c;username&#xff0c;password连接 2.3. 选择服务器上的环境 链接第3点 注&#xff1a;如果服务器上环境不存在&#xff0c;先创建…

springboot+vue居民小区设备报修系统

小区报修系统可以提高设施维护的效率&#xff0c;减少机构的人力物力成本&#xff0c;并使得维修人员可以更好地了解维护设备的情况&#xff0c;及时解决问题。 对于用户来说&#xff0c;报修系统也方便用户的维修请求和沟通&#xff0c;提高了用户的满意度和信任。其次小区报修…

在 MacOS 上虚拟化 x86Linux 的最佳方法(通过 Rosetta)

categories: [VM] tags: MacOS VM 写在前面 买了 ARM 的 mac, 就注定了要折腾一下虚拟机了… 之前写过一篇文章是通过 utm 虚拟化archlinux, 其实本质上还是调用了 qemu-system-x86_64, 所以速度并不快, 后来想着能不能借用 Rosetta 的优势即原生转译, 来虚拟化 Intel 的 Linu…

【学网攻】 第(24)节 -- 帧中继(点对点)

系列文章目录 目录 系列文章目录 文章目录 前言 一、帧中继是什么&#xff1f; 二、实验 1.引入 实验拓扑图 实验配置 在帧中继中配置通信链路​编辑 实验验证 文章目录 【学网攻】 第(1)节 -- 认识网络【学网攻】 第(2)节 -- 交换机认识及使用【学网攻】 第(3)节 --…

Low 级别反射型 XSS 攻击演示(附链接)

环境准备 如何搭建 DVWA 靶场保姆级教程&#xff08;附链接&#xff09;https://eclecticism.blog.csdn.net/article/details/135834194?spm1001.2014.3001.5502 测试 打开 DVWA 靶场并登录&#xff0c;找到反射型 XSS 页面&#xff08;笔者这里是 Low 级别&#xff09; 先…

【PyQt】09-控件提示信息、Lable标签

文章目录 前言一、控件提示信息1.1 代码1.2 解释 < b >在HTML标签中的作用1.3 添加按键后的代码运行结果 二、QLabel控件介绍2.1 内容2.2 常用的事件2.3 代码结果展示 总结 前言 1、控件提示信息 2、QLabel控件介绍 一、控件提示信息 关键点在于 效果如图所示&#x…