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如何查看文件权限的命令

《Linux如何查看文件权限的命令》Linux中使用ls-R命令递归查看指定目录及子目录下所有文件和文件夹的权限信息,以列表形式展示权限位、所有者、组等详细内容... 目录linux China编程查看文件权限命令输出结果示例这里是查看tomcat文件夹总结Linux 查看文件权限命令ls -l 文件或文件夹

idea的终端(Terminal)cmd的命令换成linux的命令详解

《idea的终端(Terminal)cmd的命令换成linux的命令详解》本文介绍IDEA配置Git的步骤:安装Git、修改终端设置并重启IDEA,强调顺序,作为个人经验分享,希望提供参考并支持脚本之... 目录一编程、设置前二、前置条件三、android设置四、设置后总结一、php设置前二、前置条件

Debian系和Redhat系防火墙配置方式

《Debian系和Redhat系防火墙配置方式》文章对比了Debian系UFW和Redhat系Firewalld防火墙的安装、启用禁用、端口管理、规则查看及注意事项,强调SSH端口需开放、规则持久化,... 目录Debian系UFW防火墙1. 安装2. 启用与禁用3. 基本命令4. 注意事项5. 示例配置R

最新Spring Security的基于内存用户认证方式

《最新SpringSecurity的基于内存用户认证方式》本文讲解SpringSecurity内存认证配置,适用于开发、测试等场景,通过代码创建用户及权限管理,支持密码加密,虽简单但不持久化,生产环... 目录1. 前言2. 因何选择内存认证?3. 基础配置实战❶ 创建Spring Security配置文件

Python获取浏览器Cookies的四种方式小结

《Python获取浏览器Cookies的四种方式小结》在进行Web应用程序测试和开发时,获取浏览器Cookies是一项重要任务,本文我们介绍四种用Python获取浏览器Cookies的方式,具有一定的... 目录什么是 Cookie?1.使用Selenium库获取浏览器Cookies2.使用浏览器开发者工具

Linux系统中查询JDK安装目录的几种常用方法

《Linux系统中查询JDK安装目录的几种常用方法》:本文主要介绍Linux系统中查询JDK安装目录的几种常用方法,方法分别是通过update-alternatives、Java命令、环境变量及目... 目录方法 1:通过update-alternatives查询(推荐)方法 2:检查所有已安装的 JDK方

Linux系统之lvcreate命令使用解读

《Linux系统之lvcreate命令使用解读》lvcreate是LVM中创建逻辑卷的核心命令,支持线性、条带化、RAID、镜像、快照、瘦池和缓存池等多种类型,实现灵活存储资源管理,需注意空间分配、R... 目录lvcreate命令详解一、命令概述二、语法格式三、核心功能四、选项详解五、使用示例1. 创建逻

Java获取当前时间String类型和Date类型方式

《Java获取当前时间String类型和Date类型方式》:本文主要介绍Java获取当前时间String类型和Date类型方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,... 目录Java获取当前时间String和Date类型String类型和Date类型输出结果总结Java获取

Linux下在线安装启动VNC教程

《Linux下在线安装启动VNC教程》本文指导在CentOS7上在线安装VNC,包含安装、配置密码、启动/停止、清理重启步骤及注意事项,强调需安装VNC桌面以避免黑屏,并解决端口冲突和目录权限问题... 目录描述安装VNC安装 VNC 桌面可能遇到的问题总结描js述linux中的VNC就类似于Window

linux下shell脚本启动jar包实现过程

《linux下shell脚本启动jar包实现过程》确保APP_NAME和LOG_FILE位于目录内,首次启动前需手动创建log文件夹,否则报错,此为个人经验,供参考,欢迎支持脚本之家... 目录linux下shell脚本启动jar包样例1样例2总结linux下shell脚本启动jar包样例1#!/bin