(26)Linux 进程通信之共享内存(共享储存空间)

共享内存是System V版本的最后一个进程间通信方式。共享内存,顾名思义就是允许两个不相关的进程访问同一个逻辑内存,共享内存是两个正在运行的进程之间共享和传递数据的一种非常有效的方式。不同进程之间共享的内存通常为同一段物理内存。进程可以将同一段物理内存连接到他们自己的地址空间中,所有的进程都可以访问共享内存中的地址。如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。

1、共享内存的通信原理 

在Linux中,每个进程都有属于自己的进程控制块(PCB)和地址空间(Addr Space),并且都有一个与之对应的页表,负责将进程的虚拟地址与物理地址进行映射,通过内存管理单元(MMU)进行管理。两个不同的虚拟地址通过页表映射到物理空间的同一区域,它们所指向的这块区域即共享内存。

共享内存原理图:

 上图:当两个进程通过页表将虚拟地址映射到物理地址时,在物理地址中有一块共同的内存区,即共享内存,这块内存可以被两个进程同时看到。这样当一个进程进行写操作,另一个进程读操作就可以实现进程间通信。但是,我们要确保一个进程在写的时候不能被读,因此我们使用信号量来实现同步与互斥。

对于一个共享内存,实现采用的是引用计数的原理,当进程脱离共享存储区后,计数器减一,挂架成功时,计数器加一,只有当计数器变为零时,才能被删除。当进程终止时,它所附加的共享存储区都会自动脱离。

2、为什么共享内存速度最快?

借助上图说明:Proc A 进程给内存中写数据, Proc B 进程从内存中读取数据,在此期间一共发生了两次复制

(1)Proc A 到共享内存       (2)共享内存到 Proc B

因为直接在内存上操作,所以共享内存的速度也就提高了。

最简单的共享内存的使用流程

  • ①ftok函数生成键值
  • ②shmget函数创建共享内存空间
  • ③shmat函数获取第一个可用共享内存空间的地址
  • ④shmdt函数进行分离(对共享存储段操作结束时的步骤,并不是从系统中删除共享内存和结构)
  • ⑤shmctl函数进行删除共享存储空间

3、ftok函数生成键值 

每一个共享存储段都有一个对应的键值(key)相关联(消息队列、信号量也同样需要)。

//使用此函数,需导入此头文件
#include <sys/types.h>
#include <sys/ipc.h>key_t ftok(const char *pathname, int proj_id);
  • const char *pathname: 一个以null结尾的字符串,表示文件系统中文件的路径。文件应该在调用ftok()时存在。在多个进程中生成相同的键时,需要使用相同的文件路径。
  • int proj_id: 一个非零的整数,作为生成键的种子。在多个进程中生成相同的键时,需要使用相同的proj_id。
     

返回值:成功返回键值(相当于32位的int)。出错返回-1

总之,ftok() 是一个标准C库函数,用于为System V IPC对象生成键。它需要提供文件系统中文件的路径和一个非零整数作为参数。通过使用相同的文件路径和非零整数,可以在多个进程之间生成相同的键,以便共享IPC对象。 

例如:key_t key = ftok( “/tmp”, 66);

 4、shmget函数创建共享存储空间并返回一个共享存储标识符

所需头文件:#include<sys/shm.h>函数原型: int shmget(key_t key, size_t size, int shmflg);
  • [参数key]:由ftok生成的key标识,标识系统的唯一IPC资源。
  • [参数size]:需要申请共享内存的大小。在操作系统中,申请内存的最小单位为页,一页是4k字节,为了避免内存碎片,我们一般申请的内存大小为页的整数倍。
  • [参数shmflg]:如果要创建新的共享内存,需要使用IPC_CREAT,IPC_EXCL,如果是已经存在的,可以使用IPC_CREAT或直接传0。
  • [返回值]:成功时返回一个新建或已经存在的的共享内存标识符,取决于shmflg的参数。失败返回-1并设置错误码。

例如:int id = shmget(key,4096,IPC_CREAT|IPC_EXCL|0666);创建一个大小为4096个字节的权限为0666(所有用户可读可写,具体查询linux权限相关内容)的共享存储空间,并返回一个整形共享存储标识符,如果key值已经存在有共享存储空间了,则出错返回-1。

     int id = shmget(key,4096,IPC_CREAT|0666);创建一个大小为4096个字节的权限为0666(所有用户可读可写,具体查询linux权限相关内容)的共享存储空间,并返回一个共享存储标识符,如果key值已经存在有共享存储空间了,则直接返回一个共享存储标识符。

 

 5、shmat 函数:挂接共享内存

