【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, message
        sleep(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++的多态一样,数组中的对象为基类,具体的对象(共享内存,消息队列,信号量)为子类

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Spring JDBC和事务管理

Spring JDBC是Spring框架用来处理关系型数据库的模块&#xff0c;对JDBC的API进行了封装。 Spring JDBC的核心类为JdbcTemplate&#xff0c;提供数据CRUD方法 Spring JDBC使用步骤 Maven工程引入依赖spring-jdbc <dependency><groupId>org.springframework<…

案例026:基于微信小程序的原创音乐系统的设计与实现

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;SSM JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder X 小程序…

建立个人学习观|地铁上的自习室

作者&#xff1a;向知 如果大家有机会来北京&#xff0c;可以来看看工作日早上八九点钟&#xff0c;15 号线从那座叫“顺义”的城市通向“望京”的地铁&#xff0c;你在那上面&#xff0c;能看到明明白白的&#xff0c;人们奔向梦想的模样。 一、地铁上的自习室 我在来北京之前…

基于JavaWeb+SSM+Vue助农扶贫微信小程序系统的设计和实现

基于JavaWebSSMVue助农扶贫微信小程序系统的设计和实现 源码获取入口Lun文目录前言主要技术系统设计功能截图 源码获取入口 Lun文目录 目 录 第一章 绪论 1 1.1 研究背景 1 1.2 研究意义 1 1.3 研究内容 2 第二章 开发环境与技术 3 2.1 JSP技术 3 2.2 MySQL数据库 3 2.3 Java…

DIP——边缘提取与分割

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

TailwindCSS 如何处理RTL布局模式

背景 TikTok作为目前全世界最受欢迎的APP&#xff0c;需要考虑兼容全世界各个地区的本地化语言和阅读习惯。其中对于阿拉伯语、波斯语等语言的阅读书写习惯是从右向左的&#xff0c;在前端有一个专有名字RTL模式&#xff0c;即Right-to-Left。 其中以阿拉伯语作为第一语言的人…

《算法与数据结构》答疑

答疑 问题一问题二问题三问题四 问题一 在匹配成功时&#xff0c;在返回子串位置那里&#xff0c;为什么不是i-t的长度啊&#xff0c;为什么还要加一 问题二 问题三 问题四 问&#xff1a;如果题目让我们构造一个哈夫曼树&#xff0c;像我发的这个例题的话&#xff0c;我画成我…

Kafka Streams:深度探索实时流处理应用程序

Apache Kafka Streams 是一款强大的实时流处理库&#xff0c;为构建实时数据处理应用提供了灵活且高性能的解决方案。本文将深入探讨 Kafka Streams 的核心概念、详细原理&#xff0c;并提供更加丰富的示例代码&#xff0c;以帮助读者深入理解和应用这一流处理框架。 1. Kafka…

uniapp自定义的日历(纯手写)

效果图&#xff1a; html&#xff1a; <!-- 年月 --><view class"box"><view class"box_time"><view class"time"><image click"lefts" :src"url/uploads/20231206/9d1fb520b12383960dca3c214d84fa0…

三. LiDAR和Camera融合的BEV感知算法-融合算法的基本介绍

目录 前言0. 简述1. 融合背景2. 融合思路3. 融合性能优劣总结下载链接参考 前言 自动驾驶之心推出的《国内首个BVE感知全栈系列学习教程》&#xff0c;链接。记录下个人学习笔记&#xff0c;仅供自己参考 本次课程我们来学习下课程第三章——LiDAR和Camera融合的BEV感知算法&am…

数据分析基础之《numpy(2)—ndarray属性》

一、ndarray的属性 1、属性方法 属性名字属性解释ndarray.shape数组维度的元组&#xff08;形状&#xff09;ndarray.ndim数组维数ndarray.size数组中的元素数量ndarray.itemsize一个数组元素的长度&#xff08;字节&#xff09;ndarray.dtype数组元素的类型使用方法 数组名.…

List 接口

1 List 接口 java.util 中的集合类包含 Java 中某些最常用的类。最常用的集合类是 List 和 Map。 List是一种常用的集合类型&#xff0c;它可以存储任意类型的对象&#xff0c;也可以结合泛型来存储具体的类型对象&#xff0c;本质上就是一个容器。 1.1 List 类型介绍 有序性…

JVM进程缓存

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

Jmeter 请求签名api接口-BeanShell

Jmeter 请求签名api接口-BeanShell 项目签名说明编译扩展jar包jmeter 使用 BeanShell 调用jar包中的签名方法 项目签名说明 有签名算法的api接口本地不好测试&#xff0c;使用BeanShell 扩展jar 包对参数进行签名&#xff0c;接口签名算法使用 sha512Hex 算法。签名的说明如下…

web,Apache简述

一.HTTP请求访问的完整过程 1.建立连接 2.接收请求 3.处理请求 4.访问资源 服务器获取请求报文中请求的资源web服务器&#xff0c;即存放了web资源的服务器&#xff0c;负责向请求者提供对方请求的静态资源&#xff0c;或动态运行后生成的资源 静态资源&#xff1a;不需要…

IDEA 中文乱码解决方案

我的电脑Window11默认编码是UTF-8 1. console控制台乱码 修改IDEA目录下bin/idea64.exe.vmoptions文件&#xff0c;在文件结尾添加-Dfile.encodingUTF-8&#xff1b;保险起见&#xff0c;再修改下idea配置Help ->Edit Custom VM Options&#xff0c;同样是在文件结尾添加-D…

Spring Cloud Gateway + Nacos + LoadBalancer实现企业级网关

1. Spring Cloud Gateway 整合Nacos、LoadBalancer 实现企业级网关 前置工作&#xff1a; 创建 SpringBoot 多模块项目创建网关&#xff08;gateway-service&#xff09;、用户&#xff08;user-service&#xff09;模块用户模块添加 Nacos discovery 支持以及 Spring Web&am…

堆的基础功能实现和优先级队列

1. 堆的插入与删除 1.1 堆的插入 步骤&#xff1a; 1、先将元素放入到底层空间中(注意&#xff1a;一般是放到整个二叉树的最后一个叶子节点的后边&#xff0c;其次存储空间不够时需要扩容) 2、将最后新插入的节点向上调整&#xff0c;直到满足堆的性质&#xff08;判断该节点…

30 张图解 HTTP 常见的面试题

前言 在面试过程中&#xff0c;HTTP 被提问的概率还是比较高的 我搜集了 5 大类 HTTP 面试常问的题目&#xff0c;同时这 5 大类题跟 HTTP 的发展和演变关联性是比较大的&#xff0c;通过问答 图解的形式由浅入深的方式帮助大家进一步的学习和理解 HTTP 协议。 HTTP 基本概…

【数学建模】《实战数学建模:例题与讲解》第十讲-时间序列预测(含Matlab代码)

【数学建模】《实战数学建模&#xff1a;例题与讲解》第十讲-时间序列预测&#xff08;含Matlab代码&#xff09; 基本概念移动平均&#xff08;Moving Average, MA&#xff09;:指数平滑法&#xff08;Exponential Smoothing&#xff09;:季节性调整&#xff08;Seasonal Adju…