目录
本文介绍另一种进程间通信方式:共享内存。
一、共享内存的理解
什么是共享内存?
共享内存就是申请了一块物理内存用来进行数据共享,需要进行数据共享的进程可以将同一块物理内存映射到自己的虚拟地址空间,然后通过自己的虚拟地址空间直接访问这块空间,从而实现数据交流。
共享内存和管道的区别:
(1)管道的通信类型是传输,数据是先进先出的,数据被读取走之后才会消失,因此数据不会被覆盖。
(2)共享内存是覆盖式的,因此如果多个进程对共享内存进行操作的话,就会存在安全隐患。但是共享内存也是最快的进程间通信方式。
共享内存与管道在数据传输效率中的差异:
(1)管道:数据在传输过程中经历了两次数据在内核空间与用户空间之间的拷贝。
(2)共享内存:进程直接通过虚拟地址空间访问共享内存,不需要进行内核空间与用户空间的两次数据拷贝。
如图:进程A和进程B之间如果使用管道进行数据传输,那么首先进程A要把数据从用户空间拷贝一份发送到内核空间的管道中。然后进程B要从内核空间中拷贝一份数据到用户空间中,这样才可以使用。因此管道传输一次数据要经历两次在内核空间与用户空间之间的拷贝。
如图:共享内存是在物理内存中申请的一块空间,需要共享数据的两个进程都与这块共享内存建立映射关系,然后就可以直接从共享内存中读取数据,而不需要经历两次拷贝,因此是最快的进程间通信方式。
二、共享内存操作流程
-
创建或打开共享内存:
- 创建共享内存就是开辟一块物理内存用来作为共享内存,打开共享内存就是打开已经被开辟好的共享内存。
-
将共享内存映射到虚拟地址空间:
- 进程要想使用这块共享内存,就必须让共享内存映射到进程的虚拟地址空间,这样进程就可以通过虚拟地址空间访问这块共享内存。
-
共享内存操作:
- 操作共享内存就是使用共享内存进行通信,也就是将数据写入到共享内存或者从共享内存中读取数据等操作。
-
解除映射关系
- 在共享内存使用完毕后,需要先解除共享内存与进程虚拟空间的映射关系。
-
删除共享内存
- 解除映射关系后就可以删除共享内存。
在删除共享内存的时候,还有一个东西要注意:映射连接数(和这块共享内存建立映射的进程的个数)。
进程解除和共享内存的映射关系后,映射连接数减1。但是一个共享内存通常是多个进程一起操作的(比如进程A,进程B),进程A执行了删除共享内存的操作后,这个共享内存是不会被立即删除的,不然进程B用的好好的突然断了咋整。
因此执行了删除共享内存这操作后,共享内存不能被立即删除,但是共享内存会被标记成被删除的状态,以后其他进程如果要连接这个共享内存,系统发现该共享内存是被删除状态,就拒绝其他进程连接了。
等到连接这块共享内存的进程都和这块共享内存解除映射后,映射连接数就变成了0,此时系统才会释放共享内存。
三、共享内存操作接口
按照共享内存的操作流程来依次介绍共享内存的操作接口。
1.创建/打开共享内存
int shmget(key_t key,size_t size,int shmflag)
作用:创建或打开共享内存
-
ket_t key:共享内存的标识符,也就是共享内存的名字(注意把标识符和文件标识符区分开)。
-
size_t size:要创建的共享内存的大小。
-
int shmflag:创建权限/打开方式。如果是创建共享内存就是创建权限,如果是打开共享内存就是打开方式。打开方式通常是 IPC_CREAT(共享内存不存在就创建)
返回值:成功返回共享内存的操作句柄(非负整数),失败返回-1
关于key:
多个进程通过共享内存的名字找到同一个共享内存。(如果把名字命名为 IPC_PRIVATE,就代表这个共享内存只能用于具有亲缘关系的进程间通信,因为这样其他进程就找不到这个共享内存)
关于size:
共享内存的大小和共享内存的占用空间不是一回事。共享内存空间的开辟是以内存页为单位的,不是说要创建10字节的共享内存就会开辟10字节大小的共享内存空间。因为内存和磁盘的空间管理是以块为单位管理的,而不是以字节为单位管理。
也就是说,如果要创建10字节的大小的共享内存,实际上会开辟4096个字节大小的共享内存,只不过使用的大小是10字节。
2.进程与共享内存建立映射关系
void* shmat(int shmid,void* addr,int shmflag)
作用:建立映射关系
- shmid:要建立映射的共享内存的操作句柄。
- addr:指定映射首地址,也就是把共享内存映射到虚拟空间的什么位置,通常置NULL(让系统自己选择合适的位置)。
- shmflag:要对共享内存进行的操作,通常传入0(可读可写),也可以是SHM_RDONLY(只读)
返回值:建立成功则返回映射的空间的首地址(通过这个地址操作共享内存),失败返回(void*)-1。
3.操作共享内存
因为共享内存其实就是一块内存,因此我们可以直接用操作内存的函数来操作共享内存,比如:memcpy、strcpy、printf
4.进程解除与共享内存的映射
int shmdt(void* shm_start)
作用:解除进程与指定的共享内存的映射关系
- shm_start:共享内存在虚拟空间中的映射首地址。
5.删除共享内存
int shmctl(int shmid,int cmd,struct shmid_ds* buf)
作用:删除共享内存(这个函数不止是用来删除,它的功能很多,只不过我们常用到的是删除功能)
- int shmid:要操作的共享内存的操作句柄。
- int cmd:要对共享内存进行的操作。 我们通常使用 IPC_RMID(表示删除共享内存)
- struct shmid_ds* buf:用于设置或者获取共享内存信息,不用则置NULL。
返回值:返回值其实不是固定的,因为不同的操作选项代表了不同的功能,不同的功能有不同的返回值。对于删除操作来说,成功返回0,失败返回-1。
四、共享内存相关指令
1.查看共享内存信息
- ipcs -m
如图:此时系统中只有一个共享内存。
- key:共享内存的名字
- shmid:共享内存的id
- owner:共享内存的拥有者
- perms:权限
- bytes:共享内存大小
- nattch:映射连接数
- status:共享内存的状态
2.删除指定共享内存
- ipcrm -m shmid
注意,此时的共享内存状态改变为 dest(被删除状态),变成这个状态后就不允许其他进程再和它建立映射了。