当前位置: 首页 > ai >正文

System-V 共享内存

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

共享内存概念

共享内存是一种进程间通信(IPC)机制,允许多个进程访问同一块物理内存区域。而这块区域是由操作系统开辟的,因为进程间具有独立性,所以这块内存不是由某个进程创建的。
请添加图片描述

如图所示,多个进程可以同时访问共享内存区域。这意味着不同的进程可以读取和写入相同的数据,从而实现进程间的数据共享和协作。
现在我们就来学习一下如何创建并使用一个共享内存。

创建共享内存—shm

shmget

功能:用来创建共享内存。头文件:<sys/ipc.h><sys/shm.h>
原型:int shmget(key_t key, size_t size, int shmflg);
参数:

  • key: 用于标识要创建或获取的共享内存段。
  1. key是一个数字。它必须在内核中具有唯一性,能够让不同的进程进行唯一性标识。
  2. 第一个进程可以通过key创建共享内存,第二个之后的进程,只要拿着同一个key就可以和第一个进程看到同一个共享内存了;
  3. 对于一个已经创建好的共享内存,key在共享内存的描述对象当中。
  • size:共享内存的大小,单位是字节。
    共享内存的大小是以4kb为基本单位开辟的,也就是4096个字节。所以,设置shm大小的时候最好以4096的整数倍开辟空间。就算你要开辟一个字节大小的空间,那么系统也会给开辟4096个字节,但是你只能使用1个字节的空间,多的无法使用。
  • shmflg:指定共享内存的访问权限和其它选项。shmflg常用的选项有2个,本质是一个位图。
  • IPC_CREAT:如果创建的共享内存不存在,就直接创建;如果存在就直接获取。
  • IPC_EXCL:此标志与 IPC_CREAT一起使用,以确保此调用创建区段。 如果该 segment 已存在,则调用失败。(此标志不可单独使用)
    当然也可以跟文件的读写权限,直接将权限值按照八进制按位或到第三个参数中即可,例如:IPC_CREAT|IPC_EXCL|0666,意思是如果共享内存不存在就直接创建,并且读写权限是0666。

返回值:创建成功的话返回一个int类型,这个整型叫做shmid,用来标识唯一的共享内存;创建失败则返回-1,并设置一个错误码。


ftok

说了这么多,关键点就在于如何获取到key,因此我们还需要了解一个函数ftok。这个函数可以帮助我们生成key
功能:通过一个路径名和项目的唯一id来生成一个System V ipc的key。
头文件:<sys/types.h><sys/ipc.h>
函数原型:key_t ftok(const char *pathname, int proj_id);

  • pathname:路径名称。可以自己指定。
  • proj_id:项目的唯一id。
    返回值:创建成功返回一个key_t类型的值,创建失败返回-1,并设置一个错误码。
    所以只需要传入一个路径和一个数字,ftok就会生成一个key

示例:

#include <iostream>
#include <sys/types.h>
#include <sys/ipc.h>
using namespace std;int main()
{key_t key = ftok("/home/HJW/linux-learning/System_V", 1);cout << key << endl;return 0;
}

请添加图片描述

key值已经被创建出来了,需要注意的是path必须是一个存在的路径,只有这样才能展现出唯一性,ftok会利用path和传入的第二个参数使用某种算法来得到key值。需要进程间通信的双方,只需要事先约定好这个path以及第二个整型,就可以利用ftok产生相同的key值,进而访问同一块共享内存了。


然后我们来对shmget进行使用。

int main()
{key_t key = ftok("/home/HJW/linux-learning/System_V", 10);int shmid = shmget(key, 4096, IPC_CREAT | IPC_EXCL | 0666);cout << key << " " << shmid << endl;return 0;
}

上面的代码中,就是一个简单的创建共享内存的过程,参数就不再进行解释了,直接看结果。
请添加图片描述

我们可以通过ipcs -m来查看建立的共享内存。
可以看到,我们创建的共享内存,key=0x0a010d99转换成16进制就是167841177,shmid=1,拥有者就是自己,初始权限perm=666,大小是4096字节,nattch是连接这个共享内存的连接数,这个我们一会在讲。
建立完成之后,进程已经结束了,但是我们创建的共享内存还在。共享内存的生命周期是随内核的!也就是说用户不主动关闭,共享内存就会一直存在,除非内核重启(用户主动释放)
所以我们想要删除这个共享内存,有2种方式可以删除:指令方式/接口方式。
通过指令方式删除掉话,我们需要使用ipcrm -m shmid的方式,其中shmid就是我们刚才看到的shmid。还要其它的关闭方法,可以自己使用man手册进行查看。
请添加图片描述

