【Linux】进程间通信——system V共享内存 | 消息队列 | 信号量

news2024/9/28 9:31:03

文章目录

  • 一、system V共享内存
    • 1. 共享内存的原理
    • 2. 共享内存相关函数
    • 3. 共享内存实现通信
    • 4. 共享内存的特点
  • 二、system V消息队列(了解)
  • 三、system V信号量(信号量)


一、system V共享内存

1. 共享内存的原理

共享内存是一种在多个进程之间进行进程间通信的机制。它允许多个进程访问相同的物理内存区域,从而实现高效的数据交换和通信。

因为进程具有独立性(隔离性),内核数据结构包括对应的代码、数据与页表都是独立的。OS系统为了让进程间进行通信,必须让不同的进程看到同一份资源。所以共享内存的原理如下:

1.申请一块空间
2.将创建好的内存映射进进程的地址空间。

共享内存让不同的进程看到同一份的资源就是在物理内存上申请一块内存空间,将创建好的内存分别与各个进程的页表之间建立映射,然后在虚拟地址空间中将虚拟地址填充到各自页表的对应位置,建立起物理地址与虚拟地址的联系。

在这里插入图片描述
在这里插入图片描述

我们把创建好的内存称为共享内存,把进程和共享内存建立映射关系的操作称为挂接,把取消进程和内存的映射关系称为去关联,把释放内存称为释放共享内存

共享内存的建立: 在物理内存当中申请共享内存空间;将申请到的共享内存挂接到地址空间,即建立映射关系。

共享内存的释放: 共享内存与地址空间去关联,即取消映射关系;释放共享内存空间,即将物理内存归还给系统。

对共享内存的理解:

  • 共享内存不属于通信的任意一个进程,其属于操作系统,由操作系统所管理。

  • 管道的本质是文件,操作系统已经有相应的内核数据结构来管理文件,因此不需要再去设计新的内核数据结构去管理管道。而共享内存是专门为了进程间通信而设计的,操作系统可能会有很多共享内存,那么操作系统就需要将这些共享内存管理起来。

  • 管理的方式是先描述再组织,那么共享内存就等于共享内存块加上共享内存对应的内核数据结构。

  • 对共享内存的修改包括对属性的修改和对内容的修改。


2. 共享内存相关函数

  • shmget: 用来创建或者获取共享内存。失败时返回-1。

在这里插入图片描述

参数:

shmflg: 通常被设置成两个选项: IPC_CREAT、 IPC_EXCL

  • IPC_CREAT:共享内存不存在,则创建,如果存在则获取;
  • IPC_EXCL:无法单独使用,IPC_CREAT | IPC_EXCL:如果不存在就创建,如果存在就出错返回

size: 共享内存的大小

key: 共享内存字段的名字,通信的进程需要通过该key值找到同一个共享内存,从而进行通信。因此key能保证多个进程看到同一份共享内存,能进行唯一性标识。

  • ftok: 生成key。失败时返回-1。

在这里插入图片描述

ftok函数将pathname和project id 经过一定的算法转换成 key,pathname必须存在,projectid不能为0。

OS一定会存在很多的共享内存,共享内存本质就是在内存中申请一块空间,而key能进行唯一标识。OS申请的,自然要做管理,共享内存也是如此,如何管理:先描述,在组织。所以共享内存=物理内存块+共享内存的相关属性。进程如果在内存中创建了共享内存,为了让共享内存在系统中保证唯一的,通过key来进行标识,只要让另一个进程也看到同一个key。

  • shmat:将共享内存段连接到进程地址空间(建立页表映射关系)。成功返回一个指针,指向共享内存第一个字节;失败返回 (void*) -1。

在这里插入图片描述

参数:

shmid: 共享内存的标识符。

shmaddr: 指定连接地址,如果设置为nullptr,则让操作系统指定连接到合适的地址上。

shmflg: 它的两个可能取值是 SHM_RND 和 SHM_RDONLY。shmflg 等于 SHM_RDONLY 时,表示连接操作用来只读共享内存。

  • shmdt: 将共享内存段与当前进程脱离。成功返回0,失败返回-1。

在这里插入图片描述

参数:

shmaddr: 由shmat函数所返回的指针。

