Linux之systemV共享内存方式

2025-04-29 17:50

本文主要是介绍Linux之systemV共享内存方式,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《Linux之systemV共享内存方式》:本文主要介绍Linux之systemV共享内存方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教...

一、工作原理

Linux之systemV共享内存方式

操作系统在物理内存上申请一块空间,然后将申请到的空间通过页表映射到进程地址空间mm_struct的共享区中,然后返回虚拟地址供程序使用,如果多个进程申请的是同一块物理空间,那么它们就可以进行通信由于同一时间可能有多组进程进行通信,所以系统当中可能存在多个共享内存块,所以操作系统要把这些内存管理起来,所以内核中会有一个结构体来描述共享内存

二、系统调用接口

1、申请共享内存

(一)key的获取

#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
  • 返回值:成功ftok 会将proj_idpathname对应的文件信息结合起来生成最终的key值,失败返回-1
  • pathname:已经存在的文件或目录的路径名
  • proj_id:由用户指定的非零整数,通常是一个单http://www.chinasem.cnhttp://www.chinasem.cn字符(因为只有低 8 位会被使用),

只要这两个才参数一致,那么生成的key值就一定一致

key值是我们少见的由我们自己传参生成的固定的值,linux在涉及内存方面的时候,通常是操作系统代为处理,这里其实是因为假设我们有两个进程AB,操作系统生成一个key给到A,但是B也需要这个key,但是操作系统是不知道哪两个进程要建立信道进行通信的,只有程序员知道我们哪两个进程要建立信道进行通信,因为在建立信道之前进程之间是相互独立的,所以就不能由操作系统来分配key

(二)共享内存的申请

shmget函数的主要作用是在内核中创建或获取共享内存段的标识符

#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);

返回值:成功返回一个非负整数,即该共享内存段的标识码(shmid),失败返回-1

  • key:key是一个数字,它是不同共享内存让自己具备唯一性的标识
  • size:共享内存大小,单位为字节,一般最好是4096的整数倍,它按照页申请,即需要4097字节就申请4096*2个字节

shmflg是一个标识符

shmflg含义作用
IPC_CREAT创建标志如果指定的共享内存段不存在,就创建一个新的共享内存段;若已存在,则直接返回其标识符,常用于在不确定共享内存是否存在时创建或获取它
IPC_EXCL排他标志需和 IPC_CREAT 一起使用,若共享内存已存在,shmget 调用失败,errno 设为 EEXIST,可确保新创建共享内存
IPC_NOWAIT非阻塞标志当操作不能立即完成时,不阻塞调用进程,直接返回错误,例如在获取共享内存时,若当前资源无法立即获取,不等待而直接返回错误
SHM_HUGETLB大页标志尝试使用大页内存分配共享内存段,大页内存可减少页表项数量,降低内存管理开销,提高系统性能,适合处理大量数据
SHM_NORESERVE不保留交换空间标志不预先为共享内存段保留交换空间,正常创建共享内存时,系统会预留交换空间以防内存不足,使用此标志可节省交换空间,但可能导致内存紧张时出现页面交换问题
权限标志(如 0600、0666 等)权限设置类似文件权限设置,用于规定共享内存段的访问权限,0600 表示只有所有者有读写权限,0666 表示所有用户都有读写权限

2、将共享内存段连接到进程地址空间

shmat函数的核心作用是在调用进程的虚拟地址空间和共享内存段的物理内存之间建立映射关系,在调用shmget函数时,虽然创建或获取了共享内存段的标识符,但进程还不能直接访问该共享内存,只有通过shmat函数将共享内存段附加到进程的地址空间后,进程才能像访问普通内存一样访问共享内存段中的数据

#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
  • 返回值:成功返回一个指针,指向共享内存的起始地址,失败返回 -1
  • shmid:共享内存段的标识符,用于指定要附加的共享内存段,即shmChina编程get的返回值
  • shmaddr:指定共享内存段要附加到的进程地址空间的地址,如果为 NULL,则由系统自动选择合适的地址
  • shmflg:标识符
