在我的管道博客中曾说过关于进程间通信有很多的方式,管道是利用了Linux
内核原有的接口而创造的,且它只支持单向通信。那么既然有用了原来本来
就有的资源而创造的进程间通信方式,那么也有新创造的通信方式,其中就
有内存共享、消息队列、信号量。它们都是基于system V命名空间的一些常
见的进程间通信方式,而我今天着重介绍,内存共享。
文章目录
- 1. 共享内存的大致原理
- 2. 共享内存接口
- 3. 共享内存进程间通信
- 4. 共享内存的特点
1. 共享内存的大致原理
我们说过,进程间通信的本质就是让不同的进程能够看到同一份资源,这样就提供了进程间通信的基础,而共享内存也是如此:
共享内存就是再物理内存中申请一片空间然后,如果有两个进程想要通信,那么就可以通过页表挂接到地址空间中,这样两个进程就能看到同一片资源。这样就可以通过对这片空间进行读写就可以了:
2. 共享内存接口
知道了大致原理,我们就来通过共享内存的接口来更加深层次的认识共享内存:
shmget是进程在物理内存中开辟空间的接口,它会返回一个整形,用来供用户识别共享内存。
第一个参数可以随意填写,它相当于是共享内存的标识,系统就是使用它来识别共享内存的
第二个参数就是共享内存的大小,这个大小建议是4096字节的整数倍(因为数据块的基本单元就是4KB)
第三个参数是用来创建或者获取共享内存的标志位,就两个标志位
IPC_CREAT:使用它之后,如果key对应的共享内存不存在则创建,存在则返回共享内存的信息shmid
IPC_EXCL:如果key对应的共享内存已经创建出来就报错(-1被返回),这个标志位可以确保共享内存是新创建的
使用它之后我们就可以开辟出一片空间,在命令行中我们可以使用ipcs -m来查看我们建立好的共享内存:
这个数字是我瞎写的:
需要介绍一下这其中的有些参数
key和shmid就是我们代码中的key和shmid
perm是这个共享内存的权限(它的权限跟文件的权限一样)
nattch是有多少个进程挂接到这个共享内存
而且我们看到我们的程序在创建号共享内存后直接就退出了,但是共享内存还存在。共享内存的生命周期是随内核的,意思就是当我们关闭我们的主机时,我们的共享内存才会关闭,而匿名管道文件的生命周期是随进程的。
关于共享内存的权限我们可以在shmget中设置:
我们可以使用ipcrm -m + shmid来释放掉这个共享内存。
可以看到上面那个key就是我们接口中调用时的参数。需要注意的是,这个接口仅仅是帮我们创建出了共享内存,所以我们还要认识两个接口shmat和shmdt:
shmat是用来让进程的地址空间通过页表与共享内存挂接的。这样我们的进程才能使用其中共享内存。
shmat的第一个参数就是shmget所返回的供用户辨别的shmid
第二个参数是传一个地址,将它定为我们虚拟地址中共享内存的地址(这里一般就是传空指针,让操作系统自行决定)
第三个参数是关于对这个共享内存的读或者写的标志位,但是我们有了权限的约束,也不太需要这个
shmdt是用来让进程与共享内存解链接的,它的参数很简单就是shmat所返回来的那个地址即可,我们可以写一小段代码来观察一下:
还有一个接口是在代码中对共享内存进行释放的shmctl:
这个接口的第三个参数我们稍后再介绍。这是一个控制共享内存的接口,而当它的命令cmd是IPC_RMID时,它就是释放共享内存:
这就是关于共享内存的接口使用,直到现在我们只是实现了进程间通信的前置部分,接下来我们看看,共享内存是如何实现进程间通信的。
3. 共享内存进程间通信
在实现进程间通信前我们先要有一些储备知识。在我们的操作系统的不只是我们的进程需要进程间通信,会有许多的进程需要进程间通信,这就会造成共享内存会很多,多就需要被管理,那还是先描述再组织,将共享内存的属性信息抽象成结构体然后通过数据结构给组织起来,这样操作系统对共享内存的管理就变成了对这个数据结构的增删查改,那操作系统是怎么辨别共享内存的呢?前面说过,shmget中所使用的key就是操作系统用来分辨共享内存的标志。
我们现在可以创建共享内存也可以挂接它,还能删除它,那假如我们的A进程创建挂接好共享内存后,我们的B进程想要跟A进程通信,怎么办呢?这还是用到了key,shmget中IPC_CREAT选项是当共享内存不存在时创建,存在时返回对应的shmid。所以我们就可以使用我们的key来找到我们的目标共享内存,但是我们自己定义的key不够随机,操作系统中的共享内存可能会很多,这个时候就要介绍另一个函数ftok:
它是一个类似于字符串哈希的方式通过传入一个字符串(经常是路径,因为路径具有唯一性)和一个自定义的整形通过这两者来给我们创建一个一定程度上唯一的整形供我们用作key。那么现在我们就可以,进行进程间通信了:
首先我们需要一些共享的信息放在一个头文件中:
其次就是我们的发送信息端:
接收消息端:
shmget就只需要IPC_CREAT就可以了,或者0也可以:
关于共享内存的释放就是谁创建谁释放。
下面我们简单的写一个通信代码:
发送端:
接收端:
在这里我们发现一些事情:读端不管内存中有没有数据,我只负责读,而且读过的数据还存在(跟数组一样)。这就是共享内存的其中一个特点,就是它不跟管道一样,它没有提供同步机制。可以说是想怎么样就怎么样,非常的不安全,我们这里就可以使用管道的同步机制来限制它,让共享内存也拥有同步的那么个意思:
使用命名管道:
4. 共享内存的特点
经过上面的介绍,我们也能够得出一些共享内存的一些特点:
1. 共享内存的通信方式,不会提供同步机制,使用时一定要注意共享内存的使用安全问题
2.共享内存是所有进程间通信,速度最快的(减少数据拷贝问题)
3. 共享内存可以使用较大空间
关于速度最快这一方面,我们在使用管道的时候,我们要将一个字符串从一个进程传输给另一个进程,首先需要我们的write有一个缓冲区,然后从这个缓冲区中拷贝到管道文件的缓冲区中,然后传输给我们另一个进程的read的缓冲区中。但是共享内存可没有管道文件缓冲区的概念,我们要将一个字符串从一个进程传给另一个进程,我们可以直接在A进程中将字符串放进去,然后这个数据直接就是我们B进程的了,不需要再将数据拷贝到我们的B进程中,缺少了文件页缓冲区拷贝的那一环。那此时我们的A进程的数据直接放在共享内存中呢?连A进程的拷贝到共享内存中的过程也少了,相比于管道文件只直接少了两次拷贝,提高了进程间通信效率,这也就是为什么共享内存是所有进程间通信最快的了。