如果我们继续执行刚才的代码,可以看到shmid为-1,说明创建失败,所以需要先删除之前创建的共享内存。

shmctl

这个函数的作用是控制共享内存的各种属性。
头文件:<sys/ipc.h>和<sys/shm.h>
函数原型:int shmctl(int shmid, int cmd, struct shmid_ds *buf);

  • shmid:由shmget返回的共享内存标识码
  • cmd:将要采取的动作(有三个可取值)
命令说明
IPC_STATshmid_ds结构中的数据设置为共享内存的当前关联值
IPC_SET在进程有足够权限的前提下,把共享内存的当前关联值设置为shmid_ds数据结构中给出的值
IPC_RMID删除共享内存段
  • buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
    上面的知识点全部围绕着一个展开的,那就是shmid_ds,所以我们先来学习这个结构体。
struct shmid_ds {struct ipc_perm shm_perm;    /* Ownership and permissions */size_t          shm_segsz;   /* Size of segment (bytes) */time_t          shm_atime;   /* Last attach time */time_t          shm_dtime;   /* Last detach time */time_t          shm_ctime;   /* Creation time/time of lastmodification via shmctl() */pid_t           shm_cpid;    /* PID of creator */pid_t           shm_lpid;    /* PID of last shmat(2)/shmdt(2) */shmatt_t        shm_nattch;  /* No. of current attaches */...
};struct ipc_perm {key_t          __key;    /* Key supplied to shmget(2) */uid_t          uid;      /* Effective UID of owner */gid_t          gid;      /* Effective GID of owner */uid_t          cuid;     /* Effective UID of creator */gid_t          cgid;     /* Effective GID of creator */unsigned short mode;     /* Permissions + SHM_DEST andSHM_LOCKED flags */unsigned short __seq;    /* Sequence number */
};

这个结构体中存储的是一个共享内存的基本信息。
返回值:成功时返回0,失败时返回-1,并设置错误码。
下面我就针对第二个参数cmd来进行代码示例:
IPC_STAT

int main()
{key_t key = ftok("/home/HJW/linux-learning/System_V", 10);int shmid = shmget(key, 4096, IPC_CREAT | IPC_EXCL | 0666);struct shmid_ds shm;// 使用 shmctl 函数获取共享内存段的状态信息并存入 shm 结构体中shmctl(shmid, IPC_STAT, &shm);   // 输出共享内存段的上次访问时间(atime)cout << "atime:" << shm.shm_atime << endl;// 输出共享内存段的上次删除时间(dtime)cout << "dtime" << shm.shm_dtime << endl;// 输出共享内存段的上次状态改变时间(ctime)cout << "ctime:" << shm.shm_ctime << endl;// 输出创建共享内存段的进程 ID(cpid)cout << "cpid:" << shm.shm_cpid << endl;return 0;
}

请添加图片描述


IPC_SET

int main()
{key_t key = ftok("/home/HJW/linux-learning/System_V", 10);int shmid = shmget(key, 4096, IPC_CREAT | IPC_EXCL | 0666);struct shmid_ds shm;shmctl(shmid, IPC_STAT, &shm);cout << "atime:" << shm.shm_atime << endl;shm.shm_atime = 6;// 修改shm信息shmctl(shmid, IPC_SET, &shm);// 重新获取shm信息shmctl(shmid, IPC_STAT, &shm);cout << "atime:" << shm.shm_atime << endl;return 0;
}

开始时,我们把共享内存的相关信息存储到了结构体shm当中,然后把结构体的shm_atime设置为6。
第二次通过shmctl把修改后的状态信息写回到共享内存段中,此时的第二个参数就是IPC_SET
第三次调用就把共享内存的信息同步到shm中,最后输出结果,验证是否修改成功。
请添加图片描述


IPC_RMID
这个就是通过接口来删除共享内存段,所以第3个参数我们设置成空指针即可。

int main()
{shmctl(2, IPC_RMID, nullptr);return 0;
}

请添加图片描述

此时我们就成功删除shmid为2的共享内存了。

挂接共享内存

我们讲完了如何创建一个共享内存之后,接下来就要把2个不同的进程挂接起来,这样才能实现2个进程之间的通信。

shmat

功能:将共享内存连接到进程地址空间。
头文件:<sys/types.h>和<sys/shm.h>
函数原型:void *shmat(int shmid, const void *shmaddr, int shmflg);

  • shmid:共享内存的标识id。
  • shmaddr:指定连接的地址。一般来说我们不会主动指定地址,这个参数传入nullptr即可,操作系统会自动的帮我们选择合适的地址连接。
  • shmflg:挂接共享内存的模式。
    传入0,以读写的方式挂接共享内存;传入SHM_RDONLY,以只读的方式挂接;传入SHM_RND时,
    SHM_RND表示连接的地址需是页对齐的整数倍地址。