//使用此函数,需导入此头文件
#include <sys/types.h>
#include <sys/shm.h>void *shmat(int shmid, const void *shmaddr, int shmflg);
  • int shmid: 一个整数,表示共享内存段的标识符(ID)。这个值通常是通过调用 shmget() 函数获得的。
  • const void *shmaddr: 一个指针,表示附加共享内存段的首选地址。通常将此参数设置为NULL,让系统自动选择一个合适的地址。
  • int shmflg: 一个整数,表示附加共享内存段的标志。常用标志包括:
  •     SHM_RDONLY: 以只读方式附加共享内存段。
  •     0: 以读写方式附加共享内存段。

返回值:

  • 成功时,shmat() 返回一个非空指针,表示共享内存段在当前进程地址空间的起始地址。
  • 失败时,返回 (void *)-1,并设置相应的 errno。

总之,shmat() 是一个Linux系统调用函数,用于将共享内存段附加到当前进程的地址空间。它需要提供共享内存段的标识符、首选地址和标志作为参数。成功时,它会返回一个指向共享内存段起始地址的指针,用于后续的内存访问操作。 

6、shmctl ( ):销毁共享内存 

int shmctl(int shmid, int cmd, struct shmid_ds *buf);
  • [参数shmid]:共享存储段标识符。
  • [参数cmd]:指定的执行操作,设置为IPC_RMID时表示可以删除共享内存。
  • [参数*buf]:设置为NULL即可。
  • [返回值]:成功返回0,失败返回-1。

7、shmdt函数进行分离 

  • 当不需要对此共享内存进行操作时候,调用shmdt函数进行分离,不是删除此共享存储空间哟。
  • 所需头文件:#include<sys/shm.h>
  • 函数原型: int shmdt(const void *addr);
  • addr为shmat函数返回的地址指针
  • 返回值:成功返回0;错误返回-1

例如:int ret = shmdt(addr);
下面是一个例子,希望对你对上面的内容理解有所帮助。

comm.hpp:

#pragma once#include <iostream>
#include <cstdio>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <cassert>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "Log.hpp"using namespace std; //不推荐#define PATH_NAME "/home/whb"
#define PROJ_ID 0x66
#define SHM_SIZE 4096 //共享内存的大小,最好是页(PAGE: 4096)的整数倍#define FIFO_NAME "./fifo"class Init
{
public:Init(){umask(0);int n = mkfifo(FIFO_NAME, 0666);assert(n == 0);(void)n;Log("create fifo success",Notice) << "\n";}~Init(){unlink(FIFO_NAME);Log("remove fifo success",Notice) << "\n";}
};#define READ O_RDONLY
#define WRITE O_WRONLYint OpenFIFO(std::string pathname, int flags)
{int fd = open(pathname.c_str(), flags);assert(fd >= 0);return fd;
}void Wait(int fd)
{Log("等待中....", Notice) << "\n";uint32_t temp = 0;ssize_t s = read(fd, &temp, sizeof(uint32_t));assert(s == sizeof(uint32_t));(void)s;
}void Signal(int fd)
{uint32_t temp = 1;ssize_t s = write(fd, &temp, sizeof(uint32_t));assert(s == sizeof(uint32_t));(void)s;Log("唤醒中....", Notice) << "\n";
}void CloseFifo(int fd)
{close(fd);
}

Log.hpp:

#ifndef _LOG_H_
#define _LOG_H_#include <iostream>
#include <ctime>#define Debug   0
#define Notice  1
#define Warning 2
#define Error   3const std::string msg[] = {"Debug","Notice","Warning","Error"
};std::ostream &Log(std::string message, int level)
{std::cout << " | " << (unsigned)time(nullptr) << " | " << msg[level] << " | " << message;return std::cout;
}#endif

Makefile:

.PHONY:all
all:shmClient shmServershmClient:shmClient.ccg++ -o $@ $^ -std=c++11
shmServer:shmServer.ccg++ -o $@ $^ -std=c++11
.PHONNY:clean
clean:rm -f shmClient shmServer

shmServer.cc:

