文章目录

  • 一、共享内存的原理
  • 二、信道的建立
    • 1.创建共享内存
      • 1.key的作用
      • 2.key的选取
      • 3.shmid的作用
      • 4.key和shmid的区别
      • 5.内存设定的特性
      • 6.shmflg的设定
    • 2.绑定共享内存
    • 3.代码示例
  • 三、利用共享内存通信
    • 1.通信
    • 2.解除绑定
    • 3.销毁共享内存
      • 1.命令行销毁
      • 2.程序中销毁
  • 四、共享内存的生命周期
  • 五、数据安全问题
  • 六、源码
    • Fifo.hpp
    • Comm.hpp
    • Shm.hpp
    • server.cc
    • client.cc

一、共享内存的原理

共享内存是通过在物理内存上开辟一块空间,然后让需要通信的进程都映射到这一块空间,这样就使它们看到同一块资源了。

在这里插入图片描述

共享内存区是最快的IPC形式。⼀旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递
不再涉及到内核,换句话说是进程不再通过执⾏进⼊内核的系统调⽤来传递彼此的数据

共享内存通信是双向的,也就是说一个进程可以既读又写,使用起来就和C语言的malloc申请到的内存差不多。这种通信方式存在着数据安全问题,会在下文细说。

二、信道的建立

1.创建共享内存

创建共享内存使用shmget函数,它的作用是创建或获取共享内存段的系统调用。

对于shmget的使用来说,虽然操作起来相对简单,但要完全理解其各种参数的设定则较为困难。不过接下来我会进行详细讲解。

shmget声明如下:

#include <sys/ipc.h>
#include <sys/shm.h>int shmget(key_t key, size_t size, int shmflg);
  • 参数key:用户设定任意一个数,用于区分不同共享内存,通常由ftok生成。
  • 参数size:设定共享内存的大小。
  • 参数shmflg:标志位,用于指定共享内存段的创建方式和权限。常见的标志包括:
    IPC_CREAT :如果共享内存段不存在,则创建它。
    IPC_EXCL :与 IPC_CREAT 一起使用,确保创建的共享内存段是新的。
    权限标志:如 0666,表示所有用户都有读写权限。
  • 返回值: 成功时返回共享内存段的标识符 (shmid) 。 失败时返回 -1,并设置 errno 以指示错误类型。

1.key的作用

  • 思考1:在用户层面如何让两个独立进程共享同一块内存?
  • 思考2:在匿名管道和命名管道中,用户层面是如何让两个进程确定同一个资源的?

问题2很显然,管道的本质是文件,用户通过让两个程序打开同一个文件名来实现看到同一个资源。

因此,共享内存同样需要一个key来充当类似文件名的功能。

2.key的选取

key参数本质是一个int类型,我们可以直接指定一个数值传入,当然,为了更规范,更专业,我们通常都会使用ftok来生成。

#include <sys/types.h>
#include <sys/ipc.h>key_t ftok(const char *pathname, int proj_id);
  • 参数pathnme:一个存在的文件路径(例如 /tmp/myfile),文件必须存在,否则 ftok 会失败。
  • 参数proj_id:一个整数,用于进一步区分不同的 IPC 对象。
  • 返回值:
    成功:返回生成的 key_t 键值。
    失败:返回 -1,并设置 errno 以指示错误原因。

3.shmid的作用

shmid是一个int类型,由shmget返回,在作用上和物理意义上与文件系统中的fd类似。

它的作用主要是让用户找到指定的共享内存。

在操作系统内核中,shmid 的物理意义如下:

内核维护一个 共享内存段表(如 struct shmid_kernel),每个表项对应一块共享内存。

shmid 是该表的索引,通过它找到对应的共享内存段(含物理页、权限、挂载进程等信息)。

共享内存最终映射到 实际的物理页帧(通过页表机制)。

物理本质是内存页的映射:多个进程的虚拟地址映射到同一组物理页。
在这里插入图片描述

4.key和shmid的区别

key最终成为系统层区分不同IPC的标志,而shmid则是用户层用来区分不同IPC的标志。

