【Linux】进程间通信之共享内存/消息队列/信号量

文章目录

  • 一、共享内存的概念及原理
  • 二、共享内存相关接口说明
    • 1.shmget函数
    • 2.ftok函数
    • 3.shmat函数
    • 4.shmdt函数
    • 5.shmctl函数
  • 三、用共享内存实现server&client通信
    • 1.shm_server.cc
    • 2.shm_client.cc
    • 3.comm.hpp
    • 4.查看ipc资源及其特征
    • 5.共享内存的优缺点
    • 6.共享内存的数据结构
  • 四、system V消息队列
  • 五、system V信号量
  • 六、IPC资源的组织方式

一、共享内存的概念及原理

共享内存是通过让不同的进程看到同一个内存块的方式。

我们知道,每一个进程都会有对应的PCB-task_struct ,独立的进程地址空间,然后通过页表将地址映射到物理内存中。此时我们就可以让OS在内存中申请一块空间,然后将创建好的内存空间映射到进程的地址空间中,两个需要进行通信的进程经过映射之后就看到了同一块内存空间,在未来两个进程不想进行通信之后,取消进程和内存的映射关系—去关联,然后释放内存即可。

在这里插入图片描述

我们需要注意的是,进程地址空间,是专门设计的,用来IPC,共享内存是一种通信方式,所以想通信的进程,都可以使用,那么OS中一定会同时存在很多的共享内存。

二、共享内存相关接口说明

1.shmget函数

功能:用来创建共享内存
原型
int shmget(key_t key, size_t size, int shmflg);
头文件
#include <sys/ipc.h>
#include <sys/shm.h>
参数
key:这个共享内存段名字
size:共享内存大小
shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
其中最常用的两个标志位:
IPC_CREAT:如果文件不存在就创建,存在就获取
IPC_EXCL:无法单独使用,IPC_CREAT | IPC_EXCL,如果不存在就创建,如果存在就出错返回,这样就保证如果创建成功,一定是一个新的shm
返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1

2.ftok函数

功能;获取key
原型
key_t ftok(const char *pathname, int proj_id);
头文件
#include <sys/types.h>
#include <sys/ipc.h>
参数
pathname:文件名
proj_id:非0整数,用于形成key
返回值:成功返回key,失败返回-1,错误码被设置

我们在申请内存使用malloc函数需要告诉要申请多大的空间,而释放的时候只需要告诉内存的起始地址即可,那么操作系统是如何知道我们申请了多大的内存呢,实际上,我们在申请内存的时候,OS为我们提供了申请的空间和该空间的属性,如大小等等。一个程序多次申请空间,那么操作系统就需要将这些空间管理起来,管理的方法是先描述再组织,描述的对象的就是这些空间的属性。共享内存也是如此,OS先描述再组织,所以共享内存=共享内存块+共享内存的相关属性。那么我们创建共享内存的时候,如何保证共享内存在系统中是唯一的,答案是通过key来进行唯一标识,那么key在哪呢,key在共享内存的属性struct shm{key_t key;};中

3.shmat函数

功能:将共享内存段连接到进程地址空间
原型
void *shmat(int shmid, const void *shmaddr, int shmflg);
头文件
#include <sys/types.h>
#include <sys/shm.h>
参数
shmid: 共享内存标识
shmaddr:指定连接的地址
shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回一个指针,指向共享内存第一个节(起始地址);失败返回-1
shmaddr为NULL,核心自动选择一个地址
shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址。
shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。公式:shmaddr - 
(shmaddr % SHMLBA)
shmflg=SHM_RDONLY,表示连接操作用来只读共享内存

4.shmdt函数

功能:将共享内存段与当前进程脱离
原型
int shmdt(const void *shmaddr);
头文件
#include <sys/types.h>
#include <sys/shm.h>
参数
shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段

5.shmctl函数

功能:用于控制共享内存
原型
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作(有三个可取值)
IPC_STAT:将shmid的属性拷贝到buf中
IPC_SET:将buf中的属性写入到shmid中
IPC_RMID:是否shmid的内存
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1

在这里插入图片描述

三、用共享内存实现server&client通信

1.shm_server.cc