#include "comm.hpp"// 是不是对应的程序,在加载的时候,会自动构建全局变量,就要调用该类的构造函数 -- 创建管道文件
// 程序退出的时候,全局变量会被析构,自动调用析构函数,会自动删除管道文件
Init init; string TransToHex(key_t k)
{char buffer[32];snprintf(buffer, sizeof buffer, "0x%x", k);return buffer;
}int main()
{// 我们之前为了通信,所做的所有的工作,属于什么工作呢:让不同的进程看到了同一份资源(内存)// 1. 创建公共的Key值key_t k = ftok(PATH_NAME, PROJ_ID);assert(k != -1);Log("create key done", Debug) << " server key : " << TransToHex(k) << endl;// 2. 创建共享内存 -- 建议要创建一个全新的共享内存 -- 通信的发起者int shmid = shmget(k, SHM_SIZE, IPC_CREAT | IPC_EXCL | 0666); //if (shmid == -1){perror("shmget");exit(1);}Log("create shm done", Debug) << " shmid : " << shmid << endl;// sleep(10);// 3. 将指定的共享内存,挂接到自己的地址空间char *shmaddr = (char *)shmat(shmid, nullptr, 0);Log("attach shm done", Debug) << " shmid : " << shmid << endl;// sleep(10);// 这里就是通信的逻辑了// 将共享内存当成一个大字符串// char buffer[SHM_SIZE];// 结论1: 只要是通信双方使用shm,一方直接向共享内存中写入数据,另一方,就可以立马看到对方写入的数据。//         共享内存是所有进程间通信(IPC),速度最快的!不需要过多的拷贝!!(不需要将数据给操作系统)// 结论2: 共享内存缺乏访问控制!会带来并发问题 【如果我想一定程度的访问控制呢? 能】int fd = OpenFIFO(FIFO_NAME, READ);for(;;){Wait(fd);// 临界区printf("%s\n", shmaddr);if(strcmp(shmaddr, "quit") == 0) break;// sleep(1);}// 4. 将指定的共享内存,从自己的地址空间中去关联int n = shmdt(shmaddr);assert(n != -1);(void)n;Log("detach shm done", Debug) << " shmid : " << shmid << endl;// sleep(10);// 5. 删除共享内存,IPC_RMID即便是有进程和当下的shm挂接,依旧删除共享内存n = shmctl(shmid, IPC_RMID, nullptr);assert(n != -1);(void)n;Log("delete shm done", Debug) << " shmid : " << shmid << endl;CloseFifo(fd);return 0;
}

shmClient.cc:

#include "comm.hpp"int main()
{Log("child pid is : ", Debug) << getpid() << endl;key_t k = ftok(PATH_NAME, PROJ_ID);if (k < 0){Log("create key failed", Error) << " client key : " << k << endl;exit(1);}Log("create key done", Debug) << " client key : " << k << endl;// 获取共享内存int shmid = shmget(k, SHM_SIZE, 0);if(shmid < 0){Log("create shm failed", Error) << " client key : " << k << endl;exit(2);}Log("create shm success", Error) << " client key : " << k << endl;// sleep(10);char *shmaddr = (char *)shmat(shmid, nullptr, 0);if(shmaddr == nullptr){Log("attach shm failed", Error) << " client key : " << k << endl;exit(3);}Log("attach shm success", Error) << " client key : " << k << endl;// sleep(10);int fd = OpenFIFO(FIFO_NAME, WRITE);// 使用// client将共享内存看做一个char 类型的bufferwhile(true){ssize_t s = read(0, shmaddr, SHM_SIZE-1);if(s > 0){shmaddr[s-1] = 0;Signal(fd);if(strcmp(shmaddr,"quit") == 0) break;}}CloseFifo(fd);// 去关联int n = shmdt(shmaddr);assert(n != -1);Log("detach shm success", Error) << " client key : " << k << endl;// sleep(10);// client 要不要chmctl删除呢?不需要!!return 0;
}

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

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

相关文章

3D scanner with DLPC3478

https://www.bilibili.com/video/BV1vJ411J7ih?p3&vd_source109fb20ee1f39e5212cd7a443a0286c5 因数&#xff1a; 分别率波长pattern速度 DMD 与 DLPC匹配 3D scanner是结构光的概念走的 Internal pattern, 是DLPC内部提供图像给DMD External Pattern, 外部FPGA /MCU…

查看SQL Server的表字段类型、长度、描述以及是否可为null

