共享内存
共享内存允许两个或多个进程共享物理内存的同一块区域,由于一个共享内存段会成为一个进程用户空间内存的一部分,因此这种IPC机制无需内核介入,所以需要做的就是让一个进程将数据复制进共享内存中,并且这部分数据会对其他所有共享同一个段的进程可用。
共享内存不由内核控制意味着通常需要通过某些同步方法使得进程不会出现同时访问共享内存的情况。
调用一个共享内存段通常需要执行下面的步骤:
- 调用
shmget()
创建一个新共享内存段或者取地一个既有共享内存段的标识符(即由其他进程创建的共享内存段)。这个调用将返回后续调用中需要用到的共享内存标识符; - 使用
shmat()
来附上共享内存段,即使该段成为调用进程的虚拟内存的一部分; - 此刻在程序中可以像对待其他可用内存那样对待这个共享内存段,为引用这块共享内存,程序需要使用由
shmat()
调用返回的addr
值,它是一个指向进程的虚拟地址空间中该共享内存段的起点的指针; - 调用
shmdt()
来分离共享内存段,在这个调用之后,进程就无法再引用这块共享内存,这一步可选,在进程终止时会自动完成这一步; - 调用
shmctl()
来删除共享内存段,只有在当前所有附加内存段的进程都与之分离之后内存段才会被销毁;
共享内存在虚拟内存中的位置
共享内存段被附加在向上增长的堆和向下增长的栈之间未被分配的空间中,目的是为了给堆和栈的增长腾出空间,附加共享内存段的虚拟地址从0x40000000
开始。
在共享内存中存储指针
每个进程都可能会用到不同的共享库和内存映射,并且可能会附加不同的共享内存段集。按照推荐的方法,让内核选择将共享内存段附加到何处,那么一个段在各个进程中可能会被附加到不同的地址上,所以,在共享内存段中存储指向段中其他地址的引用时应该使用(相对)偏移量,而不是(绝对)指针。
对于一个共享内存段的起始地址为baseaddr
,再假设需要在p指向的位置处存储一个指针,该指针指向的位置与target
指向的位置相同,如上图所示,如果在段中构建一个链表或者一个二叉树,那么这种操作性就是非常典型的一种操作。
在C中设置*p
的传统做法如下;
*p = target;
上面的代码存在的问题是当共享内存段被附加到另一个进程中时,target
指向的位置可能会位于一个不同的虚拟地址处,意味着在进程中存储在*p
中的值是无意义的。正确的做法是在*p
处存储一个偏移量,如下所示:
*p = (target - baseaddr);
在解引用时需要颠倒上面的步骤
target = baseaddr + *p;