这里我们需要注意:将共享内存和当前进程脱离不等于删除共享内存。

  • shmctl: 用语控制共享内存。

在这里插入图片描述

参数:

shmid: 由shmget函数返回的共享内存标识符。

cmd: 将要采取的动作。(有三个可以选择)

buf: 为指向一个保存着共享内存的模式状态和访问权限的数据结构。不关心共享内存的内核数据结构时,buf可以设置为nullptr。

这里我们需要注意的是:当进程运行结束时,进程创建的共享内存还会存在,这是因为system V IPC资源的生命周期是随其内核的。其内核可以通过代码删除(shmctl函数),也可以通过ipcrm -m shmid 指令手动删除。使用ipcs -m 指令可以查看系统中已经创建好的共享内存。

在这里插入图片描述


3. 共享内存实现通信

makefile

.PHONY:all
all: shmclient shmserver

shmclient:client.cc
	g++ -o $@ $^ -std=c++11
shmserver:server.cc
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -f shmclient shmserver

comm.hpp

#ifndef __COMM_HPP__
#define __COMM_HPP_

#include <iostream>
#include <cstring>
#include <cstdio>
#include <cassert>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/stat.h>
using namespace std;

#define PATHNAME "."
#define PROJID 0x6666

const int gsize = 4096;

//获取key
key_t getKey()
{
    key_t k = ftok(PATHNAME, PROJID);
    if(k == -1)
    {
        cerr << "error: " << errno << " : " << strerror(errno) << endl;
        exit(1);
    }
    return k;
}

//转十六进制函数
string toHex(int x)
{
    char buffer[64];
    snprintf(buffer, sizeof buffer, "0X%x", x);
    return buffer;
}

//共享内存公共函数
static int createShmHelper(key_t k, size_t size, int flag)
{
    int shmid = shmget(k, size, flag);
    if(shmid == -1)
    {
        cerr << "error: " << errno << " : " << strerror(errno) << endl;
        exit(2);
    }
    return shmid;
}

//创建共享内存
int createShm(key_t k, size_t size)
{
    umask(0);
    return createShmHelper(k, size, IPC_CREAT | IPC_EXCL | 0666);
}

//获取共享内存
int getShm(key_t k, size_t size)
{
    return createShmHelper(k, size, IPC_CREAT);
}

//关联进程
char* attachShm(int shmid)
{
    char* start = (char*)shmat(shmid, nullptr, 0);
    return start;
}

//去关联进程
void detachShm(char* start)
{
    int n = shmdt(start);
    assert(n != -1);
    (void)n;
}

//释放共享内存
void delShm(int shmid)
{
    int n = shmctl(shmid, IPC_RMID, nullptr);
    assert(n != -1);
    (void)n;
}

#define SERVER 1
#define CLIENT 0

class Init
{
public:
    Init(int t)
        :_type(t)
    {
        key_t key = getKey();
        if(_type == SERVER)
            _shmid = createShm(key, gsize);
        else
            _shmid = getShm(key, gsize);
        _start = attachShm(_shmid);           
    }

    char* getChar(){return _start;}

    ~Init()
    {
        detachShm(_start);
        if(_type == SERVER) delShm(_shmid);
    }
private:
    char* _start;
    int _type; // server or client
    int _shmid;
};

#endif

服务端:server.cc

#include "comm.hpp"

int main()
{

    Init init(SERVER);
    char* start = init.getChar();
    
    int n = 0;
    while(n <= 35)
    {
        cout <<"client -> server# "<< start << endl;
        sleep(1);
        n++;
    }
    
    // // 1. 创建key
    // key_t k = getKey();
    // cout << "server:" << toHex(k) << endl;
    
    // // 2. 创建共享内存
    // int shmid = createShm(k, gsize);
    // cout << "shmid:" << shmid << endl;
    // sleep(8);

    // // 3. 将自己和共享内存关联起来
    // char* start = attachShm(shmid);
    // sleep(20);

    // // 4. 将自己和共享内存去关联
    // detachShm(start);
    // sleep(3);
	   // 5. 删除共享内存
    // delShm(shmid);

    return 0;
}

客户端:client

#include "comm.hpp"

