本文介绍另一种进程间通信方式:共享内存。
什么是共享内存?
共享内存就是申请了一块物理内存用来进行数据共享,需要进行数据共享的进程可以将同一块物理内存映射到自己的虚拟地址空间,然后通过自己的虚拟地址空间直接访问这块空间,从而实现数据交流。
共享内存和管道的区别:
(1)管道的通信类型是传输,数据是先进先出的,数据被读取走之后才会消失,因此数据不会被覆盖。
(2)共享内存是覆盖式的,因此如果多个进程对共享内存进行操作的话,就会存在安全隐患。但是共享内存也是最快的进程间通信方式。
共享内存与管道在数据传输效率中的差异:
(1)管道:数据在传输过程中经历了两次数据在内核空间与用户空间之间的拷贝。
(2)共享内存:进程直接通过虚拟地址空间访问共享内存,不需要进行内核空间与用户空间的两次数据拷贝。
如图:进程A和进程B之间如果使用管道进行数据传输,那么首先进程A要把数据从用户空间拷贝一份发送到内核空间的管道中。然后进程B要从内核空间中拷贝一份数据到用户空间中,这样才可以使用。因此管道传输一次数据要经历两次在内核空间与用户空间之间的拷贝。
如图:共享内存是在物理内存中申请的一块空间,需要共享数据的两个进程都与这块共享内存建立映射关系,然后就可以直接从共享内存中读取数据,而不需要经历两次拷贝,因此是最快的进程间通信方式。
创建或打开共享内存:
将共享内存映射到虚拟地址空间:
共享内存操作:
解除映射关系
删除共享内存
在删除共享内存的时候,还有一个东西要注意:映射连接数(和这块共享内存建立映射的进程的个数)。
进程解除和共享内存的映射关系后,映射连接数减1。但是一个共享内存通常是多个进程一起操作的(比如进程A,进程B),进程A执行了删除共享内存的操作后,这个共享内存是不会被立即删除的,不然进程B用的好好的突然断了咋整。
因此执行了删除共享内存这操作后,共享内存不能被立即删除,但是共享内存会被标记成被删除的状态,以后其他进程如果要连接这个共享内存,系统发现该共享内存是被删除状态,就拒绝其他进程连接了。
等到连接这块共享内存的进程都和这块共享内存解除映射后,映射连接数就变成了0,此时系统才会释放共享内存。
按照共享内存的操作流程来依次介绍共享内存的操作接口。
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字节。
void* shmat(int shmid,void* addr,int shmflag)
作用:建立映射关系
返回值:建立成功则返回映射的空间的首地址(通过这个地址操作共享内存),失败返回(void*)-1。
因为共享内存其实就是一块内存,因此我们可以直接用操作内存的函数来操作共享内存,比如:memcpy、strcpy、printf
int shmdt(void* shm_start)
作用:解除进程与指定的共享内存的映射关系
int shmctl(int shmid,int cmd,struct shmid_ds* buf)
作用:删除共享内存(这个函数不止是用来删除,它的功能很多,只不过我们常用到的是删除功能)
返回值:返回值其实不是固定的,因为不同的操作选项代表了不同的功能,不同的功能有不同的返回值。对于删除操作来说,成功返回0,失败返回-1。
如图:此时系统中只有一个共享内存。
注意,此时的共享内存状态改变为 dest(被删除状态),变成这个状态后就不允许其他进程再和它建立映射了。