进程间的通信(二)共享内存
什么是共享内存
共享内存(Shared Memory)是指两个或多个进程共享一个给定的存储区。换句话说,共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。
大致原理如下图
使用共享内存的基本步骤
1,创建共享内存
首先,需要在系统中创建一块共享内存区域。这通常通过系统调用(如shmget在Linux系统中)完成。
功能:用来创建共享内存
头文件
#include <sys/ipc.h>
#include <sys/shm.h>
原型
int shmget(key_t key, size_t size, int shmflg);
参数
key:这个共享内存段名字
size:共享内存大小
shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1
这里我们详细谈谈着第一个和第三个参数
key参数
1.key是一个数字,这个数字是几,不重要。关键在于它必须在内核中具有唯一性,能够让不同的进程进行唯一性标识
⒉第一个进程可以通过key创建共享内存,第二个之后的进程,只要拿着同一个key就可以和第一个进程看到同一个共享内存了
3.对于一个已经创建好的共享内存,key在哪? key在共享内存的描述对象中!
4.第一次创建的时候,必须有一个key了。
5.key —类似----路径----唯一
那么如何在Linux得到一个唯一的key值呢
一般我们使用ftok函数
该函数内部一套独有的算法,通过给的pathname和proj_id进行计算
功能:生成一个独有的数
头文件
#include <sys/types.h>
#include <sys/ipc.h>
原型
key_t ftok(const char *pathname, int proj_id);
参数
pathname:传入一个路径(一般是当前路径“ . ”)
proj_id: 随便填写一个数(要做通信的话通信的另外一端要与这个数保持一致才能找到对应的icpID)
返回值:成功返回一个独有的数,失败则返回-1
shmflg参数
该参数常用如下
IPC_CREAT(单独):如果你申请的共享内存不存在,就创建,存在,就获取并返回
IPC_CREAT | IPC_EXCL:如果你申请的共享内存不存在,就创建,存在,就出错返回、确保,如果我们申请成功了一个共享内存,这个共享内存一定是一个新的!
lPC_EXCL:不单独使用!
2,映射共享内存
接下来,每个需要访问共享内存的进程都需要将这块内存映射到自己的虚拟地址空间中。通过系统调用(如shmat)实现。
功能:将共享内存段连接到进程地址空间
头文件
#include <sys/types.h>
#include <sys/shm.h>
原型
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数
shmid: 由shmget返回的共享内存标识码
shmaddr:指定连接的地址
shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回一个指针,指向共享内存第一个节,相当于返回共享内存的地址;失败返回-1
shmaddr参数
使用说明如下
shmaddr为NULL,核心自动选择一个地址
shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址。
shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。
shmflg
SHM_RND表示读写共享内存
SHM_RDONLY表示连接操作用来只读共享内存
3,访问和修改共享内存
一旦映射成功,进程就可以像访问普通内存一样读写共享内存中的数据。由于多个进程访问的是同一块物理内存,因此一个进程对共享内存的修改会立即对其他进程可见。
4,解除映射
当进程不再需要访问共享内存时,应该解除映射,以释放其在虚拟地址空间中的占用。这通过系统调用(如shmdt)实现。
功能:将共享内存段与当前进程脱离
头文件
#include <sys/types.h>
#include <sys/shm.h>
原型
int shmdt(const void *shmaddr);
参数
shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段
5,删除共享内存
最后,当所有进程都不再需要这块共享内存时,应该删除它,以释放系统资源。这通常通过系统调用(如shmctl配合IPC_RMID命令)完成。
功能:用于控制共享内存
头文件
#include <sys/types.h>
#include <sys/shm.h>
原型
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作(有三个可取值)
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1
cmd参数
使用说明如下
buf参数
实际上buf是一个指向shmid_ds的结构体指针
而shmid_ds结构体如下
通过buf指针访问shmid_ds结构体可以查看共享内存的属性
测试代码如下(未完整)
comm.hpp
// 共享内存的大小一般建议是4096的整数倍
// 4097,实际上操作系统给你的是4096*2的大小
const int size = 4096;
const string pathname="/home/whb";
const int proj_id = 0x6666;
//获得唯一key
key_t GetKey()
{
key_t k = ftok(pathname.c_str(), proj_id);
return k;
}
//创建一块共享内存区域
int GetShareMemHelper(int flag)
{
key_t k = GetKey();
int shmid = shmget(k, size, flag);
return shmid;
}
//申请新的共享内存
int CreateShm()
{
return GetShareMemHelper(IPC_CREAT | IPC_EXCL);
}
processa.cc
#include"comm.hpp"
int main()
{
//创建共享内存
int shmid = CreateShm();
//挂接到自己的地址空间中
char *shmaddr = (char*)shmat(shmid, nullptr, 0);
struct shmid_ds shmds;
while(true)
{
//直接访问共享内存
cout << "client say@ " << shmaddr << endl;
//把shmid ds结构中的数据设置为共享内存的当前关联值
shmctl(shmid, IPC_STAT, &shmds);
//访问shmid ds结构体属性
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(shmid, IPC_RMID, nullptr);
close(fd);
return 0;
}
processb.cc
#include "comm.hpp"
int main()
{
//创建共享内存
int shmid = CreateShm();
//挂接到自己的地址空间中
char *shmaddr = (char*)shmat(shmid, nullptr, 0);
// 一旦有了共享内存,挂接到自己的地址空间中,你直接把他当成你的内存空间来用即可!
// 不需要调用系统调用
while(true)
{
cout << "Please Enter@ ";
fgets(shmaddr, 4096, stdin);//从键盘中向共享内存中输入消息
write(fd, "c", 1); // 通知对方
}
//将共享内存段与当前进程脱离
shmdt(shmaddr);
close(fd);
return 0;
}
使用共享内存注意的问题
•同步与互斥:共享内存不提供任何同步或互斥机制,因此必须由程序员自行实现。常见的同步机制包括信号量、互斥锁等,用于确保多个进程在访问共享内存时不会发生冲突。
•错误处理:在使用共享内存时,必须妥善处理各种可能的错误情况,如内存分配失败、映射失败等。这通常涉及检查系统调用的返回值,并采取相应的错误处理措施。
•内存管理:共享内存的生命周期管理也很重要。必须确保在不再需要时及时删除共享内存,以避免资源浪费和潜在的安全问题。