5.内存设定的特性

这里的内存设定指的是shmget函数中的参数size。

当传入的内存不足4096字节(4KB)的倍数时,会扩到4096倍数。但是只会提供size大小的使用空间。这样做可以规避掉一些因为共享内存过多带来的问题。

6.shmflg的设定

对于共享内存,我们可以将程序简单分为创建端和使用端,它们的shmflg设定通常是:

  • 创建端:IPC_CREAT | IPC_EXCL | 0666
  • 使用端:IPC_CREAT

创建端要保证IPC是最新的,所以需要加IPC_EXCL,然后还需要设定权限。

使用端只需要获取共享内存段的系统调用,因此只用一个IPC_CREAT即可。

2.绑定共享内存

以上我们完成的只是共享内存的创建,接下来还需要把进程绑定到共享内存,使用函数shmat,其中at指的是单词attach。

#include <sys/types.h>
#include <sys/shm.h>void *shmat(int shmid, const void *shmaddr, int shmflg);
  • 参数shmid:传入从shmget中返回的shmid来指定共享内存。
  • 参数shmaddr:指定共享内存段附加到进程地址空间的位置,通常设为nullptr,系统会自动选择一个合适的地址。
  • 参数shmflg:读写方式,常用的有:
    SHM_RDONLY:以只读方式附加共享内存段。
    0:以读写方式附加共享内存段。
  • 返回值:
    成功时,返回共享内存段附加到进程地址空间的起始地址。
    失败时,返回 (void *) -1,并设置 errno。

3.代码示例

创建端程序:

int main()
{//生成一个keyint key = ftok(".", 48);//创建共享内存int shmid = shmget(key, 4069, IPC_CREAT | IPC_EXCL | 0666);//连接到共享内存void* p = shmat(shmid,nullptr,0);//使用共享内存//... ...return 0;
}

使用端程序:

int main()
{//生成一个相同keyint key = ftok(".", 48);//获取到共享内存的系统调用int shmid = shmget(key, 4069, IPC_CREAT);//连接到共享内存void* p = shmat(shmid,nullptr,0);//使用共享内存//... ...return 0;
}

注意:为了简洁和方便说明问题,以上代码省略了头文件的包含和返回值有效性的判断等等,在实际开发中可不敢省略。

三、利用共享内存通信

1.通信

上文我们只是完成了信道的建立,接下来我们进行通信,通过上面的操作,我们已经获取到共享内存的起始地址。

它的用法与C语言的malloc申请的内存用法相同,只是共享内存可以同时被两个进程访问。

如下写端:

int main()
{int key = ftok(".", 48);int shmid = shmget(key, 4069, IPC_CREAT | IPC_EXCL | 0666);void* p = shmat(shmid,nullptr,0);//使用共享内存char* chp = (char*)p;for(int i='a';i<='z';i++){sleep(1);*chp=i;chp++;}return 0;
}

读端:

int main()
{int key = ftok(".", 48);int shmid = shmget(key, 4069, IPC_CREAT);void* p = shmat(shmid,nullptr,0);//使用共享内存char* chp = (char*)p;while(true){sleep(1);cout<<chp<<endl;}return 0;
}

注意:为了获取到同一个共享内存,我们设定的key必须一致。

2.解除绑定

如果进程退出时没有解除绑定,共享内存段仍然会保留在系统的共享内存资源中,直到显式删除(通过 shmctl 或系统重启)。

使用shmdt来解除绑定,其中dt代表单词delete。

int shmdt(const void *shmaddr);
  • 参数shmaddr:需要断开连接的共享内存的起始地址。

  • 返回值:

    成功:返回0。
    失败:返回-1,并设置errno以指示错误原因。
    一个共享内存,与它绑定的程序的个数是由一个引用计数机制进行维护的,当shmdt成功,引用计数减1。

3.销毁共享内存

共享内存不会随程序的结束而销毁,它是随内核的 因此需要显式地进行销毁,可以使用shmctl函数。或在命令行中使用指令进行销毁。

