system V 共享内存
进程间通信的本质就是让不同的进程看到同一个空间
直接原理
操作系统在物理内存处开辟一段空间, 在修改页表将这段空间映射到一个进程的共享区, 然后将对应的虚拟地址的起始地址显示给应用层, 对另外一个进程也做同样的操作, 这样两个进程就可以通过页表访问这一段内存了。
而如果我们要释放共享内存:那么就有操作系统先释放共享内存, 再去关联
上面的这些操作都是有操作系统去直接做的
直接写一部分代码, 测试一下对应的功能
创建共享内存
你怎么保证让不同的进程看到的是同一个共享内存?
你怎么知道这个共享内存是存在还是不存在?
这就靠的是我们的第一个参数key
(共享内存标识符)
谈谈key:
1 key是一个数字, 他是几不重要, 关键在于他必须在内核中具有唯一性, 能够让不同的进程进行唯一性标识。
2 第一个进程可以通过key创建共享内存, 第二个之后的进程, 只要拿到同一个key就可以和第一个进程看到同一个共享内存了。
3 对于一个已经创建好的共享内存, key在哪? 在共享内存的描述对象中!
4 第一次创建的时候, 必须有一个key, 这个key怎么来?
5 key跟路径一样是具有唯一性
的
第一个参数是路径, 第二个参数是项目id
当我们测试的时候, 发现第一次key和shmid都成功了, 但是第二次的时候key成功了但是shmid却失败了, 这是为什么呢?
我们通过查看系统内共享内存发现, 原来是我们之前创建的共享内存没有被释放导致的
所以我们得知共享内存的生命周期是随内核的!
用户不主动关闭, 共享内存会一直存在。
我们看上面的那个图, 其中perms代表的是该共享内存的权限, nattch代表的是有多少个进程和这个共享内存关联。
我们如何设置共享内存的权限呢?
直接在最后一个参数中带上权限即可
现在我们成功的创建了共享内存, 但是如何用另外一个进程获取这个共享内存呢?
我们像这样就实现了一个接口是创建内存, 一个接口是获取内存。
关于共享内存的大小, 我们做一下补充的说明
现在我们能获取共享内存的shmid了, 如何将我们的进程与这个共享内存挂接起来呢?
shmid
就不用解释了
shmaddr
表示的是, 我们想把共享内存挂接到进程的那个地址里去? 如果不想自己设置就传NULL让系统自己去决定, 最后具体挂接到哪个位置会由该调用的返回值void*返回给我们。
shmflg
我们不去了解, 他表示是我们去挂接他的权限, 我们直接传0表示就默认用共享内存的权限即可。
如果我们像去关联的话可以使用shmdt
我们只需要传入共享内存挂接的地址即可去关联
其中返回值 成功返回0 失败返回-1
我们创建了共享内存, 自然也需要去释放他, 所以还有一个接口叫做shmctl
第一个参数不用解释
cmd
表明我们要做什么操作
这是他的一些选项, 我们现在要关注的就是这个IPC_RMID
, 因为这个选项就是用于删除共享内存的
buf
shmid_ds 是一个类似于操作系统中管理共享内存的结构体
由于我们现在只是需要删除共享内存, 所以我们不关注他的属性, 直接传NULL即可
成功就返回0, 失败就返回-1
我们来运行一下上述的代码。
首先, 我们发现, 我们直接运行用户端, 发现用户端没有等客户端输入消息, 直接就开始读取, 所以读到的是空
我们运行客户端, 写入一段消息, 成功的被用户端读取到了
事实上, 我们是不需要自己定义缓冲区的, 因为共享内存本身就是一个空间, 所以我们直接往里面输入数据即可。
一旦有共享内存挂接到了自己的地址空间中, 我们直接把他当做自己的空间来用就可以了, 不需要使用系统调用
共享内存的特征, 扩展代码
- 共享内存没有同步互斥之类的保护机制
- 共享内存是所有的进程中速度最快的!(拷贝操作进行的少)
- 共享内存内部的数据, 由用户自己维护
共享内存的属性
shmid_ds
从上到下:
- 共享内存的权限
- 共享内存的大小
- 最后一次挂接时间
- 最后一次去关联时间
- 最后一次改变的时间
- 谁创建的这个共享内存(靠pid标识)
- 谁最后关联/去关联(靠pid标识)
- 当前有多少的进程与该共享内存挂接
我们仔细观察可以发现第一个变量的类型是一个结构体, 这个结构体的结构在我们图下方
ipc_perm
- __key 这个就是我们提供给shmget的那个key
- mode 表示共享内存的权限
我们通过shmctl的IPC_STAT选项来获取共享内存的shmid_ds,
用管道维护共享内存的同步机制
在客户端以写方式打开管道文件
当我们在写入后, 我们就象征性的写点内容进管道文件里
在用户端用读方式打开管道文件
是的用户在读到管道文件里的字符后再去读共享内存里的内容