int main()
{

    Init init(CLIENT);
    char *start = init.getChar();
    char c = 'A';

    while(c <= 'Z')
    {
        start[c - 'A'] = c;
        c++;
        start[c - 'A'] = '\0';
        sleep(1);
    }

    // // 1. 获取key
    // key_t k = getKey();
    // cout << "client:" << toHex(k) << endl;
    
    // // 2. 获取共享内存
    // int shmid = getShm(k, gsize);
    // cout << "shmid:" << shmid << endl;

    // // 3. 将自己和共享内存关联起来
    // char* start = attachShm(shmid);
    // sleep(15);
    
    // // 4. 将自己和共享内存去关联
    // detachShm(start);

    return 0;
}

在这里插入图片描述


4. 共享内存的特点

优点:

  • 只要通信双方使用共享内存,一方直接向共享内存中写入数据,另一方就可以马上看到对方写入的数据。共享内存是所有进程间通信(IPC)中速度最快的!因为其不需要过多的拷贝(不需要将数据给操作系统)

下面我们来比对一下共享内存和管道:

管道通信的拷贝:

C++输入设备把数据拷贝到cin或者stdin文件缓存区不考虑,这里总共进行了4次拷贝。

在这里插入图片描述

共享内存通信的拷贝:

直接从输入到共享内存,从共享内存到输出。

在这里插入图片描述

缺点:

以共享内存的方式进行进程间通信缺乏访问控制,会带来同步问题!比如:写端还没将全部数据写入,读端就已经开始读取了,这将会带来巨大的问题!


二、system V消息队列(了解)

消息队列 是OS提供的内核级队列,消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法,每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值。

  • 消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法。
  • 每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值。
  • IPC资源必须删除,否则不会自动清除,除非重启,所以system V IPC资源的生命周期随内核

常用系统调用:ftok,msgget(创建消息队列),msgctl(控制消息队列),msgsnd(向消息队列发送数据),msgrcv(从消息队列中读取数据)等。

在这里插入图片描述


三、system V信号量(信号量)

下面,我们先来引出几个概念:

"公共资源:" 被多个进程同时访问的资源,访问没有保护的公共资源:数据不一致问题。要让不同的进程看到同一份资源是为了通信,通信是为了让进程间实现协同,而进程之间具有独立性,所以为了解决独立性问题要让进程看到同一份资源,但是会导致数据不一致的问题。

"互斥": 任何一个时刻,都只允许一个执行流在进行共享资源的访问。各进程间竞争使用这些资源,竞争的这种关系为进程的互斥。

"临界资源": 任何一个时刻,都只允许一个执行流在进行访问的共享资源,叫做临界资源。

"临界区": 临界资源是需要通过代码访问的,凡是访问临界资源的代码,叫做临界区。

"原子性": 要么不做、要么做完,只有两种确定状态的属性,叫做原子性。

任何一个执行流,想访问临界资源中的一个子资源时,不能直接访问。得先申请信号量,信号量/信号灯 本质是一个计数器,描述资源数量的计数器。 信号量是对临界资源的预定机制。

  • 申请信号量
    只要申请信号量成功,临界资源内部一定给你预留了你想要的资源,申请信号量本质是对临界资源的一种预定机制,让信号计数器减减,信号量计数器为0时,进程申请信号量无法成功。只能阻塞等待其他进程退出,才能申请信号量,访问临界资源。
  • 访问临界资源——进程执行自己的临界区代码
  • 释放信号量——信号量计数器加加

在这里插入图片描述
在这里插入图片描述

我们可以发现,共享内存、消息队列、信号量接口相似度非常高,获取与删除,都是system V标准的进程间通信。

OS如何管理:先描述,在组织,对相关资源的内核数据结构做管理,对于共享内存、消息队列、信号量的第一个成员都是ipc_perm:

struct ipc_perm {
           key_t          __key;    /* Key supplied to shmget(2) */
           uid_t          uid;      /* Effective UID of owner */
           gid_t          gid;      /* Effective GID of owner */
           uid_t          cuid;     /* Effective UID of creator */
           gid_t          cgid;     /* Effective GID of creator */
           unsigned short mode;     /* Permissions + SHM_DEST and
                                       SHM_LOCKED flags */
           unsigned short __seq;    /* Sequence number */
};