1.命令行销毁

查看共享内存信息

ipcs -m

如下:

在这里插入图片描述

nattch信息:它表示与该 共享内存连接的程序个数。

销毁共享内存

ipcrm -m 2

这里需要填入shmid(即这里的2)来指定共享内存。

2.程序中销毁

在程序中销毁我们使用函数shmctl,其中ctl代表单词control。

#include <sys/ipc.h>
#include <sys/shm.h>int shmctl(int shmid, int cmd, struct shmid_ds *buf);
  • 参数shmid:传入从shmget返回的shmid来指定需要销毁的共享内存。
  • 参数cmd:需要传入一个操作选项,操作选项很多,而IPC_RMID就是用来销毁共享内存的。
  • 参数shmid_ds:这是一个输出型参数,如果你需要获取共享内存的信息,则传入一个shmid_ds类型的指针来接收,如果不是通常传入nullptr即可。
  • 返回值:
    成功时返回 0。
    失败时返回 -1,并设置 errno 以指示错误类型。

注:命令行销毁和程序中销毁效果是一样的,因为命令行销毁底层还是调用了shmctl函数。

四、共享内存的生命周期

共享内存的生命周期是不随进程的,而是随内核,如果没有显示删除它就会一直存在,尽管相关的进程已经退出。直到重装系统才得以释放。

使用shmctl释放共享内存存在的情况

1.正常释放

nattach(引用计数)为0时,即没有进程与它绑定,被正常释放。

