目录
System V是人为规定出的用于操作系统本地进程间通信的一套标准,而System V中的进程间通信方式主要分为3种
1、共享内存
2、消息队列
3、信号量
其中,共享内存这篇博文会详细介绍
共享内存
共享内存是System V中的一个进程间通信的方式,而只要是需要进程间通信,那么它的必要条件就是让不同的进程,能够看到同一份资源,那么接下来,针对于这一点,我们要从原理的角度理解
共享内存的原理
前置背景
有两个进程,分别是进程A和进程B,实际上每一个进程都有它的PCB,在Linux当中就是task_struct,而每一个进程PCB当中都有指向各种内核数据结构的字段,其中一个内核数据结构就是进程虚拟地址空间,有了虚拟地址空间以后每个进程还需要有自己的页表用于虚拟地址与物理地址之间的转化的有,如下图
我们首要的目标是要让上图中的进程A和进程B能看到同一份资源
让不同的进程看到同一份资源,第一步:
所以第一步首先是要在物理内存中开辟一个空间
需要注意的是,这个内存空间是由操作系统开辟的,对于用户来说需要使用特定的系统调用来开辟,后面会说
让不同的进程看到同一份资源,第二步:
接下来的第二步是要让进程A和进程B都能看到开辟的这个空间,所以需要在各自的页表当中构建好各自虚拟地址到这个空间的物理地址的映射,其中虚拟地址空间我们选择共享区往下,堆区往上的空间
而第二步的构建映射也是由操作系统来完成的,所以用户需要使用系统调用接口来构建映射
让不同的进程看到同一份资源,第三步:
而第三步是把进程A和进程B中这个空间所对应的虚拟地址的起始位置返回给上层用户,对于两个进程来说可以使用虚拟地址最终找到同一块内存
其中上述这个物理内存中开辟的空间我们称之为共享内存,上述即为共享内存的原理
当然,创建共享内存以后如果需要对共享内存进行删除,那么根据上述原理,只需要删除页表中的映射并把物理内存中这块空间释放即可
共享内存的管理
接下来的问题是在操作系统当中难道只有进程A和进程B使用共享内存进行通信吗?有没有可能在进程A和进程B通信的同时进程C和进程D也在使用共享内存进行通信呢?
答案是可能的,所以一个操作系统当中可能会有非常多的共享内存,那么对于如此多的共享内存操作系统就要对它进行管理,那么如何管理呢?
答案是先描述后组织,所以在内核中一定有一个描述共享内存的结构体,我们暂且认为这个结构体是struct shm,而描述一个共享内存实际上就是这个结构体当中有着共享内存的各种属性,除此之外,每一个shm中都有一个next指针指向下一个shm,此时就形成了一个链表并且对共享内存的管理就变成了对链表的增删查改,如下
struct shm
{
//共享内存的属性
struct shm* next;
};
共享内存的标识符
由于系统中会存在着很多的共享内存,那么接下来的问题是你怎么保证,两个或者多个不同的进程看到的是同一个共享内存呢?
所以系统为了解决这个问题,就给每个共享内存提供唯一性的标识,这个标识是被保存在描述共享内存的结构体(shm)当中的
这个标识共享内存的字段我们称之为key
如下
struct shm
{
//共享内存的属性
key_t key //标识
struct shm* next;
};
此时,对于进程A和进程B来说只需要拿着同一个key,即可访问到同一个共享内存
这个key与文件中的inode编号其实是很像的,唯一不同的是inode是由系统自动分配的,而共享内存的key是由上层用户编写代码时协商好两个进程后自定义分配的,也就是说这个key是要由用户自己来传的,原因后文会说
共享内存的使用
而接下来,我们讲讲共享内存的应用方面
进程获取/创建共享内存的接口
首先,要介绍的第一个系统调用是shmget
SYNOPSIS
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
shmget的头文件是sys/ipc.h和sys/shm.h
shmget的功能是分配一个共享内存给进程
shmget的第二个参数size是获取的共享内存的大小
对于size我们需要注意的一个点是,共享内存的基本单位是4KB,换句话来说如果你创建共享内存时如果大于4KB,例如开辟4097个字节,那么底层它会向上取整给你开辟8KB的空间,并且开辟完以后你也只能用4097个字节,所以建议共享内存的大小设置为4KB的整数倍
shmget的第三个参数shmflg实际上是通过比特位传参的,而系统中已经规定出了两个主要的宏,如下
1、IPC_CREAT
2、IPC_EXCL
换句话来说,shmflg我们可以直接填上面这两个宏,也可以用“|”把它们组合起来,但它们的含义并不相同,如下:
1、单独使用IPC_CREAT:如果共享内存不存在,就创建一个,如果共享内存已经存在,直接获取它
2、单独使用IPC_EXCL:不能单独使用,没意义
3、IPC_CREAT | IPC_EXCL:如果共享内存不存在,就创建一个,如果共享内存已经存在,就出错返回,换句话来说这样使用如果不是出错返回,就一定获取的是一个新的共享内存
一般来说,我们通信双方的两个进程是一个创建共享内存,一个获取共享内存,所以我们只需要让创建共享内存的进程使用IPC_CREAT | IPC_EXCL创建,获取共享内存的进程单独使用IPC_CREAT创建,就可以实现一个创建,一个获取
当然,除了上述三种用法以外,也可以直接使用"|"组合设置共享内存的权限(例如0666格式)
获取共享内存标识符
shmget的第一个参数key实际上在介绍共享内存的原理时已经说明了,key其实就是共享内存的唯一标识,接下来的问题是这个key是如何形成的
首先,需要明晰的一个概念是这个key的意义到底是什么?
实际上,key值是什么并不重要,主要是让两个进程获取同一个key,所以系统中提供了一个系统调用ftok
SYNOPSIS
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
ftok的头文件是sys/types.h和sys/ipc.h<