虽然内部的属性差别很大,但是维护它们的数据结构的第一个成员都是ipc_perm类型的成员变量,都可以通过key来标识唯一性。这样设计的好处:在操作系统内可以定义一个struct ipc_perm类型的数组,此时每当我们申请一个IPC资源,就在该数组当中开辟一个这样的结构。((struct shmid_ds*)perms[0],强转,此时就可以访问其他剩下的属性)

在这里插入图片描述


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

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

相关文章

自动化测试Junit(测试系列8)

目录 前言&#xff1a; 1.什么是Junit 2.Junit相关的技术 2.1注解 2.1.1Test 2.1.2Disable 2.1.3BeforeAll和AfterAll 2.1.4BeforeEach和AfterEach 2.2参数化 2.2.1单参数 2.2.2多参数 2.2.2.1CSV获取参数 2.2.2.2方法获取参数 2.3测试套件 2.3.1通过class运行测…

Java工程师研学之路【002Java基础语法上】

知识体系&#xff08;Knowledge system&#xff09; 练习&#xff08;practice&#xff09; 要求&#xff1a;从控制台输入两个数字&#xff0c;然后输出两个数字的求和结果。 import java.util.Scanner; public class HelloJava {public static void sum(){System.out.print…

kafka集群搭建(Linux环境)

zookeeper搭建&#xff0c;可以搭建集群&#xff0c;也可以单机&#xff08;本地学习&#xff0c;没必要搭建zookeeper集群&#xff0c;单机完全够用了&#xff0c;主要学习的是kafka&#xff09; 1. 首先官网下载zookeeper&#xff1a;Apache ZooKeeper 2. 下载好之后上传到…

以数据要素为支点,兴业银行撬动企业“技术杠杆”

文 | 螳螂观察 作者 | 李永华 推荐理财产品&#xff0c;恰好符合客户能承受的风险水平和想要的收益率水平&#xff0c;在资金投入上也契合客户当下的财务安排&#xff0c;于是顺利成交&#xff1b; 为客户办理的信用卡&#xff0c;优惠的场景方向与客户常常消费的领域大体一…

RWEQ模型教程

详情点击链接&#xff1a;基于“RWEQ”集成技术在土壤风蚀模拟与风蚀模数估算、变化归因分析中的实践应用及SCI论文撰写 前沿 土壤风蚀是一个全球性的环境问题。中国是世界上受土壤风蚀危害最严重的国家之一&#xff0c;土壤风蚀是中国干旱、半干旱及部分湿润地区土地荒漠化的…

U盘安装CentOS7.9出错:进入 dracut问题和解决方法

U盘安装CentOS7.9出错&#xff1a;进入 dracut问题和解决方法 原因&#xff1a;U盘名称未识别&#xff0c; 解决&#xff1a;进入启动界面&#xff0c;按e进入编辑界面 修改&#xff1a; vmlinuz initrdinitrd.img inst.stage2hd:LABELCentOS\x207\x20x86_64.check quiet 为 …

前端框架学习-Vue(二)

最近在学习Vue框架&#xff0c;Vue中的内容很多。相当于把之前后端的MVC&#xff0c;V层转移到前端来编写和部署。下面是学习Vue时的大纲。 Vue生命周期是Vue应用的生命周期Vue脚手架&#xff0c;即vue-cli&#xff0c;使用node.js 来创建和启动vue项目Vue组件知识&#xff0c;…

java重试机制实现方案

本文内容是目前团队内小磊同学对重试机制实现方案的梳理总结。 从为什么需要重试的背景开始&#xff0c;到重试的场景&#xff0c;大致的一些设计思路&#xff0c;最后通过两个成熟的retry组件进行案例讲解&#xff0c;理论实战。 背景 重试是系统提高容错能力的一种手段。在一…

Windows下Nginx安装与配置教程

一、前言 1、Nginx是什么&#xff1f; Nginx是一个开源的Web服务器&#xff0c;同时Nginx也提供了反向代理和负载均衡的功能。 Nginx通常作为负载均衡器暴露在外网接受用户请求&#xff0c;同时也使用其反向代理的功能&#xff0c;将用户的请求转发到实际提供服务的内网服务器…