shmaddr为NULL,核心自动选择一个地址
shmaddr不为NULL且shmflgSHM_RND标记,则以shmaddr为连接地址。
shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。公式:shmaddr -(shmaddr % SHMLBA)
shmflg=SHM_RDONLY,表示连接操作用来只读共享内存

返回值:挂接成功,返回挂接后共享内存的地址;挂接失败。返回(void *)-1
使用shmat连接共享内存后,进程可以像访问普通内存一样访问共享内存段中的数据。

shmdt

既然能挂接,那么也能取消挂接共享内存,只需要使用shmdt接口即可。
功能:将共享内存段与当前进程脱离。
头文件:<sys/types.h><sys/shm.h>
函数原型:int shmdt(const void *shmaddr);
需要把挂接共享内存的地址传入到shmdt中,就可以取消挂接。
返回值:成功返回0;失败返回-1。
注意:将共享内存段与当前进程脱离不等于删除共享内存段。


接下来我们就通过2个进程来实现共享内存的挂接和取消。processa负责发送消息,processb负责接收消息。
processa进程:

#include <iostream>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
using namespace std;int main()
{key_t key = ftok("/home/HJW/linux-learning/System_V", 10);int shmid = shmget(key, 4096, IPC_CREAT | IPC_EXCL | 0666);char *ptr = (char *)shmat(shmid, nullptr, 0);for (int ch = 'A'; ch <= 'Z'; ch++){ptr[ch - 'A'] = ch;sleep(1);}shmctl(shmid, IPC_RMID, nullptr);return 0;
}

processa进程通过ftok生成key,然后使用shmget接口生成一个共享内存,再用shmat将共享内存挂接到进程地址空间,此时ptr指针就指向这个共享内存了,最后在把26个字母写入到共享内存当中,然后使用shmctl关闭共享内存。
processb进程:

#include <iostream>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
using namespace std;int main()
{key_t key = ftok("/home/HJW/linux-learning/System_V", 10);int shmid = shmget(key, 4096, IPC_CREAT);char *ptr = (char *)shmat(shmid, nullptr, 0);while (true){cout << ptr << endl;sleep(5);}return 0;
}

processb进程的ftok参数和另一个进程一样,所以生成的key值都是一样的。随后通过shmget获得共享内存的shmid,这个共享内存是由A维护的,因此B只需要拿到shmid即可,共享内存的创建和销毁都由A来控制。
随后B进程在一个循环中,每隔五秒读取一次共享内存。结果如下:
请添加图片描述

http://www.xdnf.cn/news/4740.html

相关文章:

  • Java流程控制
  • 果汁厂通信革新利器:Ethernet/IP转CANopen协议网关
  • 为什么跨境电商要了解固定IP?常见疑问解析
  • 算法竞赛进阶指南.次小生成树
  • 同比和环比有什么区别?同比和环比的计算方法
  • Oracle OCP认证考试考点详解083系列12
  • RISC-V hardfault分析工具,RTTHREAD-RVBACKTRACE
  • C语言 指针(9)
  • 初学者如何获得WordPress技术支持
  • 模拟内存管理
  • 如何添加二级域名
  • Linux操作系统中的通知机制
  • 单片机 + 图像处理芯片 + TFT彩屏 指示灯控件
  • python小记(十四):Python 中 **参数解包:深入理解与应用实践
  • 【java】oop 结课模拟题版
  • 探索大语言模型(LLM):硅基流动+Cherry studio免费白嫖Qwen3模型
  • librosa.load 容易遇到的采样率问题
  • RISC-V入门资料
  • Pyinstaller编译EXE及反编译
  • 在Postman中高效生成测试接口:从API文档到可执行测试的完整指南
  • Linux下的c/c++开发之操作Sqlite3数据库
  • SpringBoot3 + Druid + DynamicDataSource + PgSQL 连接池优化方案
  • Matlab 镍氢电池模型
  • 流批了,低调使用
  • 巧用python之--模仿PLC(PLC模拟器)
  • C++ STL入门:vecto容器
  • 四川安全员考试的内容包括哪些?
  • 2025年微服务架构关键知识点(一):核心原则与演进趋势
  • Web 架构之高可用基础
  • 基于FPGA的血氧和心率蓝牙监测系统设计-max30102