文章目录:
一、共享内存基本概念
(一)定义
在物理内存上申请一块地址,多个进程可以将其映射到自己的虚拟地址空间中,所有进程都可以访问这块内存的地址。这块内存就称为被共享的内存
。
规范化来说就是:共享内存允许两个或多个进程共享同一块存储区,通过地址映射将这块物理内存映射到不同进程的地址空间中,多个进程可以通过这块物理空间进行数据的交互,达到进程间通讯的目的。
如下图所示:
如果某个进程向共享内存写入数据,所做的改动将立刻被可以访问同一段共享内存的其他进程看到,也会影响其他进程读取到的数据。
因为共享内存并没有提供同步机制
,所以会出现多进程的读取不可控,所以我们需要利用其他机制来同步对共享内存的访问,如信号量。
(二)原理
在Linux中,每个进程都有属于自己的进程控制块PCB,地址空间,页表。内存管理单元MMU负责将进程的虚拟地址与物理地址进行映射。 两个不同的虚拟地址空间通过页表映射到物理内存的同一区域,它们所指向的这块区域即共享内存
进程通过页表将虚拟地址映射到物理地址时,在物理地址中有一块共同的内存区,即共享内存。这块内存可以被两个进程看到,那么当一个进程修改共享内存的数据时,另一个进程访问共享内存时就会得到新数据。
如果我们用函数指定获取128字节大小的共享内存时,还是会映射4K大小的共享内存,因为虚拟地址到物理地址的映射是通过页表,以页为单位进行映射,故一个页表大小4K的共享内存空间被进程共享,但只能使用128字节。
(三)特点
1.共享内存是最快的IPC(进程间通讯)方式,它只需要两次数据拷贝
,而管道和消息队列需要四次数据拷贝:因为管道和消息队列进行共享的空间都是由内核对象提供管理,所执行的操作也都是系统调用,而这些数据最终还是要在内存中存储的。管道使用数组来保存数据,利用write,read系统调用将数据写入/读取;消息队列使用自定义消息结构体保存数据,利用msgsnd,msgrcv将数据保存/读取到内核中。我们画出管道的拷贝过程:
所以有四次拷贝,分别是:
- 从内存空间缓冲区将数据拷贝到内核空间缓冲区。
- 从内核缓冲区将数据拷贝到内存
- 从内存将数据拷贝到内核空间缓冲区
- 从内核空间缓冲区将数据拷贝到用户空间缓冲区。
而共享内存使用相关函数,在内存中开辟一块空间,映射到不同进程的虚拟地址空间,并且向用户返回指向该块内存的指针,因此对该内存可通过指针直接访问,只需要两次:
- 从用户空间缓冲区拷贝数据到内存。
- 从内存拷贝数据到用户空间缓冲区。
2.可以实现任意两个进程之间的通讯。
3.父进程forl子进程或者exec执行一个新的程序,在子进程和新程序里面不会继承父进程之间使用的共享内存。
4.共享内存没有提供同步机制,需要配合其他机制实现进程间同步和通讯。
二、共享内存相关函数
内核为每个共享内存维护着一个结构,该结构包含以下成员
struct shmid_ds {
struct ipc_perm shm_perm; //用户权限
size_t shm_segsz; //共享内存大小
time_t shm_atime; //最后一次连接时间
time_t shm_dtime; //最后断开连接时间
time_t shm_ctime; //最后改变事件
pid_t shm_cpid; //PID
pid_t shm_lpid; //最后一个PID
shmatt_t shm_nattch; //多少个进程正在使用这个共享存储
};
(一)共享内存的创建引用shmget函数
用来创建或获得一个共享内存,使用shmget函数,函数原型为:
# include<sys/shm.h>
int shmget(key_t key,size_t size,int flag);
成功返回共享存储ID,失败返回-1
每一个共享存储对应一个ID,唯一标识符,这个函数就返回一个ID。
参数:
- key,flag参数和消息队列的使用一样,不再阐述。
- size:指定创建一个共享内存的长度,单位为字节。如果获取共享内存,那么指定为0。
(二)共享内存的操作shmctl函数
用来控制共享内存shmctl函数,函数原型为:
# include<sys/shm.h>
int shmctl(int shmid,int cmd,struct shmid_ds* buf);
成功返回0,失败返回-1。
可以对指定的共享内存进行删除,获取信息的操作。
参数:
-
shmid:共享内存标识符
-
cmd:要采取的操作,取值如下:
(1)IPC_STAT:把shmid_ds结构中的数据设置为共享内存当前的关联值,即共用内存的当前关联值覆盖shmid_ds的值。
(2)IPC_SET:如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值。
(3)IPC_RMID:删除共享内存段。 -
buf:内核为每个共享内存创建的结构体shmid_ds。
(三)连接共享内存shmat函数
一旦创建/获得一个共享内存ID,那么进程就可以调用shmat函数将共享内存连接到它的地址空间中。函数原型为:
# include<sys/shm.h>
void* shmat(int shmid,const void* addr,int flag);
成功返回指向共享存储段的指针,出错返回-1
参数:
- shmid:共享内存标识符
- addr:指定共享内存连接到当前地址中的地址位置。一般为NULL空,表示让系统来选择共享内存的地址。
- flag:标志位,通常为0。
(四)断开共享内存shmdt函数
对共享内存的操作完成时,将共享内存从当前进程分离,函数原型为:
# include<sys/shm.h>
int shmdt(const void* shmaddr);
成功返回0,出错返回-1
注意,分离不代表删除共享内存,只是使该共享内存对当前进程不再可用,直到某个进程带IPC_RMID命令的调用shmctl删除共享内存为止,共享