【Linux】system V 共享内存

2023-10-16 15:34:37 浏览数 (1)

system V

system V 是一套标准,独立于文件系统之外的,专门为了通信设计出来的模块 让两个毫不相关的进程看到同一份资源

1. 共享内存原理

第一阶段原理

进程A和进程B都通过自己的页表映射到物理内存中的特定区域,进而找到该进程匹配的代码和数据 为了让进程A和进程B通信,前提是两者要看到同一份资源 假设在物理内存上开辟一块空间 进程A和进程B在自己的地址空间中都有自己的共享区 想办法把物理内存中新开辟空间 通过页表 映射到 进程A和进程B的共享区中 把地址空间的起始地址返回给用户 进程A和进程B就可以通过起始的虚拟地址,对应页表访问到内存 就完成了让进程A和进程B看到同一份资源,这份资源就被称为共享内存

第二阶段原理

系统中可以用ssh进行通信 ,是不是只能有一对进程使用共享内存呢? 可以,其他进程也可以通信 所以在任何时刻,可能有多个共享内存在被使用 系统中一定会存在很多共享内存同时存在 操作系统要不要整体管理所有的共享内存呢?要 操作性系统如何管理多个共享内存呢? 先描述,在组织 并不是在内存中开辟空间即可,系统为了管理共享内存,构建对应的描述共享内存的结构体对象 共享内存=共享内存的内核数据结构(伪代码:struct shm) 真正开辟的内存空间

2. 直接写代码--编写代码进行原理介绍


打开vscode,创建文件client.cc和server.cc(后缀为cc说明是c )的文件 创建公共路径 comm.hpp

shmget函数

创建共享路径接口 ,输入 man shmget 查看

申请一个 系统V的共享内存块 如果创建成功,则会返回共享内存标识符,失败返回-1


size代表申请内存块的大小 shmflg代表 选项 有两个最常用的选项,IPC_CREAT IPC_EXCL 转到定义就可以发现其实这两个都是宏


若单独使用 IPC_CREAT :创建一个共享内存,如果共享内存不存在,就创建之,如果已经存在,就获取已经存在的共享内存并返回 IPC_EXCL不能单独使用 ,一般都要配合 IPC_CREAT 若要将两个选项同时传进去 IPC_CREAT | IPC_EXCL 两个选项同时用: 创建一个共享内存,如果共享内存不存在,就创建之,如果已经存在,则立马出错返回 如果创建成功,对应的共享内存一定是最新的


获取共享内存时,需要有一个key值

ftok函数

输入 man ftok

根据路径和项目id进行算法结合,形成一个冲突概率低的key值 失败就返回-1,成功返回key值

key值用法

假设进程A创建了一个共享内存,但是进程B怎么知道那个共享内存是创建的吗? 就需要借助上述提到的 ftok 函数


刚开始约定好 A和B用同样的路径字符串和项目id 借助A形成一个key值,将key值放入A创建的共享内存描述结构体中 此时B也形成一个相同的key值,通过寻找key值来找到A所创建的共享内存


pathname 代表 用户自己设定的路径字符串 proj_id 代表 项目id key值意义为 让创建共享内存的进程可以给新共享内存设置key值 让获取共享内存的进程 通过key值 去找特定匹配的共享内存

1. 创建key值

comm.hpp 公共路径中构建一个函数 Getkey 用于返回key值


构建一个函数 tohex,用于将数转换为十六进制


通过server.cc与client.cc中分别调用Getkey 与tohex函数


两者的返回值key 是相同的,并且返回的都是十六进制数

2. 创建共享内存 获取共享内存

创建共享内存,调用shmget函数,通过两个选项 若共享内存不存在则创建,若存在则报错 而获取共享内存,调用shmget函数,则返回已有的共享内存


此时运行可执行程序,发现server与client的shmid(共享内存标识符)相同

3. 将自己和共享内存关联起来

输入 man shmat 指令

at代表 关联 将共享内存和目标值关联起来 返回值为 共享内存的虚拟地址的起始地址 我们不知道应该把共享内存放在虚拟空间的什么地址处,所以shmaddr设为NULL让系统自主去选择 shmflg 可以设置为 SHM_RDONLY 表示当前共享内存是只读的 一般设为0,默认为读写的


4. 将自己和共享内存取消关联

输入 man shmdt 指令

shmdt代表 虚拟地址 成功返回0,失败返回-1

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 代表 共享内存的属性

在comm.hpp下 设置删除共享内存的函数,在server.cc中调用函数 即可删除共享内存

完整代码

makefile
代码语言:javascript复制
.PHONY:all
all:server client

server:server.cc
	g   -o $@ $^
client:client.cc
	g   -o $@ $^
.PHONY:clean
clean:
	rm -f server client 

如何使两个可执行程序运行,在上一篇文章提到过,点击查看:命名管道


comm.hpp
代码语言:javascript复制
#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
代码语言:javascript复制
#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
代码语言:javascript复制
#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;
}

0 人点赞