类型含义作用
SHM_RDONLY共享内存只读以只读模式将共享内存段附加到进程的地址空间,进程只能读取共享内存中的数据,不能进行写操作,增强数据安全性,适用于多进程共享只读数据的场景
SHM_RND地址舍入当 shmaddr 参数不为 NULL 时,将 shmaddr 向下舍入到一个合适的内存边界(通常是系统页面大小的整数倍),保证共享内存的正确附加
SHM_REMAP重新映射如果共享内存段已经被附加到进程的地址空间,使用此标志可以重新映射该共享内存段,常用于更新共享内存的映射关系
SHM_EXEC可执行权限允许在共享内存段上执行程序指令,不过并非所有系统都支持该标志,适用于需要在共享内存中执行代码的特殊场景
SHM_COPY创建私有副本某些系统支持该标志,尝试创建共享内存段的一个私有副本,后续对该副本的修改不会影响其他进程看到的共享内存内容,用于实现进程对共享内存的独立修改
SHM_ANON匿名共享内存部分系统支持该标志,用于创建匿名共享内存段,此时 shmid 参数会被忽略,可结合 shmaddr 使用,常用于父子进程间的内存共享

3、将内存共享段与当前进程脱离

当进程调用shmat函数将共享内存段附加到自己的地址空间后,系统会在进程的虚拟地址空间和共享内存段的物理内存之间建立映射关系,使得进程可以像访问普通内存一样访问共享内存,而shmdt函数的核心作用就是解除这种映射关系

#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
  • 返回值:成功返回0,失败返回-1
  • shmaddrshmat返回的指针

4、控制共享内存

通过cmd控制共享内存

#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
  • 返回值:成功返回0,失败返回-1
  • shmid:同上
  • cmd:将要采取的动作
类型含义作用
IPC_STAT获取状态信息将与 shmid 关联的共享内存段的当前状态信息复制到 buf 指向的 struct shmid_ds 结构体中,这个结构体包含了如共享内存段的大小、所有者、权限、创建时间、最后访问时间等信息
IPC_SET设置状态信息使用 buf 指向的 struct shmid_ds 结构体中的值来更新与 shmid 关联的共享内存段的部分状态信息,可以更新的信息包括共享内存段的所有者、权限等
IPC_RMID删除共享内存段标记与 shmid 关联的共享内存段为删除状态,当最后一个使用该共享内存段的进程分离它之后,系统会真正释放该共享内存段所占用的资源,此时 buf 参数会被忽略,通常传递 NULL
IPC_INFO获取系统共享内存信息获取系统范围内的共享内存资源信息,这些信息会被存储在一个由系统定义的特定结构体中(通常不是 struct shmid_ds),buf 应指向该结构体,用于接收信息
SHM_INFO获取共享内存段信息获取系统中共享内存段的相关统计信息,返回一个包含这些统计信息的结构体,同样,buf 要指向合适的结构体来接收数据
SHM_STAT通过索引获取共享内存状态类似于 IPC_STAT,但不是通过 shmid 来指定共享内存段,而是通过一个索引值,可以通过这种方式遍历系统中的共享内存段
  • buf:指向一个保存着共享内存的模式状态和访问权限的结构体struct shmid_ds

三、开始通信

由于共享内存通信没办法做到同步和互斥,我们通过加入命名管道的方式来形成同步与互斥的效果,普通的共享内存通信就调用完接口直接写直接读就行,比较简单,因为共享内存是可以通过指针直接读的,所以我们升级一下

1、comm.hpp

#ifndef __COMM_HPP__
#define __COMM_HPP__
China编程
#include <IOStream>
#include <string>
#include <cstdlib>
#include <cstring>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/types.h>
#include <sys/stat.h>

#define FIFO_FILE "./myfifo"
#define MODE 0664

enum
{
    FIFO_CREATE_ERR = 1,
    FIFO_DELETE_ERR,
    FIFO_OPEN_ERR,
    FIFO_READ_ERR
};

using namespace std;

Log log;

const int size = 4096; 
const string pathname="/home/slm";
const int proj_id = 0x6667;

//封装一个获取Key值的函数
key_t GetKey()
{
	//使用ftok函数获取key值
    key_t k = ftok(pathname.c_str(), proj_id);
    if(k < 0)
    {
        exit(1);
    }
    //返回生成的key值
    return k;
}
//封装一个建立共享内存区的函数
int GetShareMemHelper(int flag)
{
	//调用获取key值,然后在物理内存上申请共享内存
    key_t k = GetKey();
    int shmid = shmget(k, size, flag);
    if(shmid < 0)
    {
        exit(2);
    }
	//返回shmid
    return shmid;
}
//用于创建一个新的共享内存段,如果指定的共享内存段已经存在,函数会失败
int CreateShm()
{
    return GetShareMemHelper(IPC_CREAT | IPC_EXCL | 0666);
}
//用于获取一个共享内存段,如果指定的共享内存段不存在,函数会创建一个新的共享内存段;如果已经存在,则直接返回该共享内存段的标识符
int GetShm()
{
    return GetShareMemHelper(IPC_CREAT); 
}

