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

相关文章

Linux join命令的使用及说明

《Linuxjoin命令的使用及说明》`join`命令用于在Linux中按字段将两个文件进行连接,类似于SQL的JOIN,它需要两个文件按用于匹配的字段排序,并且第一个文件的换行符必须是LF,`jo... 目录一. 基本语法二. 数据准备三. 指定文件的连接key四.-a输出指定文件的所有行五.-o指定输出

Linux jq命令的使用解读

《Linuxjq命令的使用解读》jq是一个强大的命令行工具,用于处理JSON数据,它可以用来查看、过滤、修改、格式化JSON数据,通过使用各种选项和过滤器,可以实现复杂的JSON处理任务... 目录一. 简介二. 选项2.1.2.2-c2.3-r2.4-R三. 字段提取3.1 普通字段3.2 数组字段四.

Linux kill正在执行的后台任务 kill进程组使用详解

《Linuxkill正在执行的后台任务kill进程组使用详解》文章介绍了两个脚本的功能和区别,以及执行这些脚本时遇到的进程管理问题,通过查看进程树、使用`kill`命令和`lsof`命令,分析了子... 目录零. 用到的命令一. 待执行的脚本二. 执行含子进程的脚本,并kill2.1 进程查看2.2 遇到的

Java AOP面向切面编程的概念和实现方式

《JavaAOP面向切面编程的概念和实现方式》AOP是面向切面编程,通过动态代理将横切关注点(如日志、事务)与核心业务逻辑分离,提升代码复用性和可维护性,本文给大家介绍JavaAOP面向切面编程的概... 目录一、AOP 是什么?二、AOP 的核心概念与实现方式核心概念实现方式三、Spring AOP 的关

Linux云服务器手动配置DNS的方法步骤

《Linux云服务器手动配置DNS的方法步骤》在Linux云服务器上手动配置DNS(域名系统)是确保服务器能够正常解析域名的重要步骤,以下是详细的配置方法,包括系统文件的修改和常见问题的解决方案,需要... 目录1. 为什么需要手动配置 DNS?2. 手动配置 DNS 的方法方法 1:修改 /etc/res

Linux创建服务使用systemctl管理详解

《Linux创建服务使用systemctl管理详解》文章指导在Linux中创建systemd服务,设置文件权限为所有者读写、其他只读,重新加载配置,启动服务并检查状态,确保服务正常运行,关键步骤包括权... 目录创建服务 /usr/lib/systemd/system/设置服务文件权限:所有者读写js,其他

Linux下利用select实现串口数据读取过程

《Linux下利用select实现串口数据读取过程》文章介绍Linux中使用select、poll或epoll实现串口数据读取,通过I/O多路复用机制在数据到达时触发读取,避免持续轮询,示例代码展示设... 目录示例代码(使用select实现)代码解释总结在 linux 系统里,我们可以借助 select、

Linux挂载linux/Windows共享目录实现方式

《Linux挂载linux/Windows共享目录实现方式》:本文主要介绍Linux挂载linux/Windows共享目录实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录文件共享协议linux环境作为服务端(NFS)在服务器端安装 NFS创建要共享的目录修改 NFS 配

Vue3视频播放组件 vue3-video-play使用方式

《Vue3视频播放组件vue3-video-play使用方式》vue3-video-play是Vue3的视频播放组件,基于原生video标签开发,支持MP4和HLS流,提供全局/局部引入方式,可监听... 目录一、安装二、全局引入三、局部引入四、基本使用五、事件监听六、播放 HLS 流七、更多功能总结在 v

linux系统中java的cacerts的优先级详解

《linux系统中java的cacerts的优先级详解》文章讲解了Java信任库(cacerts)的优先级与管理方式,指出JDK自带的cacerts默认优先级更高,系统级cacerts需手动同步或显式... 目录Java 默认使用哪个?如何检查当前使用的信任库?简要了解Java的信任库总结了解 Java 信