#include "comm.hpp"int main()
{key_t key = getkey();printf("key:0x%x\n", key);int shmid = createShm(key);printf("shmid:%d\n", shmid);char *start = (char *)attachShm(shmid);printf("attach success, address start: %p\n", start);while (true){printf("client say : %s\n", start);// struct shmid_ds ds;// shmctl(shmid, IPC_STAT, &ds);// printf("获取属性: size: %d, pid: %d, myself: %d, key: 0x%x",//        ds.shm_segsz, ds.shm_cpid, getpid(), ds.shm_perm.__key);sleep(1);}// 去关联detachShm(start);// 删除共享内存delShm(shmid);return 0;
}

2.shm_client.cc

#include "comm.hpp"int main()
{key_t key = getkey();printf("key:0x%x\n", key);int shmid = getShm(key);printf("shmid:%d\n", shmid);char *start = (char *)attachShm(shmid);printf("attach success, address start: %p\n", start);const char *message = "hello server, 我是另一个进程,正在和你通信";pid_t id = getpid();int cnt = 1;// char buffer[1024];while (true){snprintf(start, MAX_SIZE, "%s[pid:%d][消息编号%d]", message, id, cnt++);// snprintf(buffer, sizeof(buffer), "%s[pid:%d][消息编号:%d]", message, id, cnt++);// memcpy(start, buffer, strlen(buffer)+1);// pid, count, messagesleep(1);}detachShm(start);return 0;
}

3.comm.hpp

#ifndef __COMM_HPP_
#define __COMM_HPP_#include <iostream>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <unistd.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/ipc.h>#define PATHNAME "."
#define PROJ_ID 0x666// 共享内存的大小,一般建议是4KB的整数倍
// 系统分配共享内存是以4KB为单位的! --- 内存划分内存块的基本单位Page
#define MAX_SIZE 4096 // --- 内核给你的会向上取整, 内核给你的,和你能用的,是两码事key_t getkey()
{key_t key = ftok(PATHNAME, PROJ_ID);if (key == -1){std::cerr << "error" << strerror(errno) << std::endl;exit(1);}return key;
}int getShmHelper(key_t key, int flags)
{int shmid = shmget(key, MAX_SIZE, flags);if (shmid < 0){std::cerr << errno << ":" << strerror(errno) << std::endl;exit(2);}return shmid;
}int getShm(key_t key)
{return getShmHelper(key, IPC_CREAT);
}int createShm(key_t key)
{return getShmHelper(key, IPC_CREAT | IPC_EXCL | 0600);
}void *attachShm(int shmid)
{void *mem = shmat(shmid, nullptr, 0);if ((long long)mem == -1L){std::cerr << "shmat:" << errno << ":" << strerror(errno) << std::endl;exit(3);}return mem;
}void detachShm(void *start)
{if (shmdt(start) == -1){std::cerr << "shmdt:" << errno << ":" << strerror(errno) << std::endl;}
}void delShm(int shmid)
{if (shmctl(shmid, IPC_RMID, nullptr) == -1){std::cerr << "shmctl:" << errno << ":" << strerror(errno) << std::endl;}
}
#endif

4.查看ipc资源及其特征

查看IPC资源的指令

ipcs -m/-q/-s
分别查看共享内存/消息队列/信号量
删除使用  ipcrm -m shmid

在这里插入图片描述

IPC资源的特征

共享内存的生命周期是随OS的,不是随进程的

我们使用ctrl C终止程序之后,第二次显示文件存在

在这里插入图片描述

5.共享内存的优缺点

优点:在所有进程间通信中是最快的,能大大的减少数据拷贝的次数

共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到

内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据

在这里插入图片描述

同样的代码,如果用管道来实现,综合考虑管道和共享内存,考虑键盘输入和显示器输出,共享内存和管道各有几次数据拷贝

在这里插入图片描述

对于管道,我们将输入的数据线拷贝到我们自己定义的缓冲区buffer中,然后通过系统调用write写到管道中,然后通过read读到自己定义的缓冲区buffer中,然后再拷贝到显示器上,这里我们不考虑输入时从键盘到stdin的拷贝,stdout到显示器的拷贝,所以管道一共需要拷贝4次

对于共享内存,我们只需要将数据拷贝到共享内存中,然后从共享内存中拷贝到显示器上,同样这里不考虑键盘到stdin,stdout到显示器的拷贝,所以一共需要拷贝2次

缺点:不能进行同步和互斥操作,没有对数据做任何保护

6.共享内存的数据结构

struct shmid_ds {struct ipc_perm shm_perm; /* operation perms */int shm_segsz; /* size of segment (bytes) */__kernel_time_t shm_atime; /* last attach time */__kernel_time_t shm_dtime; /* last detach time */__kernel_time_t shm_ctime; /* last change time */__kernel_ipc_pid_t shm_cpid; /* pid of creator */__kernel_ipc_pid_t shm_lpid; /* pid of last operator */unsigned short shm_nattch; /* no. of current attaches */unsigned short shm_unused; /* compatibility */void *shm_unused2; /* ditto - used by DIPC */void *shm_unused3; /* unused */
};

在这里插入图片描述

struct shmid_ds结构体中包含了struct ipc_perm结构体,其中就包含了key

四、system V消息队列

消息队列就是内核提供了队列这种数据结构用于进程间通信,两个进程可以相互通信,队列的每一个节点包含了类型标识符,表示是各自读的数据还是写的数据

消息队列的接口如下:

int msgget(key_t key, int msgflg);
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);