2.共享内存段被标记为已删除,但仍有进程附加(shmat

共享内存段已经被标记为已删除(不能附加到新的进程),但之前仍有一些进程附加到该共享内存段并正在使用。所以共享内存段不会被立即释放。只有当所有附加的进程都调用 shmdt 分离后,系统才会释放资源。

五、数据安全问题

共享内存最大的优点就是快 相比使用管道技术,它减少了中间复杂转化和拷贝工作,而是直接对物理内存进行访问。

但它也有一个致命的缺点,相比管道技术,共享内存它的读端和写端是不带有同步机制的,这就很容易使得数据混乱,也就是造成数据不一致问题。

比如我们写端写入“hello world”,而读端读到的可能是“he”,“ll”,“o wor”,“ld”等等无法预测的奇葩数据。 读端一个劲地读,不会管写端这句话是否已经说完,而且也无法知道。

当我们不了解锁的情况下想要解决这个问题,可以利用命名管道来解决,因为命名管道带有同步机制,我们用它的write和read函数来保护数据的安全,当然write和read并不用写或读什么有意义的数据。

六、源码

Fifo.hpp

#pragma once
#include <iostream>
#include <cstdio>
#include <sys/types.h>
#include <sys/stat.h>
#include <string>
#include <fcntl.h>
#include <unistd.h>
using namespace std;#include "Comm.hpp"#define PATH "."
#define FILENAME "fifo"//#define FIFO_FILE "fifo"class NamedFifo
{
public:NamedFifo(const string& path,const string& name):_path(path),_name(name){_fifoname =_path + "/" + _name;umask(0);//创建管道int n = mkfifo(_fifoname.c_str(), 0666);if(n < 0){cout << "mkdir fifo error" << endl;ERR_EXIT("makefifo");}else{cout << "fifo success" << endl;}}~NamedFifo(){//删除管道文件int n = unlink(_fifoname.c_str());if(n == 0){cout << "unlink fifo" << endl;}else{// perror("remove fifo fail");// exit(1);ERR_EXIT("unlink");}}
private:string _path;string _name;string _fifoname;
};class FileOper
{
public:FileOper(const string& path,const string& name):_path(path), _name(name), _fd(-1){_fifoname = _path + "/" + _name;}~FileOper(){}void OpenForRead(){// 打开, write 方没有执行open的时候,read方,就要在open内部进行阻塞// 直到管道文件打开了,open才会返回!_fd = open(_fifoname.c_str(), O_RDONLY);if(_fd < 0){ERR_EXIT("open");}cout << "open fifo success" << endl;}void OpenForWrite(){//写_fd = open(_fifoname.c_str(), O_WRONLY);if(_fd < 0){ERR_EXIT("open");}cout << "open fifo success" << endl;}void Wakeup(){//写操作char c = ' ';int n = write(_fd, &c, 1);printf("尝试唤醒: %d\n", n);}bool Wait(){//读操作char c;int n = read(_fd, &c, 1);if(n > 0){printf("唤醒成功: %d\n", n);return true;}else{return false;}}void Close(){if(_fd > 0){close(_fd);}}private:string _path;string _name;string _fifoname;int _fd;
};

Comm.hpp

#pragma once#include <cstdio>
#include <cstdlib>#define ERR_EXIT(m) \
do\
{\perror(m);\exit(EXIT_FAILURE);\
}while(0)

Shm.hpp

#pragma once#include <iostream>
#include <cstdio>
#include <string>
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <unistd.h>
using namespace std;#include "Comm.hpp"const int gdefaultid = -1;
const int gsize = 4096;
const string pathname = ".";
const int projid = 0x66;
const int gmode = 0666;#define CREATER "creater"
#define USER "user"class Shm
{
public:Shm(const string& pathname,int projid,const string& usertype):_shmid(gdefaultid),_size(gsize),_start_mem(nullptr),_usertype(usertype){_key = ftok(pathname.c_str(), projid);if(_key < 0){ERR_EXIT("fotk");}if(_usertype == CREATER){   //创建共享内存Create();}else if(_usertype == USER){//得到共享内存Get();}else{}//链接共享内存Attach();}void* VirtualAddr(){printf("VirtualAddr: %p\n", _start_mem);return _start_mem;}int Size(){return _size;}void Attr(){struct shmid_ds ds;//ds输出型参数int n = shmctl(_shmid, IPC_STAT, &ds);printf("shm_segsz: %ld\n", ds.shm_segsz);printf("key: 0x%x\n", ds.shm_perm.__key);}~Shm(){cout << _usertype << endl;if(_usertype == CREATER){Destroy();}}private://创建新的共享内存void CreateHelper(int flg){printf("key: 0x%x\n", _key);//共享内存的生命周期,跟随内核_shmid = shmget(_key, _size, flg);if(_shmid < 0){ERR_EXIT("shmget");}printf("shmid: %d\n", _shmid);}void Create(){CreateHelper(IPC_CREAT | IPC_EXCL | gmode);}void Attach(){_start_mem = shmat(_shmid, nullptr, 0);if((long long)_start_mem < 0){ERR_EXIT("shmat");}printf("attach success\n");}void Detach(){int n = shmdt(_start_mem);if(n == 0){printf("detach success\n");}}void Get(){CreateHelper(IPC_CREAT);}void Destroy(){if(_shmid == gdefaultid){return;}Detach();if(_usertype == CREATER){int n = shmctl(_shmid, IPC_RMID,nullptr);if(n > 0){printf("shmctl delete shm: %d success!\n", n);}else{ERR_EXIT("shmctl");}}}private:int _shmid;key_t _key;int _size;void* _start_mem;string _usertype;
};

server.cc

#include "Shm.hpp"
#include "Fifo.hpp"int main()
{Shm shm(pathname, projid, CREATER);sleep(5);shm.Attr();NamedFifo fifo(PATH, FILENAME);// 文件操作FileOper readerfile(PATH, FILENAME);readerfile.OpenForRead();char* mem =  (char*)shm.VirtualAddr();//读写共享内存,没有使用系统调用while(true){if(readerfile.Wait()){printf("%s\n", mem);}else{break;}}readerfile.Close();cout << "server end normal!" << endl;return 0;
}

client.cc

#include "Shm.hpp"
#include "Fifo.hpp"int main()
{FileOper writerfile(PATH, FILENAME);writerfile.OpenForWrite();Shm shm(pathname, projid, USER);char* mem = (char*)shm.VirtualAddr();int index = 0;for(char c = 'A';c <= 'Z';c++,index += 2){sleep(2);mem[index] = c;mem[index + 1] = c;mem[index + 2] = 0;writerfile.Wakeup();sleep(1);}writerfile.Close();return 0;
}

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

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

相关文章

Python 程序设计讲义(9):Python 的基本数据类型——复数

Python 程序设计讲义&#xff08;9&#xff09;&#xff1a;Python 的基本数据类型——复数 复数与数学中的复数概念类似。在 Python 中&#xff0c;复数表示为 abj&#xff0c;其中&#xff1a;a为实数部分&#xff0c;b为虚数部分&#xff0c;j称为虚数单位。复数必须包含虚数…

leetcode_121 买卖股票的最佳时期

1. 题意 有一个股价变化图&#xff0c;你可以在一天买入&#xff0c;在未来一天卖出。 求通过这样一次操作的最大获利。 2. 题解 2.1 枚举 直接枚举&#xff0c;买入卖出的时间&#xff0c;肯定会超时啦~ 时间复杂度为O(n2)O(n^2)O(n2) 空间复杂度为O(1)O(1)O(1) class …

ToBToC的定义与区别

B 端和 C 端主要是从产品所面向的用户群体角度来区分的&#xff0c;B 端指的是企业用户&#xff08;Business&#xff09;&#xff0c;C 端指的是个人消费者&#xff08;Consumer&#xff09;&#xff0c;它们在多个方面存在明显区别&#xff0c;具体如下&#xff1a;用户特征B…

Python 程序设计讲义(8):Python 的基本数据类型——浮点数

Python 程序设计讲义&#xff08;8&#xff09;&#xff1a;Python 的基本数据类型——浮点数 目录Python 程序设计讲义&#xff08;8&#xff09;&#xff1a;Python 的基本数据类型——浮点数一、浮点数的表示形式1、小数形式2、指数形式二、浮点数的精确度浮点数也称小数&am…

MCP客户端架构与实施

前言:从模型到生产力 — MCP的战略价值 在过去的一年里,我们团队见证了大型语言模型(LLM)从技术奇迹向企业核心生产力工具的演变。然而,一个孤立的LLM无法解决实际的业务问题。真正的价值释放,源于将模型的认知能力与企业现有的数据、API及工作流进行无缝、安全、可扩展…

白盒测试核心覆盖率标准详解文档

白盒测试核心覆盖率标准详解文档 1. 什么是白盒测试与覆盖率&#xff1f; 白盒测试&#xff08;White-box Testing&#xff09;&#xff0c;又称结构测试或逻辑驱动测试&#xff0c;是一种测试方法&#xff0c;测试人员能够访问并了解被测软件的内部结构、代码和实现逻辑。测试…

顺丰面试提到的一个算法题

顺丰面试提到的一个算法题面试过程中大脑空白&#xff0c;睡了一觉后突然想明白了 原理非常简单就是根据数组中元素的值对值对应的索引进行排序 哎&#xff0c;&#xff0c;&#xff0c;&#xff0c;具体看以下代码吧[使用 Java 17 中 Stream 实现] 最好别用 CSDN 提供的在线运…

ChatGPT Agent深度解析:告别单纯问答,一个指令搞定复杂任务?

名人说&#xff1a;博观而约取&#xff0c;厚积而薄发。——苏轼《稼说送张琥》 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 目录一、什么是ChatGPT Agent&#xff1f;从"客服"到"秘书"的华丽转…

位运算在算法竞赛中的应用(基于C++语言)_位运算优化

在C算法竞赛中&#xff0c;位运算优化是一种非常重要的技巧&#xff0c;因为它可以显著提高算法的效率。以下是一些常见的位运算优化方法及其在各种算法中的应用示例&#xff1a; 常见的位运算优化 1&#xff09;位与运算 &&#xff1a; 用途&#xff1a;用于检查某个位是否…

SpringBoot 使用Rabbitmq

1.Springboot默认MQ支持rabbitmq或者kafka maven引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId></dependency>propertis添加配置 # spring.rabbitmq.host192.168…

C++核心编程学习4--类和对象--封装

C面向对象有三大特性&#xff1a;封装、继承和多态。 封装 将属性和行为作为一个整体。将属性和行为加以权限控制。 例子1&#xff1a;设计一个圆类 #include <iostream> using namespace std;// 设计一个圆类&#xff0c;求圆的周长 // 圆周率&#xff1a;3.14 const do…

AC身份认证实验之AAA服务器

一、实验背景某公司需要在企业的公司网络出口使用上网行为管理设备&#xff0c;以审计管理局域网的所有设备&#xff0c;同时&#xff0c;局域网内的所有设备都将上网行为代理上网&#xff0c;但是发生过访客外传一些非法信息&#xff0c;所以需要对外来人员进行实名认证&#…

数组算法之【数组中第K个最大元素】

目录 LeetCode-215题 LeetCode-215题 给定整数数组nums和整数k&#xff0c;返回数组中第k个最大元素 public class Solution {/*** 这里是基于小顶堆这种数据结构来实现的*/public int findKthLargest(int[] nums, int k) {// 实例化一个小顶堆MinHeap minHeap new MinHeap…

高亮匹配关键词样式highLightMatchString、replaceHTMLChar

replaceHTMLChar: s > s.toString().replace(/</g, <).replace(/>/g, >),// 高亮匹配关键词样式----------------------------------------highLightMatchString(originStr, matchStr, customClass ) {matchStr && (matchStr matchStr.replace(/[.*?…

HUAWEI Pura80系列机型参数对比

类别HUAWEI Pura80 UltraHUAWEI Pura80 ProHUAWEI Pura80 ProHUAWEI Pura80建议零售价&#xffe5;9999起&#xffe5;7999起&#xffe5;6499起&#xffe5;4699起颜色鎏光金、鎏光黑釉红、釉青、釉白、釉黑釉金、釉白、釉黑丝绒金、丝绒绿、丝绒白、丝绒黑外观材质设计光芒耀…

使用 PyTorch 的 torchvision 库加载 CIFAR-10 数据集

CIFAR-10是一个更接近普适物体的彩色图像数据集。CIFAR-10 是由Hinton 的学生Alex Krizhevsky 和Ilya Sutskever 整理的一个用于识别普适物体的小型数据集。一共包含10 个类别的RGB 彩色图片&#xff1a;飞机&#xff08; airplane &#xff09;、汽车&#xff08; automobile …

蓝桥杯51单片机

这是我备考省赛的时候总结的错误点和创新点那个时候是用来提醒自己的&#xff0c;现在分享给你们看^_^一考点二注意点记得初始化&#xff39;&#xff14;&#xff0c;&#xff39;&#xff15;&#xff0c;&#xff39;&#xff16;&#xff0c;&#xff39;&#xff17;&…

【2025/07/23】GitHub 今日热门项目

GitHub 今日热门项目 &#x1f680; 每日精选优质开源项目 | 发现优质开源项目&#xff0c;跟上技术发展趋势 &#x1f4cb; 报告概览 &#x1f4ca; 统计项&#x1f4c8; 数值&#x1f4dd; 说明&#x1f4c5; 报告日期2025-07-23 (周三)GitHub Trending 每日快照&#x1f55…

【生成式AI導論 2024】第12講:淺談檢定大型語言模型能力的各種方式 学习记录

跟标准答案做对比看是否正确 选择题是不是正确 MMLU massive multitask Language Understanding MT-bench 使用语言模型来评分 还有其他任务的对比,也有特别刁钻的问题 阅读长文的能力 grep kamradt 大海捞针

嵌入式 Qt 开发:实现开机 Logo 和无操作自动锁屏

在嵌入式设备开发中&#xff0c;为设备添加开机 Logo 和无操作自动锁屏功能是提升用户体验的重要环节。本文将详细介绍如何在 Qt 嵌入式项目中实现这两个功能。我们将使用 Qt 5/6 和 Linux 环境&#xff0c;确保代码的可移植性和通用性。项目结构为了实现这两个功能&#xff0c…