共享内存
原理与概念
两个进程的PCB创建虚拟地址空间然后映射到物理内存中,每个进程因为是独立的,所以在物理内存中的地址也不同。
那么共享内存是怎么做到的呢?
首先先在物理内存中申请一块内存。
然后讲这块内存通过页表映射分别映射到这两个进程的虚拟地址空间内,让这两个进程都能看到这块内存。(这里也称为进程和共享内存挂接)
最后如果不想通信了:
取消进程和内存的映射关系(去关联)
释放内存(释放共享内存)
理解:
a.这里和原本C语言当中的maclloc函数开辟空间不同,我们的目的是要让两个进程同时看到这块内存。
b.进程通信的这个申请一块共享内存是专门设计出来的,用来IPC。
c.共享内存是一种通信的方式,所有想通信的进程都可以用。
d.OS一定可能会存在很多的共享内存。
概念就是:通过让不同进程看到同一个内存块的方式就叫做共享内存。
函数接口的介绍与使用
shmget
创建共享内存接口:
首先来看第三个参数:
这里是通过位图的方式(二进制标志位)传参。
IPC_CREAT 如果不存在,创建,如果存在,就获取共享内存的位置。
IPC_EXCL 这个选项无法单独使用,必须结合IPC_CREAT使用,一起使用代表的含义是,如果不存在就创建,存在就会返回错误。(也就是说如果创建成功,他一定是一个新的共享内存——shm)
第二个参数是创建shm的大小。
返回值:
如果成功就返回一个共享内存的合法标识符,失败就返回-1。(这个和文件操作符完全不同,不是一个体系)
第一个参数:
这个是让多个进程看到同一份shm的关键。能进行唯一标识。
这个值是怎么来的呢?
用这个函数生成:
将一个合法路径(字符串)和字符数据通过某种算法组合来进行计算出key值,然后返回key。
失败了返回-1。
那么,怎么样才能让两个进程看到同一份共享内存呢?
在两个进程中如果传入到ftok中的两个参数相同,返回的key也相同,其中一个进程通过shmget接口创建共享内存,另一个接口通过shmget接口接收共享内存的位置,这样两个进程就能看到同一份资源了。
我们要利用接口让两个进程实现通信,首先创建两个.cc的文件,一个头文件.hpp。
因为两个进程都要创建/获取共享内存,所以获取key等等操作在头文件更方便。
#ifndef _COMM_HPP_ #define _COMM_HPP_ #include<iostream> #include<cassert> #include<cstring> #include<cerrno> #include<sys/ipc.h> #include<sys/shm.h> #include<cstdlib> #define PATHNAME "."//ftok的第一个参数,是一个合法路径 #define PROJ_IO 0X666//ftok的第二个参数 key_t getkey() { key_t k = ftok(PATHNAME, PROJ_IO); if(k < 0) { std::cerr << errno << ":" << strerror(errno) << std::endl; exit(1); } return k; } #endif
然后来测试一下这个函数:
两个进程都调用这个函数:
#include"shm.hpp" int main() { key_t k = getkey(); printf("0x%x\n",k); return 0; }
运行之后我们发现两个进程打印的结果key值都是相同的。
key_t的本质就是一个32位的整数。
然后再用shmget去创建和获取共享内存。
int getshmhelper(key_t k, int flags) { int shmid = shmget(k, MAXSIZE, flags); if(shmid < 0) { std::cerr << errno << ":" << strerror(errno) << std::endl; exit(2); } return shmid; } int getshm(key_t k)//获取共享内存 { return getshmhelper(k, IPC_CREAT);//没有就创建,有就获取 } int createshm(key_t k)//创建共享内存 { return getshmhelper(k, IPC_CREAT | IPC_EXCL | 0600);//没有创建,有就报错,这里创建内存需要给对应的权限 }
我们让server去创建一个共享内存,client去拿共享内存中的数据。
这里再次创建共享内存会报错。
那么key的意义是什么呢?
首先清楚,OS一定可能会存在很多共享内存,并且本质就是申请一块空间,能进行唯一性标识最重要。
之前的C语言,malloc开辟n大小的空间的时候,释放时并不需要告诉他释放多大,自己就知道释放掉n个大小的内存,其实这一块内存中OS也要对这块中间做管理,申请了n个大小,并不代表就是n个,因为里面还有对这块内存的属性和数据左储存,比如大小,释放的时候就回去找这个大小。
这里共享内存也是一样的,OS要先描述再组织,才能进行管理,每次申请一块共享内存,OS还会给这块共享内存申请一个数据结构对象。
所以:共享内存 = 物理内存快 + 共享内存的相关属性
OS管理的是对这个共享内存的数据结构对象做管理的。
那么在创建共享内存的时候,如何保证共享内存在OS中是唯一的呢?答案就是key。
key就像餐厅当中的桌号一样,每个都具有唯一性。
其中一个进程创建共享内存,这块区域中有key值,只要另一个进程也看到同一个key就说明能看到同一块内存。
那么怎么找到key 的位置呢?就在共享内存中的数据结构中。
struct shm
{
key_t k;
}
也就是说ftok函数返回的key值其实就是赋值给共享内存数据结构中的key值。
这就是创建key的含义,key是要通过shmget设置进入共享内存属性中的,用来表示该共享内存在内核中的唯一性。
那么用来接收shmget返回值的变量有什么意义呢?
比如说,我们在企业有自己的员工号,企业扩大的时候,变动的是员工号,并不影响我们的身份证号,这时一种解耦的体现。
用来接收shmget返回值的变量和key值互相不干扰。
就像钥匙和锁一样。(fd与inode也是相同的道理)
那么如何查看IPC资源呢?
首先上面的代码在创建共享内存的时候,明明进程已经结束了,但还显示这个资源已被占用,因为共享内存是OS级的,他的生命周期是和OS相同的,要等OS的关机才会释放掉。
这里用 ipcs -m 查看共享内存。
想删除这块共享内存要用 ipcrm -m shmid
这种方法不太好,所以提供了另一个接口。
shmctl
第一个参数就是上面接受创建共享内存函数的返回值,第二个参数是选项,用来控制这个函数做什么,第三个参数是一个数据结构,就是下面的那个:
返回值是shmid,失败返回-1。
这个选项就是删除掉这段共享内存。
void delshm(int shmid) { if(shmctl(shmid, IPC_RMID, nullptr) == -1) { std::cerr << errno << ":" << strerror(shmid) << std::endl; exit(3); } }
这里谁创建的谁进行删除就可以了。
现在还差一步让两个进程与这个共享内存关联。