消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法

每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值特性方面

IPC资源必须删除,否则不会自动清除,除非重启,所以system V IPC资源的生命周期随内核

这里就不做过多的介绍了,消息队列我们了解即可。

五、system V信号量

信号量本质是一个计数器,通常用来表示公共资源中,资源数量的多少问题的。公共资源是被多个进程同时可以访问的资源。访问没有保护的公共资源,那么就会导致数据不一致的问题。

我们为什么要让不同的进程看到同一份资源呢,因为我们要进行通信,进程间实现协同,但是进程具有独立性,我们让进程看到就提出一些方法,但是又引入了一些新的问题(数据不一致问题)

我们未来将被保护起来的公共资源:临界资源,大部分是独立的

资源(内存,文件,网络等)是要被使用的,如何被进程使用呢,一定是该进程有对应的代码来访问这部分临界资源,这部分代码被称为临界区,其他的代码被称为非临界区

我们如何对数据进行保护呢:同步和互斥。互斥就是我在访问临界资源的时候,其他人不能够进行访问

原子性:要么不做,要做就做完,只有两态

为什么要有信号量呢?

我们举一个例子,我们在电影院看电影,我们不是只有坐在电影院对应的座位上的时候这个座位CIA属于我,而是我们买票的时候,就对放映厅中的座位进行了预定,此时座位就已经属于我们了。即当我们想要某种资源的时候,我们可以进行预定。假如放映厅有100个座位,那么电影院会不会卖出101张票呢,答案是不会,我们该如何做到呢。我们可以定义一个信号量,初始值为100,卖出一张票就-1,为0的时候就停止售票

信号量sem=100
sem--;//预定资源   --- P操作
访问公共资源
sem++;//释放公共资源  --- V操作

所以的进程在访问公共资源之前,都必须先申请sem信号量 ->先申请信号量的前提,是所有进程都必须先看到同一个信号量 ->信号量本身就是公共资源 -> 信号量本身也要保证自身的安全,–,++ ->信号量必须保证自身操作的安全性,–,++操作是原子

共享资源可以被当做一个整体资源,也可以被划分为一个一个资源的子部分

当一个信号量的初始值为1的时候,那么该信号量我们称为二元信号量,具有互斥的功能

由于各进程要求共享资源,而且有些资源需要互斥使用,因此各进程间竞争使用这些资源,进程的这种关系为进程的互斥

系统中某些资源一次只允许一个进程使用,称这样的资源为临界资源或互斥资源。

在进程中涉及到互斥资源的程序段叫临界区

IPC资源必须删除,否则不会自动清除,除非重启,所以system V IPC资源的生命周期随内核

六、IPC资源的组织方式

我们可以发现system V三种方式的数据结构如下:

共享内存:

在这里插入图片描述

消息队列:

在这里插入图片描述

信号量:

在这里插入图片描述

我们发现,他们的接口的相似度非常高,这样说明他们都是system V方式的进程间通信

我们可以创建一个struct ipc_perm *perms[]的数组,用于存放struct ipc_perm

我们创建一个共享内存数据结构的对象 struct shmid_ds myshm,然后perms[0] = &myshm.shm_perm

创建一个消息队列的数据结构对象 struct msqid_ds mymsg, prems[1] = &mymsg.msg_perm

然后我们在获取共享内存的属性的时候,就可以通过如下的方式进行获取

(struct shmid_ds*)perms[0] ->其他属性

