【Linux】进程间通信 -- 共享内存

共享内存

共享内存是SystemV标准进程间通信的一种,该标准还有消息队列和信号量,但下文主要介绍共享内存,然后在谈一下信号量的内容。SystemV标准的进程间通信可以看做单机版的进程间通信。

// 1. log.hpp
#pragma once#include <iostream>enum ErrLevel
{lev_0,lev_1,lev_2,lev_3,lev_4
};const std::string error[] = {"err_0","err_1","err_2","err_3","err_4"
};std::ostream& Log(const std::string& msg, int level)
{std::cout << " | " << (unsigned int)time(0) << " | " << error[level] << " | " << msg << " |";return std::cout;
}
// 2. comm.hpp
#pragma once#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <cstring>using namespace std;#include "log.hpp"// key_t ftok(const char *pathname, int proj_id);
#define PATH_NAME "/home/zs/linux/testcpp"
#define PROJ_ID 0x20231118// 共享内存的大小,最好是页(PAGE:4096)的整数倍
#define SHM_SIZE 4096#define MODE 0666
#define FIFO_PATH "./fifo"class FIFO
{
public:FIFO(){if(0 != mkfifo(FIFO_PATH, MODE)){perror("mkfifo");exit(1);}Log("create fifo success", lev_1) << endl;}~FIFO(){if(0 != unlink(FIFO_PATH)){perror("unlink");exit(2);}Log("unlink fifo success", lev_2) << endl;}
};int openFIFO(const string& pathname, int flags)
{int fd = open(pathname.c_str(), flags);if(fd == -1){perror("open");exit(3);}return fd;
}void Wait(int fd)
{uint32_t i = 0;ssize_t size = read(fd, &i, sizeof(i));if(size == -1){perror("Wait::read");exit(4);}
}void Signal(int fd)
{uint32_t i = 1;ssize_t size = write(fd, &i, sizeof(i));if(size == -1){perror("Signal::write");exit(5);}
}void closeFIFO(int fd)
{close(fd);
}

创建共享内存使用shmget函数。
key:只有创建的时候用到key(key是调用ftok用算法形成的,标识了系统层上的唯一性);大部分情况下用户访问共享内存用的还是shmid(shmget的返回值,它的使用类似文件描述符fd,标识了用户层上的唯一性)。
size:设置创建的共享内存的大小。
shmflg

  • IPC_CREAT:单独使用,如果共享内存不存在,创建并返回;如果已经存在,获取并返回。
  • IPC_EXCL:单独使用,没有意义。
  • IPC_CREAT | IPC_EXCL:共同使用,如果共享内存不存在,创建并返回;如果已经存在,出错返回(如果返回成功,一定是一个全新的共享内存)。
    在这里插入图片描述
    创建共享内存之前key的生成由ftok接口完成。
    ftok可以确保接收到相同的pathnameproj_id会使用算法生成相同的key
    而不同进程通过拿到相同的key值来看到同一份资源。
    在这里插入图片描述
// 3. server.hpp
#include "comm.hpp"// 程序在加载时,自动构建全局变量,自动调用fifo的构造函数,创建管道文件
// 程序退出时,全局变量会被析构,自动调用fifo的析构函数,删除管道文件
FIFO fifo;void test()
{// 通信的前置工作,让不同进程看到同一份资源(内存)// 1.创建公共的key值key_t key = ftok(PATH_NAME, PROJ_ID);if(key == -1){perror("ftok");exit(1);}Log("server create key success", lev_1) << " server key: " << key << endl;// 2.创建共享内存 -- 建议创建一个全新的共享内存int shmid = shmget(key, SHM_SIZE, IPC_CREAT | IPC_EXCL | MODE);if(shmid == -1){perror("shmget");exit(2);}Log("server create shm success", lev_2) << " shmid: " << shmid << endl;// 3.将共享内存挂接到自己的地址空间char* shmaddr = (char*)shmat(shmid, nullptr, 0);if((void*)shmaddr == (void*)-1){perror("shmaddr");exit(3);}Log("server attach shm success", lev_3) << " shmaddr: " << (void*)shmaddr << endl;// 4.通信 -- 将共享内存看做字符串存储空间// 关于共享内存的通信有两个结论:// a.通信双方一方可以直接向共享内存写数据,另一方可以立刻看到写的数据//   共享内存是所有进程间通信(IPC)速度最快的。因为它不需要过多的拷贝(表现就是不需要将数据给到操作系统)//   共享内存映射进进程地址空间的共享区(在用户空间内),所以不需要经过系统调用,可以直接访问,双方进程通信属于内存级的读和写。// b.共享内存缺乏访问控制,会引起并发问题// 此处采用管道的访问控制功能,来对共享内存进行一定的访问控制int fd = openFIFO(FIFO_PATH, O_RDONLY);while(true){Log("server wait...", lev_2) << endl;Wait(fd);cout << "server output: " << shmaddr << endl;if(strcmp(shmaddr, "quit") == 0){cout << "client quit, then server quit" << endl;break;}// sleep(1);}closeFIFO(fd);// sleep(10);// 5.将共享内存从地址空间中祛关联int dt = shmdt(shmaddr);if(dt == -1){perror("shmdt");exit(4);}Log("server detach shm success", lev_4) << endl;// 6.删除共享内存 -- IPC_RMID: 即使当前还有进程和shm挂接,依旧会删除shmint ipcrm = shmctl(shmid, IPC_RMID, nullptr);if(ipcrm == -1){perror("shmctl");exit(5);}Log("server rm shm success", lev_1) << endl;
}