class Init
{
public:
    Init()
    {
        // 创建管道
        int n = mkfifo(FIFO_FILE, MODE);
        if (n == -1)
        {
            perror("mkfifo");
            exit(FIFO_CREATE_ERR);
        }
    }
    ~Init()
    {
		// 销毁管道
        int m = unlink(FIFO_FILE);
        if (m == -1)
        {
            perror("unlink");
            exit(FIFO_DELETE_ERR);
        }
    }
};
#endif

2、processa.cpp

#include "comm.hpp"

extern Log log;

int main()
{
	//创建管道
    Init init;
    //调用 CreateShm 函数创建一个共享内存段,并返回该共享内存段的标识符shmid
    int shmid = CreateShm();
    //调用 shmat 函数将共享内存段附加到当前进程的地址空间,shmid 是共享内存段的标识符
    //nullptr 表示让系统自动选择附加地址,0 表示默认附加标志
    //返回值是共享内存段在进程地址空间中的起始地址,将其强制转换为char*类型并赋值给shmaddr
    char *shmaddr = (char*)shmat(shmid, nullptr, 0);
    //以只读模式打开命名管道文件FIFO_FILE,由于是以只读模式打开,该操作会阻塞
    //直到有其他进程以写模式打开同一个命名管道
    int fd = open(FIFO_FILE, O_RDONLY); 
    if (fd < 0)
    {
        exit(FIFO_OPEN_ERR);
    }
    //定义shmds用于存储共享内存段的状态信息
    struct shmid_ds shmds;
    while(true)
    {
	//定义一个字符变量c,调用read函数从命名管道文件描述符fd中读取1个字节的数据到c中
	//并将实际读取的字节数存储在s中,如果能读取到说明通信进程给我们发信号了,有内容
	//那我们就读取共享内存部分
        char c;
        ssize_t s = read(fd, &c, 1);
        if(s == 0) break;
        else if(s < 0) 
        {
            exit(FIFO_READ_ERR);
        }
		//可以直接通过指针访问共享内存
        cout << "client say@ " << shmaddr << endl; 
        sleep(1);
		//调用shmctl函数,使用IPC_STAT命令获取共享内存段的状态信息,并将其存储在shmds结构体中
		//然后将结构体shmds部分信息打印出来
        shmctl(shmid, IPC_STAT, &shmds);
    python    cout << "shm size: " << shmds.shm_segsz << endl;
        cout << "shm nattch: " << shmds.shm_nattch << endl;
        printf("shm key: 0x%x\n",  shmds.shm_perm.__key);
        cout << "shm mode: " << shmds.shm_perm.mode << endl;
    }
	//取消链接
    shmdt(shmaddr);
    //调用shmctl函数,使用IPC_RMID命令将共享内存段标记为待删除状态
    //当所有附加进程都分离后,系统会自动删除该共享内存段
    shmctl(shmid, IPC_RMID, nullptr);
    close(fd);
    return 0;
}

3、processb.cpp

#include "comm.hpp"

int main()
{
	//调用GetShm函数获取一个共享内存段的标识符shmid
	//在参数相同、无特殊情况的情况下,shmid也相同
    int shmid = GetShm();
    char *shmaddr = (char*)shmat(shmid, nullptr, 0);

    int fd = open(FIFO_FILE, O_WRONLY); 
    if (fd < 0)
    {
        exit(FIFO_OPEN_ERR);
    }
    // 一旦有了共享内存,挂接到自己的地址空间中,直接把他当成你的内存空间来用即可
    // 不需要调用系统调用接口
    while(true)
    {
        cout << "Please Enter@ ";
        fgets(shmaddr, 4096, stdin);
        //在管道写入一个字符来通知服务器共享内存有新数据
        write(fd, "c", 1); 
    }
	//调用shmdt函数将共享内存段从当前进程的地址空间分离,释放相关的资源
    shmdt(shmaddr);
    close(fd);
    return 0;
}

Linux之systemV共享内存方式

四、其他问题