这是因为一个结构体的第一个成员的地址,在数字上,和结构体对象本身的地址数字是相等的

这种方式向C++的多态一样,数组中的对象为基类,具体的对象(共享内存,消息队列,信号量)为子类

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

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

相关文章

【GIS】JDK版本升级到17后,GeoServer的图层无法通过openLayer预览

JDK版本升级到17后&#xff0c;图层无法通过openLayer预览 1. 错误图示 终端输出的错误 网页端无法显示图层&#xff0c;并且输出错误提示 2.原因猜测 估计可能是由于java17的模块化&#xff0c;Java被分成了多个独立部署和运行的模块&#xff0c;这使得Java应用能够更快…

Wireshark添加自定义协议解析

最终效果如下&#xff1a; 参考文档&#xff1a;https://mika-s.github.io/topics/ 此参考文档中7个例子教我们如何编写lua脚本去识别我们自定义的协议 安装Wireshark https://www.wireshark.org/上下载安装包安装即可。我的安装路径是D:\Install\Wireshark&#xff0c;在W…

kafka学习笔记--基础知识概述

本文内容来自尚硅谷B站公开教学视频&#xff0c;仅做个人总结、学习、复习使用&#xff0c;任何对此文章的引用&#xff0c;应当说明源出处为尚硅谷&#xff0c;不得用于商业用途。 如有侵权、联系速删 视频教程链接&#xff1a;【尚硅谷】Kafka3.x教程&#xff08;从入门到调优…

IT新闻资讯系统,使用mysql作为后台数据库,此系统具有显示数据库中的所有信息和删除两大功能。

表的准备&#xff1a; -- MySQL Administrator dump 1.4 -- -- ------------------------------------------------------ -- Server version 5.1.40-community /*!40101 SET OLD_CHARACTER_SET_CLIENTCHARACTER_SET_CLIENT */; /*!40101 SET OLD_CHARACTER_SET_RESULTSCHAR…

nodejs+vue+微信小程序+python+PHP个性化服装搭配系统APP-计算机毕业设计推荐 android

考虑到实际生活中在个性化服装搭配方面的需要以及对该系统认真的分析,将app权限按管理员和用户这两类涉及用户划分。 (a) 管理员&#xff1b;管理员使用本系统涉到的功能主要有个人中心、用户管理、个性穿搭管理、我的衣橱管理、服饰分类管理、我的收藏管理、系统管理等功能。 …

Unity使用打成图集的Sprite作为模型贴图使用的问题

大家好&#xff0c;我是阿赵。   有时候用Unity引擎做项目的时候&#xff0c;会遇到这样的需求&#xff0c;美术做了一些模型或者特效&#xff0c;然后策划想在游戏运行的时候&#xff0c;读取一些游戏图标放在特效或者模型上面当做贴图使用。   这个需求实现起来很简单&am…

【Hive】启动beeline连接hive报错解决

1、解决报错2、在datagrip上连接hive 1、解决报错 刚开始一直报错&#xff1a;启动不起来 hive-site.xml需要配置hiveserver2相关的 在hive-site.xml文件中添加如下配置信息 <!-- 指定hiveserver2连接的host --> <property><name>hive.server2.thrift.bin…

DIP——边缘提取与分割

1.使用canny算法进行边缘提取 本实验比较简单&#xff0c;基本思路是对原图像进行一个高斯模糊处理&#xff0c;用于去噪&#xff0c;之后转换为灰度图&#xff0c;直接调用cv库中的canny记性边缘提取。若想直接得到彩色边缘&#xff0c;则通过按位与操作&#xff0c;将原始彩色…

【小沐学Python】Python实现TTS文本转语音(speech、pyttsx3、百度AI)

文章目录 1、简介2、Windows语音2.1 简介2.2 安装2.3 代码 3、pyttsx33.1 简介3.2 安装3.3 代码 4、ggts4.1 简介4.2 安装4.3 代码 5、SAPI6、SpeechLib7、百度AI8、百度飞桨结语 1、简介 TTS(Text To Speech) 译为从文本到语音&#xff0c;TTS是人工智能AI的一个模组&#xf…

Redis 持久化 —— 超详细操作演示!

四、Redis 持久化 四、Redis 持久化4.1 持久化基本原理4.2 RDB持久化4.3 AOF持久化4.4 RDB与AOF对比4.5 持久化技术转型 五、Redis 主从集群六、Redis 分布式系统七、Redis 缓存八、Lua脚本详解九、分布式锁 数据库系列文章&#xff1a; 关系型数据库: MySQL —— 基础语法大全…

