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

news2024/11/25 20:16:05

文章目录

  • 一、共享内存的概念及原理
  • 二、共享内存相关接口说明
    • 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++的多态一样,数组中的对象为基类,具体的对象(共享内存,消息队列,信号量)为子类

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

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

相关文章

ADAudit Plus:强大的网络安全卫士

随着数字化时代的不断发展&#xff0c;企业面临着越来越复杂和多样化的网络安全威胁。在这个信息爆炸的时代&#xff0c;保护组织的敏感信息和确保网络安全已经成为企业发展不可或缺的一环。为了更好地管理和监控网络安全&#xff0c;ADAudit Plus应运而生&#xff0c;成为网络…

【队列】数据也得排队

目录 引言 队列的概念 队列的实现 单向链表队列 结构 初始化 入队 出队 取队头 取队尾 求个数 判空 内存释放 总结 引言 队列&#xff0c;这个看似普通的数据结构&#xff0c;其实隐藏着无尽的趣味和巧思。就像单向链表这把神奇的魔法钥匙&#xff0c;它能打开队…

解决Git提交错误分支

如果 Git 提交到错误的分支&#xff0c;可以通过以下步骤将其转移到正确的分支上&#xff1a; 1.检查当前所在的分支&#xff0c;可以通过 git branch 命令查看。 git branch2.切换到正确的分支&#xff0c;可以通过 git checkout <正确的分支名> 命令进行切换。 git …

vue3-vite前端快速入门教程 vue-element-admin

Vue3快速入门学习 初始化项目 # 创建项目 npm create vitelatest my-vue-app -- --template vue # 安装依赖 npm i # 运行 npm run dev 模板语法 文本插值​ 最基本的数据绑定形式是文本插值&#xff0c;它使用的是“Mustache”语法 (即双大括号)&#xff1a; <span&g…

三相不平衡电压的正负序分析

1、什么是正负序&#xff1f; ABC 正序 ACB 负序 2、在abc坐标系下 接着利用矢量的旋转消去其它分量。。。 同理&#xff0c;得到其它的所有正负序的分量abc 3、在α/β坐标系下&#xff0c; 依次算出正负序的α/β来表示的abc 有一点需要特别注意&#xff0c;可以看到…

贪心其实很简单

关卡名 认识贪心思想 我会了✔️ 内容 1.复习一维数组&#xff0c;对数组进行多轮插入或者删除时会频繁移动数据&#xff0c;理解双指针是如何避免该问题的 ✔️ 2.理解滑动窗口的原理和适用场景 ✔️ 3.掌握窗口变与不变的两种情况是如何用来解题的 ✔️ 1.难以解释的贪心…

基于SpringBoot+Vue前后端分离的商城管理系统(Java毕业设计)

大家好&#xff0c;我是DeBug&#xff0c;很高兴你能来阅读&#xff01;作为一名热爱编程的程序员&#xff0c;我希望通过这些教学笔记与大家分享我的编程经验和知识。在这里&#xff0c;我将会结合实际项目经验&#xff0c;分享编程技巧、最佳实践以及解决问题的方法。无论你是…

【Linux】进程周边001之进程概念

&#x1f440;樊梓慕&#xff1a;个人主页 &#x1f3a5;个人专栏&#xff1a;《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》《C》《Linux》 &#x1f31d;每一个不曾起舞的日子&#xff0c;都是对生命的辜负 目录 前言 1.基本概念 2.描述进程-PCB…

贪心算法及相关题目

贪心算法概念 贪心算法是指&#xff0c;在对问题求解时&#xff0c;总是做出在当前看来是最好的选择。也就是说&#xff0c;不从整体最优上加以考虑&#xff0c;算法得到的是在某种意义上的局部最优解 。 贪心算法性质&#xff08;判断是否可以使用贪心算法&#xff09; 1、贪…

【SpringBoot教程】SpringBoot 创建定时任务(配合数据库动态执行)

作者简介&#xff1a;大家好&#xff0c;我是撸代码的羊驼&#xff0c;前阿里巴巴架构师&#xff0c;现某互联网公司CTO 联系v&#xff1a;sulny_ann&#xff08;17362204968&#xff09;&#xff0c;加我进群&#xff0c;大家一起学习&#xff0c;一起进步&#xff0c;一起对抗…

DDD系列 - 第6讲 仓库Repository及Mybatis、JPA的取舍(一)