带wiringPi库的交叉编译 ---宿主机x86Ubuntu,目标机ARMv8 aarch64(香橙派)

带wiringPi库的交叉编译如何进行 先交叉编译wiringPi库&#xff0c;编译出的库适合香橙派&#xff0c;这时候交叉编译可执行程序的平台和链接库的格式也是正确的&#xff0c;然后通过-I和-L来指定链接的wiringPi库的头文件和库的位置&#xff0c;但是现在还没有学习过&#xf…

基于正交滤波器组的语音DPCM编解码算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 ...........................................................g0zeros(1,lenH); g1zeros(1,l…

加解密相关工具网站总结

加解密相关工具&网站总结 文章目录 加解密相关工具&网站总结CMD5&#xff0c;解密&#xff0c;反向查询JSFuck&#xff08;JavaScriptAAEncode加密/解密&#xff08;Javascript在线CTF编码工具开源加解密工具大佬文章&#xff1a;1.30余种加密编码类型的密文特征分析2.…

VMware搭建Hadoop集群 for Windows(完整详细,实测可用)

目录 一、VMware 虚拟机安装 &#xff08;1&#xff09;虚拟机创建及配置 &#xff08;2&#xff09;创建工作文件夹 二、克隆虚拟机 三、配置虚拟机的网络 &#xff08;1&#xff09;虚拟网络配置 &#xff08;2&#xff09;配置虚拟机 主机名 &#xff08;3&#xf…

数值分析第六章节 用Python实现解线性方程组的迭代法

参考书籍&#xff1a;数值分析 第五版 李庆杨 王能超 易大义编 第5章 解线性方程组的迭代法 文章声明&#xff1a;如有发现错误&#xff0c;欢迎批评指正 文章目录 迭代法的基本概念雅可比迭代法与高斯-塞格尔迭代法雅可比迭代法高斯-塞格尔迭代法 迭代法的基本概念 6.1.1引言…

Python 进阶(三):正则表达式(re 模块)

❤️ 博客主页&#xff1a;水滴技术 &#x1f338; 订阅专栏&#xff1a;Python 入门核心技术 &#x1f680; 支持水滴&#xff1a;点赞&#x1f44d; 收藏⭐ 留言&#x1f4ac; 文章目录 1. 导入re模块2. re模块中的常用函数2.1 re.search()2.2 re.findall()2.3 re.sub()2.4…

020 - STM32学习笔记 - Fatfs文件系统(二) - 移植与测试

020 - STM32学习笔记 - Fatfs文件系统&#xff08;二&#xff09; - 移植与测试 上节学习了FatFs文件系统的相关知识&#xff0c;这节内容继续学习在STM32上如何移植FatFs文件系统&#xff0c;并且实现文件的创建、读、写与删除等功能。 一、FatFs文件系统移植 移植还是在之…

uniapp 全局数据(globalData)的设置,获取,更改

globalData&#xff0c;这是一种简单的全局变量机制。这套机制在uni-app里也可以使用&#xff0c;并且全端通用 因为uniapp基本上都是将页面&#xff0c;或者页面中相同的部分&#xff0c;进行组件化&#xff0c;所以会存在父&#xff0c;子&#xff0c;&#xff08;子&#xf…

AI革命:揭开微软无与伦比的AI技术面纱

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 2023年7月25日&#xff0c;全球科技行业的领导者之一微软(MSFT)公布了其2023财年第四季度的财报。 除了举世闻名的Windows操作系统&#xff0c;微软还通过笔记本电脑、个人电脑和服务器等产品改变了世界&#xff0c;该公司…

ARM裸机-2

1、搞清楚各种版本号 1.1、ARM的型号命名问题 ARM7和ARMv7不是一回事。 Cortex-A9比Cortex-A7更先出来。 型号很乱&#xff0c;初学者容易分不清哪个是哪个&#xff0c;比较迷茫。 1.2、ARM的几种版本号 ARM内核版本号&#xff08;ARM卖给别人的核心版本号&#xff09; …

IntelliJ IDEA 2023.2 主要更新了什么?(图文版)

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…