key是在操作系统内标定共享内存唯一性的,而shmid是在进程内标定资源唯一性的,二者虽然都是标定唯一性,但是使用范围不同,并且虽然共享内存属于文件系统,但是shmid和文件描述符兼容性不好,共享内存这方面单独搞了一套类似于文件描述符表的规则

共享内存的生命周期随内核,除非用户释放或者内核重启,共享内存才会释放

ipcs -m命令可以查看操作系统中所有的共享内存,其中perms是权限位,nattch是和当前共享内存关联的进程个数

Linux之systemV共享内存方式

共享内存是没有存在同步互斥这样的保护机制的,它是所有进程通信方式中最快的,因为相较其他方式,它的拷贝更少

共享内存内部的数据由用户自己维护

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程China编程(www.chinasem.cn)。

这篇关于Linux之systemV共享内存方式的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1154430

相关文章

SpringBoot中@Value注入静态变量方式

《SpringBoot中@Value注入静态变量方式》SpringBoot中静态变量无法直接用@Value注入,需通过setter方法,@Value(${})从属性文件获取值,@Value(#{})用... 目录项目场景解决方案注解说明1、@Value("${}")使用示例2、@Value("#{}"php

SpringBoot分段处理List集合多线程批量插入数据方式

《SpringBoot分段处理List集合多线程批量插入数据方式》文章介绍如何处理大数据量List批量插入数据库的优化方案:通过拆分List并分配独立线程处理,结合Spring线程池与异步方法提升效率... 目录项目场景解决方案1.实体类2.Mapper3.spring容器注入线程池bejsan对象4.创建

防止Linux rm命令误操作的多场景防护方案与实践

《防止Linuxrm命令误操作的多场景防护方案与实践》在Linux系统中,rm命令是删除文件和目录的高效工具,但一旦误操作,如执行rm-rf/或rm-rf/*,极易导致系统数据灾难,本文针对不同场景... 目录引言理解 rm 命令及误操作风险rm 命令基础常见误操作案例防护方案使用 rm编程 别名及安全删除

Linux下MySQL数据库定时备份脚本与Crontab配置教学

《Linux下MySQL数据库定时备份脚本与Crontab配置教学》在生产环境中,数据库是核心资产之一,定期备份数据库可以有效防止意外数据丢失,本文将分享一份MySQL定时备份脚本,并讲解如何通过cr... 目录备份脚本详解脚本功能说明授权与可执行权限使用 Crontab 定时执行编辑 Crontab添加定

使用docker搭建嵌入式Linux开发环境

《使用docker搭建嵌入式Linux开发环境》本文主要介绍了使用docker搭建嵌入式Linux开发环境,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面... 目录1、前言2、安装docker3、编写容器管理脚本4、创建容器1、前言在日常开发全志、rk等不同

HTTP 与 SpringBoot 参数提交与接收协议方式

《HTTP与SpringBoot参数提交与接收协议方式》HTTP参数提交方式包括URL查询、表单、JSON/XML、路径变量、头部、Cookie、GraphQL、WebSocket和SSE,依据... 目录HTTP 协议支持多种参数提交方式,主要取决于请求方法(Method)和内容类型(Content-Ty

linux系统上安装JDK8全过程

《linux系统上安装JDK8全过程》文章介绍安装JDK的必要性及Linux下JDK8的安装步骤,包括卸载旧版本、下载解压、配置环境变量等,强调开发需JDK,运行可选JRE,现JDK已集成JRE... 目录为什么要安装jdk?1.查看linux系统是否有自带的jdk:2.下载jdk压缩包2.解压3.配置环境

使用shardingsphere实现mysql数据库分片方式

《使用shardingsphere实现mysql数据库分片方式》本文介绍如何使用ShardingSphere-JDBC在SpringBoot中实现MySQL水平分库,涵盖分片策略、路由算法及零侵入配置... 目录一、ShardingSphere 简介1.1 对比1.2 核心概念1.3 Sharding-Sp

Linux搭建ftp服务器的步骤

《Linux搭建ftp服务器的步骤》本文给大家分享Linux搭建ftp服务器的步骤,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录ftp搭建1:下载vsftpd工具2:下载客户端工具3:进入配置文件目录vsftpd.conf配置文件4:

Spring创建Bean的八种主要方式详解

《Spring创建Bean的八种主要方式详解》Spring(尤其是SpringBoot)提供了多种方式来让容器创建和管理Bean,@Component、@Configuration+@Bean、@En... 目录引言一、Spring 创建 Bean 的 8 种主要方式1. @Component 及其衍生注解