在理解内核对象后,接下来谈谈 System V 共享内存的 shmat 和 shmdt 函数。
1. shmget 函数
再次回顾一下 shmget 函数。
图1 中左侧表示的是一个进程空间(未区分内核空间与用户空间),进程空间中并非所有的线性地址被分配(或者说并非所有线性地址都被映射到物理页),黑色部分表示的是未分配的线性地址。
图1 进程空间与物理页
当你使用 shmget 创建出共享内存后,系统就会为你分配一个(或多个)物理页,具体分配多少,就看你当时使用 shmget 第二个参数指定的大小了,一般来说,至少会分配一个物理页(4KB 大小)。分配完成后,操作系统就会在 System V 共享内存的内核对象里进行记录。
2. shmat 与 shmdt
2.1 shmat 与 shmdt 函数原型
void *shmat(int shmid, const void *shmaddr, int shmflg);
int shmdt(const void *shmaddr);
2.2 shmat 函数原理
刚刚分配完物理页后,当然无法直接使用,因为没有任何线性地址映射到这个物理页,所以 shmat 函数就是干这个事的,它从进程空间中选择一个合适的或者你自己指定的线性地址(根据 shmat 第二个参数是否为0来决定,如果是0,系统自动选择),将其挂接到共享内存物理页上。图2 展示了将线性地址挂接到物理页的过程。
shmat 全称是 share memory attach,中译为共享内存挂接。
图2 shmat 函数将线性地址挂接到物理页
shmat 函数完成挂接后会返回那个位置的线性地址,这时候,用户就可以往这里存取数据了。shmat 的第三个参数,shmflg 用来控制读写权限,如果指定为 0,表示可读写,如果指定为 SHM_RDONLY,表示只读。还有其它的标志位,这是针对第二个参数不为 0 的情况所设计的,所以这里就不讨论了。
如果你理解了这个过程,shmdt 自然就解惑了。它所做的操作只是和 shmat 相反罢了。
注意:当你使用 shmdt 断开线性地址到物理地址的映射关系后,不能再次 shmat。再次 shmat 后虽然可以成功获取地址,但是强行读写会产生 segment fault. 具体原因尚不明确。有资料说是 shmdt 后会同时导致当前进程的拿到的 ipc 内核对象 id 无效化。
3. 总结
- 理解共享内存大致实现原理
- 理解 shmat 与 shmdt 干了什么
如果你对线性地址到物理地址是如何映射的感兴趣,请参考另一篇笔记《OS 学习笔记导航》 。