ISNAS-DIP: Image-Specific Neural Architecture Search for Deep Image Prior

ISNAS-DIP&#xff1a;用于深度图像先验的图像特定神经架构搜索 论文链接&#xff1a;https://arxiv.org/abs/2111.15362v2 项目链接&#xff1a;https://github.com/ozgurkara99/ISNAS-DIP Abstract 最近的研究表明&#xff0c;卷积神经网络(CNN)架构在频谱上偏向较低频率&…

【IDEA】IntelliJ IDEA中进行Git版本控制

本篇文章主要记录一下自己在IntelliJ IDEA上使用git的操作&#xff0c;一个新项目如何使用git进行版本控制。文章使用的IDEA版本 IntelliJ IDEA Community Edition 2023.3&#xff0c;远程仓库为https://gitee.com/ 1.配置Git&#xff08;File>Settings&#xff09; 2.去Git…

idea 本身快捷键ctrl+d复制 无法像eclipse快捷键ctrl+alt+上下键,自动换行格式问题解决

问题 例如我使用ctrld 想复制如下内容 复制效果如下&#xff0c;没有自动换行&#xff0c;还需要自己在进行调整 解决 让如下快捷键第一个删除 修改成如下&#xff0c;将第二个添加ctrld 提示&#xff1a;对应想要修改的item&#xff0c;直接右键&#xff0c;remove是删…

JVM进程缓存

引言 缓存在日常开发中启动至关重要的作用&#xff0c;由于是存储在内存中&#xff0c;数据的读取速度是非常快的&#xff0c;能大量减少对数据库的访问&#xff0c;减少数据库的压力。我们把缓存分为两类&#xff1a; 分布式缓存&#xff0c;例如Redis&#xff1a; 优点&…

Docker部署开源分布式任务调度平台DolphinScheduler并实现远程访问办公

文章目录 前言1. 安装部署DolphinScheduler1.1 启动服务 2. 登录DolphinScheduler界面3. 安装内网穿透工具4. 配置Dolphin Scheduler公网地址5. 固定DolphinScheduler公网地址 前言 本篇教程和大家分享一下DolphinScheduler的安装部署及如何实现公网远程访问&#xff0c;结合内…

解决electron修改主进程后需要重启才生效

nodemon 是一种工具&#xff0c;可在检测到目录中的文件更改时通过自动重新启动节点应用程序来帮助开发基于 node.js 的应用程序 nodemon 特性 自动重新启动应用程序。检测要监视的默认文件扩展名。默认支持 node&#xff0c;但易于运行任何可执行文件&#xff0c;如 python、…

Unity 实现单例模式

目录 基本概念 饿汉模式(推荐) 懒汉模式&#xff1a; 基本概念 单例模式&#xff1a;类只有一个实例&#xff0c;一般使用static来实现单例模式&#xff1b; 比如&#xff1a;有一个Test类,实现了单例&#xff0c;假设这个唯一的实例名为SingTonle,实例在类内被实现并被stat…

零基础小白怎么准备蓝桥杯-蓝桥杯竞赛经验分享

零基础小白怎么准备蓝桥杯-蓝桥杯竞赛经验分享 前言竞赛简介竞赛目的如何备战1.基础学习2.实战训练&#xff08;非常重要&#xff09; 资料分享 前言 博主在蓝桥杯中获得过十四届Java B 组的省一国二&#xff0c;本文为大家介绍一下蓝桥杯并分享一下自己的参赛经验。 竞赛简介…

Qt实现二维码生成和识别

一、简介 QZxing开源库: 生成和识别条码和二维码 下载地址&#xff1a;https://gitcode.com/mirrors/ftylitak/qzxing/tree/master 二、编译与使用 1.下载并解压&#xff0c;解压之后如图所示 2.编译 打开src目录下的QZXing.pro&#xff0c;选择合适的编译器进行编译 最后生…

git学习笔记03(小滴课堂)

详解分支的基本操作 创建分支&#xff1a; 查看分支&#xff1a; 切换分支&#xff1a; git branch 中星号是当前分支。 idea中也更新了。 提交上去。 我们新建个分支&#xff1a; 我们新建分支是复制当前分支&#xff0c;而不是直接复制的主分支。 我们切换回主分支&#xf…