文章目录 初步理解小步测试组合一下参考文章有更详细评述 继续理解得到大部分信息 本文参考&#xff1a;https://blog.csdn.net/josjiang1/article/details/80558068。 也可以直接点击这里文章链接&#xff1a; sql server查询表结构&#xff08;字段名&#xff0c;数据类型&a…

借款还款记录账本,借款还款账务处理

生活中&#xff0c;难免会有一些金钱上的往来。有时候&#xff0c;我们会因为忙碌或者其他原因&#xff0c;忘记了借款或还款的具体情况。现在【晨曦记账本】可以帮你可以清楚的掌握自己的借款和还款情况&#xff0c;无论是借款总额、还款总额&#xff0c;还是逾期未还的金额&a…

RT-Thread:STM32 PHY 调试,使用软件包 WIZNET 驱动 W5500

说明&#xff1a; 1. 本文记录使用 RT-Thread 软件包 WIZNET驱动 W5500 的调试笔记。 2. 采用 RT-Thread Studio 工程 STM32F407VET6 芯片&#xff0c;W5500 PHY芯片&#xff0c;两者之间使用SPI接口链接 。 注意&#xff1a; 1.在按流程建立工程&#xff0c;和移植完 wizn…

HCIP OSPF实验

任务&#xff1a; 1.使用三种解决ospf不规则区域的方法 2.路由器5、6、7、8、15使用mgre 3.使用各种优化 4.全网可达 5.保证更新安全 6.使用地址为172.16.0.0/16合理划分 7.每个路由器都有环回 拓扑图&IP划分如下&#xff1a; 第一步&#xff0c;配置IP&环回地址…

SIP-2401VP SIP音频广播模块SIP-2401VP SIP号角音柱音箱解码poe广播播放核心板

SV-2401VP和SV-2403VP网络音频模块是一款通用的独立SIP音频功能模块&#xff0c;可以轻松地嵌入到OEM产品中。该模块对来自网络的SIP协议及RTP音频流进行编解码。 该模块支持多种网络协议和音频编解码协议&#xff0c;可用于VoIP和IP寻呼以及高质量音乐流媒体播放等应用。同时…

centos 7.6 忘记root密码 怎么重置root密码

centos 7.6 忘记root密码 怎么重置root密码 1、 问题描述2、解决方法 1、 问题描述 centos 7.6 忘记root密码&#xff0c;登录不了root用户 2、解决方法 启动系统进入grub界面&#xff0c;按e进入编辑模式&#xff0c;找到含有quiet的这行。在这行最后 添加 rw init/bin/ba…

redis系列:01 数据类型及操作

redis的数据类型有哪些 string,list,set,sorted_set,hash 操作 sting: set name maliao get name exists name expire name 5 ttl name del name setex name 10 maliao 设置key和过期时间 setnx name maliao 当key不存在时才添加list&#xff1a; lpush letter a lpush le…

【Linux】Linux系统编程——ls命令

【Linux】Linux 系统编程——ls 命令 1.命令概述 ls 命令是 Linux 和其他类 Unix 操作系统中最常用的命令之一。ls 命令是英文单词 list 的缩写&#xff0c;正如 list 的意思&#xff0c;ls 命令用于列出文件系统中的文件和目录。使用此命令&#xff0c;用户可以查看目录中的…

关于白盒测试,这些技巧你得游刃有余~

对于很多刚开始学习软件测试的小伙伴来说&#xff0c;如果能尽早将黑盒、白盒测试弄明白&#xff0c;掌握两种测试的结论和基本原理&#xff0c;将对自己后期的学习有较好的帮助。今天&#xff0c;我们就来聊聊黑盒、白盒测试的相关话题。 1、黑盒测试的方法和小结 最常见黑盒…

Proxmox VE 超融合集群销毁Ceph Pool

作者&#xff1a;田逸&#xff08;formyz&#xff09; 销毁Ceph Pool的目的 一套五节点的Proxmox VE超融合集群&#xff0c;当初为有效利用资源&#xff0c;配备了Nvme高性能磁盘和大容量的SATA机械磁盘&#xff08;如图所示&#xff09;&#xff0c;高性能Nvme磁盘用于虚拟机…

作业--day45

定时播放 #include "mywidget.h" #include "ui_mywidget.h"MyWidget::MyWidget(QWidget *parent) :QWidget(parent),ui(new Ui::MyWidget) {ui->setupUi(this);ui->bg_lab->setPixmap(QPixmap(":/pictrue/shanChuan.jpg"));ui->bg_…