文章目录
system V
system V 是一套标准,独立于文件系统之外的,专门为了通信设计出来的模块
让两个毫不相关的进程看到同一份资源
1. 共享内存原理
第一阶段原理
进程A和进程B都通过自己的页表映射到物理内存中的特定区域,进而找到该进程匹配的代码和数据
为了让进程A和进程B通信,前提是两者要看到同一份资源
假设在物理内存上开辟一块空间
进程A和进程B在自己的地址空间中都有自己的共享区
想办法把物理内存中新开辟空间 通过页表 映射到 进程A和进程B的共享区中
把地址空间的起始地址返回给用户
进程A和进程B就可以通过起始的虚拟地址,对应页表访问到内存
就完成了让进程A和进程B看到同一份资源,这份资源就被称为共享内存
第二阶段原理
![](https://img-blog.csdnimg.cn/5502b17ebf7f465882ed4f7bbb1c0c5d.png)
系统中可以用ssh进行通信 ,是不是只能有一对进程使用共享内存呢?
可以,其他进程也可以通信
所以在任何时刻,可能有多个共享内存在被使用
系统中一定会存在很多共享内存同时存在
操作系统要不要整体管理所有的共享内存呢?要
操作性系统如何管理多个共享内存呢?
先描述,在组织
并不是在内存中开辟空间即可,系统为了管理共享内存,构建对应的描述共享内存的结构体对象
共享内存=共享内存的内核数据结构(伪代码:struct shm)+真正开辟的内存空间
2. 直接写代码–编写代码进行原理介绍
打开vscode,创建文件client.cc和server.cc(后缀为cc说明是c++)的文件
创建公共路径 comm.hpp
shmget函数
创建共享路径接口 ,输入 man shmget
查看
申请一个 系统V的共享内存块
如果创建成功,则会返回共享内存标识符,失败返回-1
size代表申请内存块的大小
shmflg代表 选项
有两个最常用的选项,IPC_CREAT IPC_EXCL
转到定义就可以发现其实这两个都是宏
![](https://img-blog.csdnimg.cn/158e33b4777a48c8a9f3d512ab338b8e.png)
若单独使用 IPC_CREAT :创建一个共享内存,如果共享内存不存在,就创建之,如果已经存在,就获取已经存在的共享内存并返回
IPC_EXCL不能单独使用 ,一般都要配合 IPC_CREAT
若要将两个选项同时传进去 IPC_CREAT | IPC_EXCL
两个选项同时用: 创建一个共享内存,如果共享内存不存在,就创建之,如果已经存在,则立马出错返回
如果创建成功,对应的共享内存一定是最新的
获取共享内存时,需要有一个key值
ftok函数
输入 man ftok
![](https://img-blog.csdnimg.cn/0d325f7de31443818c9699252ce210d8.png)
根据路径和项目id进行算法结合,形成一个冲突概率低的key值
失败就返回-1,成功返回key值
key值用法
假设进程A创建了一个共享内存,但是进程B怎么知道那个共享内存是创建的吗?
就需要借助上述提到的 ftok 函数
![](https://img-blog.csdnimg.cn/49abb96e28f24d0e9b95ed095c5f4f87.png)
刚开始约定好 A和B用同样的路径字符串和项目id
借助A形成一个key值,将key值放入A创建的共享内存描述结构体中
此时B也形成一个相同的key值,通过寻找key值来找到A所创建的共享内存
pathname 代表 用户自己设定的路径字符串
proj_id 代表 项目id
key值意义为
让创建共享内存的进程可以给新共享内存设置key值
让获取共享内存的进程 通过key值 去找特定匹配的共享内存
1. 创建key值
![](https://img-blog.csdnimg.cn/e77425d60f69484387fc398f47c938a2.png)
comm.hpp 公共路径中构建一个函数 Getkey 用于返回key值
构建一个函数 tohex,用于将数转换为十六进制
![](https://img-blog.csdnimg.cn/5537743d21a0448487e8e4f3afe305ef.png)
通过server.cc与client.cc中分别调用Getkey 与tohex函数
![](https://img-blog.csdnimg.cn/a58a1a217d7b47999e545a5bc18e2681.png)
两者的返回值key 是相同的,并且返回的都是十六进制数
2. 创建共享内存 获取共享内存
创建共享内存,调用shmget函数,通过两个选项 若共享内存不存在则创建,若存在则报错
而获取共享内存,调用shmget函数,则返回已有的共享内存
![](https://img-blog.csdnimg.cn/c5bfcb71c23d4823b038b5ec4902bc9e.png)
此时运行可执行程序,发现server与client的shmid(共享内存标识符)相同
3. 将自己和共享内存关联起来
输入 man shmat
指令
at代表 关联
将共享内存和目标值关联起来
返回值为 共享内存的虚拟地址的起始地址
我们不知道应该把共享内存放在虚拟空间的什么地址处,所以shmaddr设为NULL让系统自主去选择
shmflg 可以设置为 SHM_RDONLY 表示当前共享内存是只读的 一般设为0,默认为读写的
4. 将自己和共享内存取消关联
输入 man shmdt
指令
shmdt代表 虚拟地址
成功返回0,失败返回-1
![](https://img-blog.csdnimg.cn/813d452a405743f69e618dad1bd0159a.png)
5. 删除共享内存
创建共享内存的进程已经早就退出了,但是共享内存还存在
确认共享内存存在: ipcs
ipc作为进程间通信的简写
ipc表示资源 s表示有多个资源
显出来的为ipc通信系统所支持的三种ipc通信策略
Message Queues 消息队列
Shared Memory Segments 共享内存段
Semaphore Arrays 信号量
ipcs - m
查看共享内存段
perms 代表权限
bytes 代表字节数
nattch 代表 有几个进程和当前进程是关联的
用指令删除
key是在操作系统中使用的,类似于文件的inode编号
shmid 类似于文件的fd
所以删除操作,是在用户层,应该用shmid
ipcrm -m shmid值
就可以删除共享内存
此时就没有 shmid为0的共享内存 存在了
调用系统调用
输入 man shmctl
指令
shmid 代表 共享内存描述符 即想对那个共享内存操作
cmd 代表 选项 即想做什么操作
IPC_STAT 获取当前共享内存的属性
IPC_SET 设置共享内存属性
IPC_RMID 标记这个段被释放
buf 代表 共享内存的属性
![](https://img-blog.csdnimg.cn/87f570f004ba4c0da49499faf81c2b8e.png)
在comm.hpp下 设置删除共享内存的函数,在server.cc中调用函数 即可删除共享内存
完整代码
makefile
.PHONY:all
all:server client
server:server.cc
g++ -o $@ $^
client:client.cc
g++ -o $@ $^
.PHONY:clean
clean:
rm -f server client
如何使两个可执行程序运行,在上一篇文章提到过,点击查看:命名管道
comm.hpp
#ifndef _COMM_HPP_
#define _COMM_HPP_
#include<iostream>
#include<cerrno>
#include<cstdio>
#include<cstring>
#include<stdlib.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<assert.h>
using namespace std;
#define PATHNAME "."//.表示当前路径 路径字符串
#define PROJID 0x6666 //项目id
const int gsize=4096;
key_t getkey()//用于返回key值
{
key_t k=ftok(PATHNAME,PROJID);
if(k==-1)//失败
{
cout<<errno<< " :" <<strerror(errno)<<endl;
exit(1);
}
return k;//返回key值
}
string tohex(int x)//转换为十六进制
{
char buffer[64];
//将x以十六进制的形式传给buffer
snprintf(buffer,sizeof(buffer),"0x%x",x);
return buffer;
}
static int createshmhelper(key_t k,int size,int flag)//static修饰只在本文件有效
{
int shmid=shmget(k,size,flag);
if(shmid==-1)//创建失败
{
cout<<errno<< " :" <<strerror(errno)<<endl;
exit(2);
}
return shmid;//返回共享内存标识符
}
int createshm(key_t k,int size)//创建共享内存
{
//带有两个选项 若不存在则创建,若存在则报错
return createshmhelper(k,size,IPC_CREAT |IPC_EXCL);
}
int getshm(key_t k,int size)
{
//若有共享内存,则返回已有的共享内存
return createshmhelper(k,size,IPC_CREAT );
}
char* attachshm(int shmid)//关联
{
char*start=(char*)shmat(shmid,NULL,0);//对应类型void* 所以需要强转
return start;
}
void detachshm(char*start)//取消关联
{
int n=shmdt(start);
assert(n!=-1);
(void)n;
}
void delshm(int shmid)
{
int n=shmctl(shmid,IPC_RMID,NULL);
assert(n!=-1);//为-1就删除失败
(void)n;
}
#endif
server.cc
#include"comm.hpp"
#include<unistd.h>
int main()
{
//1. 创建key值
key_t k=getkey();//获取key值
cout<<"server:"<<tohex(k)<<endl;
//2.创建共享内存
int shmid=createshm(k,gsize);//返回的是共享内存标识符
cout<<"server shmid:"<<shmid<<endl;//将共享内存标识符转换为十六进制
sleep(5);
// 3.将自己和共享内存关联起来
char*start=attachshm(shmid);
//通信
//4. 将自己和共享内存取消关联
detachshm(start);
//5.删除共享内存
delshm(shmid);
return 0;
}
client.cc
#include"comm.hpp"
int main()
{
key_t k=getkey();//获取key值
cout<<"client key:"<<tohex(k)<<endl;
int shmid=getshm(k,gsize);//获取共享内存
cout<<"client shmid:"<<shmid<<endl;//将共享内存标识符转换为十六进制
char*start=attachshm(shmid);
detachshm(start);
return 0;
}