目录 一、领域层定义仓库接口1.1 设计聚合1.2 定义仓库Repository接口二 、基础设施层实现仓库接口2.1 设计数据库2.2 集成Mybatis2.3 引入Convetor2.4 实现仓库三、回顾一、领域层定义仓库接口 书接上回,之前通过一个关于拆解、微服务、面向对象的故事,向大家介绍了如何从微…

mysql中的DQL查询

表格为&#xff1a; DQL 基础查询 语法&#xff1a;select 查询列表 from 表名&#xff1a;&#xff08;查询的结果是一个虚拟表格&#xff09; -- 查询指定的列 SELECT NAME,birthday,phone FROM student -- 查询所有的列 * 所有的列&#xff0c; 查询结果是虚拟的表格&am…

【GlobalMapper精品教程】067:基于无人机航拍照片快速创建正射影像图

文章目录 一、加载无人机照片二、创建正射影像三、导出正射影像四、worldImagery比对一、加载无人机照片 打开globalmapper软件,点击打开数据文件。 选择配套实验数据data067.rar中的影像,Ctrl+A全选。 在globalmapper中,可以直接将照片加载为如下样式。 二、创建正射影像 …

深入理解网络中断:原理与应用

&#x1f52d; 嗨&#xff0c;您好 &#x1f44b; 我是 vnjohn&#xff0c;在互联网企业担任 Java 开发&#xff0c;CSDN 优质创作者 &#x1f4d6; 推荐专栏&#xff1a;Spring、MySQL、Nacos、Java&#xff0c;后续其他专栏会持续优化更新迭代 &#x1f332;文章所在专栏&…

[GPT]Andrej Karpathy微软Build大会GPT演讲(下)--该如何使用GPT助手

该如何使用GPT助手--将GPT助手模型应用于问题 现在我要换个方向,让我们看看如何最好地将 GPT 助手模型应用于您的问题。 现在我想在一个具体示例的场景里展示。让我们在这里使用一个具体示例。 假设你正在写一篇文章或一篇博客文章,你打算在最后写这句话。 加州的人口是阿拉…

【参天引擎】华为参天引擎内核架构专栏开始更新了,多主分布式数据库的特点,类oracle RAC国产数据开始出现了

cantian引擎的介绍 ​专栏内容&#xff1a; 参天引擎内核架构 本专栏一起来聊聊参天引擎内核架构&#xff0c;以及如何实现多机的数据库节点的多读多写&#xff0c;与传统主备&#xff0c;MPP的区别&#xff0c;技术难点的分析&#xff0c;数据元数据同步&#xff0c;多主节点的…

记录 | ubuntu上安装fzf

在 ubuntu 上采用命令行安装 fzf 的方式行不通 指的是采用下面的方式行不通&#xff1a; sudo apt install fzf # 行不通 sudo snap install fzf --classic # 行不通正确的安装方式是&#xff1a; ● 到 fzf 的 git 仓库&#xff1a;https://github.com/junegunn/fzf/re…

Landsat7_C2_SR数据集(大气校正地表发射率数据集)

Landsat7_C2_SR数据集是经大气校正后的地表反射率数据&#xff0c;属于Collection2的二级数据产品&#xff0c;空间分辨率为30米&#xff0c;基于Landsat生态系统扰动自适应处理系统&#xff08;LEDAPS&#xff09;&#xff08;版本3.4.0&#xff09;生成。水汽、臭氧、大气高度…

【SpringBoot教程】SpringBoot 实现前后端分离的跨域访问(Nginx)

作者简介&#xff1a;大家好&#xff0c;我是撸代码的羊驼&#xff0c;前阿里巴巴架构师&#xff0c;现某互联网公司CTO 联系v&#xff1a;sulny_ann&#xff08;17362204968&#xff09;&#xff0c;加我进群&#xff0c;大家一起学习&#xff0c;一起进步&#xff0c;一起对抗…

【Linux】进程见通信之匿名管道pipe

1.匿名管道的特点 以下管道的统称仅代表匿名管道。 管道是一个只能单向通信的通信信道。为了实现进程间通信.管道是面向字节流的。仅限于父子通信或者具有血缘关系的进程进行进程见通信。管道自带同步机制&#xff0c;原子性写入。管道的生命周期是随进程的。 2.匿名管道通信…