共享内存的提供者是操作系统,当进程运行结束时,共享内存是还存在着的,其生命周期是随操作系统的。共享内存有两种删除方式,一是手动删除,二是利用代码调用接口删除。
在这里插入图片描述
可以调用shmctl接口进行删除。
cmd:填写删除指令IPC_RMID
buf:是用于描述共享内存结构的结构体指针,使用buf可以对共享内存的结构进行更改。
这里可以引出对共享内存的重新理解:共享内存 = 共享内存块 + 对应的共享内存的内核数据结构。
操作系统为了管理大量的共享内存,需要先描述再组织,进行管理。
在这里插入图片描述
buf所指向的共享内存的结构体。
在这里插入图片描述
共享进程创建好后,进程要访问共享进程还需要对其进行挂接(将共享内存映射进进程所在地址空间中)。
shmaddr:除非自己特别清楚要将共享内存挂接在什么位置,否则nullptr让系统自己处理。
在这里插入图片描述

// 4. client.cpp
#include "comm.hpp"void test()
{// 1.获取keyLog("client pid is:", lev_0) << " " << getpid() << endl;key_t key = ftok(PATH_NAME, PROJ_ID); // key_t -- intif(key == -1){perror("ftok");exit(1);}Log("client create key success", lev_1) << " client key: " << key << endl;// 2.获取共享内存int shmid = shmget(key, SHM_SIZE, IPC_CREAT);if(shmid == -1){perror("shmget");exit(2);}Log("client get shm success", lev_2) << " shmid: " << shmid << endl;// 3.将共享内存挂接到自己的地址空间char* shmaddr = (char*)shmat(shmid, nullptr, 0);if((void*)shmaddr == (void*)-1){perror("shmaddr");exit(3);}Log("client attach shm success", lev_3) << " shmaddr: " << (void*)shmaddr << endl;// 4.通信int fd = openFIFO(FIFO_PATH, O_WRONLY);while(true){write(1, "client input: ", strlen("client input: "));ssize_t size = read(0, shmaddr, SHM_SIZE);if(size == -1){perror("read");exit(5);}shmaddr[size - 1] = '\0';Signal(fd);if(strcmp(shmaddr, "quit") == 0) break;}closeFIFO(fd);// 5.祛关联int dt = shmdt(shmaddr);if(dt == -1){perror("shmdt");exit(4);}Log("client detach shm success", lev_4) << endl;
}

在这里插入图片描述

信号量

这里不谈消息队列的问题,但消息队列的接口使用和共享内存都是相似的。
在这里插入图片描述
临界资源:多个进程(执行流)看到的一份公共的资源。
临界区:进程里,访问临界资源的代码部分。
由临界资源和临界区的概念可以知道,多个进程(执行流),同时运行时会互相干扰,主要是不加保护地访问了同一份资源(临界资源)。而再分临界区,多个进程(执行流)是互不影响的。
互斥:为了更好地进行临界资源的保护,可以让多个进程(执行流)在任何时刻,只能有一个进入临界区。
原子性:要么不做,要么做完,没有中间状态。

信号量的出现让任何一个进程要访问临界资源时,不能直接访问,而是要先申请信号量(这里可以看出信号量也属于临界资源,可以被多个进程看到)。
信号量本质是一个计数器,申请信号量的本质就是让计数器减一。
只要申请信号量成功,在临界资源内部一定会预留有该进程要访问的资源。
而申请信号量的本质,是对临界资源的一种预定机制。
申请信号量(P操作)和释放信号量(V操作)必须是原子的。
在这里插入图片描述

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

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

相关文章

Java(一)(引用类型的参数在传递,方法重载,面向对象编程基础)

基本类型和引用类型的参数在传递的时候有什么不同? 基本类型的值传递:参数传输存储的数据值 引用类型的值传递:参数传输存储的地址值 传递数组名字的时候,传递的是数组的地址,change方法可以通过地址直接访问我们在堆内存中开辟的数组,然后改变数组,数组中的元素发生变化 方…

K-Means算法进行分类

已知数据集D中有9个数据点&#xff0c;分别是&#xff08;1,2&#xff09;&#xff0c;(2,3), (2,1), (3,1),(2,4),(3,5),(4,3),(1,5),(4,2)。采用K-Means算法进行聚类&#xff0c;k2&#xff0c;设初始中心点为&#xff08;1.1,2.2&#xff09;&#xff0c;&#xff08;2.3,3.…

在Rust编程中使用泛型

1.摘要 Rust中的泛型可以让我们为像函数签名或结构体这样的项创建定义, 这样它们就可以用于多种不同的具体数据类型。下面的内容将涉及泛型定义函数、结构体、枚举和方法, 还将讨论泛型如何影响代码性能。 2.在函数定义中使用泛型 当使用泛型定义函数时&#xff0c;本来在函…

竞赛选题 疲劳驾驶检测系统 python

文章目录 0 前言1 课题背景2 Dlib人脸识别2.1 简介2.2 Dlib优点2.3 相关代码2.4 人脸数据库2.5 人脸录入加识别效果 3 疲劳检测算法3.1 眼睛检测算法3.2 打哈欠检测算法3.3 点头检测算法 4 PyQt54.1 简介4.2相关界面代码 5 最后 0 前言 &#x1f525; 优质竞赛项目系列&#x…

基于Adapter用CLIP进行Few-shot Image Classification

文章目录 【ECCV 2022】《Tip-Adapter: Training-free Adaption of CLIP for Few-shot Classification》【NeuIPS 2023】《Meta-Adapter: An Online Few-shot Learner for Vision-Language Model》 【ECCV 2022】《Tip-Adapter: Training-free Adaption of CLIP for Few-shot C…

使用Redis实现分布式锁

Hi, I’m Shendi 使用Redis实现分布式锁 需求场景 需要使用到分布式锁的场景非常多&#xff0c;例如抢单等并发场景&#xff0c;这里举一个例子。 有一个商品&#xff0c;限量出售100个&#xff0c;一个用户下单&#xff0c;数量就减少一个&#xff0c;当剩下最后一个时&…

Unity在Windows选项下没有Auto Streaming

Unity在Windows选项下没有Auto Streaming Unity Auto Streaming插件按网上说的不太好使最终解决方案 Unity Auto Streaming插件 我用的版本是个人版免费版&#xff0c;版本号是&#xff1a;2021.2.5f1c1&#xff0c;我的里边Windows下看不到Auto Streaming选项,就像下边这张图…

基于猕猴感觉运动皮层Spike信号的运动解码分析不同运动参数对解码的影响

公开数据集中文版详细描述参考前文&#xff1a;https://editor.csdn.net/md/?not_checkout1&spm1011.2124.3001.6192神经元Spike信号分析参考前文&#xff1a;https://blog.csdn.net/qq_43811536/article/details/134359566?spm1001.2014.3001.5501神经元运动调制分析参考…

CUDA编程一、基本概念和cuda向量加法

目录 一、cuda编程的基本概念入门 1、GPU架构和存储结构 2、cuda编程模型 3、cuda编程流程 二、cuda向量加法实践 1、代码实现 2、代码运行和结果 有一段时间对模型加速比较感兴趣&#xff0c;其中的一块儿内容就是使用C和cuda算子优化之类一起给模型推理提速。之前一直…

HAL库STM32串口开启DMA接收数据

STM32CubeMx的配置 此博客仅仅作为记录&#xff0c;这个像是有bug一样&#xff0c;有时候好使&#xff0c;有时候不好&#xff0c;所以趁现在好使赶紧记录一下&#xff0c;很多地方用到串口接收数据&#xff0c;DMA又是一种非常好的接收方式&#xff0c;可以节约CPU的时间&…

Unity - Cinemachine

动态获取Cinemachine的内部组件 vCam.GetCinemachineComponent<T>() 动态修改Cinemachine的Transposer属性 var vCamComp transfrom.GetComponent<CinemachineVirtualCamera>(); var transposerComp vCamComp.GetCinemachineComponent<CinemachineTransposer&…

2023最新最全【OpenMV】 入门教程

1. 什么是OpenMV OpenMV 是一个开源&#xff0c;低成本&#xff0c;功能强大的 机器视觉模块。 OpenMV上的机器视觉算法包括 寻找色块、人脸检测、眼球跟踪、边缘检测、标志跟踪 等。 以STM32F427CPU为核心&#xff0c;集成了OV7725摄像头芯片&#xff0c;在小巧的硬件模块上&a…