Linux IPC之System V共享内存

导言:共享内存允许两个或多个进程共享物理内存的同一块区域(段)。由于一个共享内存段会成为一个进程用户空间内存的一部分,因此,这种IPC机制无需内核介入。所有需要做的就是让一个进程将数据复制进共享内存中,并且这部分数据会对其他所有共享同一个段的进程可用。与管道或消息队列要求发送进程将数据从用户空间的缓冲区复制进内核内存和接收进程将数据从内核内存复制进用户空间的缓冲区的做法相比,共享内存的速度更快(每个进程也存在通过系统调用来执行复制操作的开销)。共享内存这种IPC机制不由内核控制,意味着通常需要通过某些同步方法使得进程不会出现同时访问共享内存的情况。

共享内存IPC机制概述

使用一个共享内存段,通常需要执行如下步骤:
1. 调用shmget()创建一个新的共享内存段,或取得一个既有的共享内存段的标识符(即,由其他进程创建的共享内存段),这个调用将返回后续调用中需要用到的共享内存标识符
2. 使用shmat()来附上(attach)共享内存段,使该段成为调用进程的虚拟内存的一部分。
3. 此刻在程序中可以像对待其他可用内存那样对待这个共享内存段。为引用这块共享内存,程序需要使用由shmat()调用返回的addr值,它是指向进程的虚拟地址空间中该共享内存段的起点的指针。
4. 调用shmdt()来分离共享内存段,在这个调用之后,进程就无法再引用这块共享内存了,这一步是可选的,并且在进程终止时会自动完成这一步。
5. 调用shmctl()来删除共享内存段,只有当,当前所有附加内存段的进程都与之分离(detach)之后,内存段才会被销毁,只有一个进程需要执行这一步。

使用方法

shmget

shmget()在成功时,返回新的既有的共享内存段的标识符

#include <sys/types.h> // for portability
#include <sys/shm.h>

// returns shared memory segment identifier on success, or -1 on error
int shmget(key_t key, size_t size, int shmflg);
  • key参数通常是IPC_PRIVATE值或由ftok()返回的键。
  • size是一个正整数,表示需要分配的段的字节数。内核是以系统分页大小的整数倍来分配共享内存的,因此,实际上size会被提升到最近的系统分页大小的整数倍。如果使用shmget()来获取一个既有段的标识符,那么size对段不会产生任何效果,但它必须要小于或等于段的大小。
  • shmflg参数执行的任务与其在其他IPC get调用中执行的任务一样,指定施加于新共享内存段上的权限,或需检查的既有内存段的权限。

shmat

shmat()系统调用将shmid标识的共享内存段附加到调用进程的虚拟地址空间中。shmat()结果是返回附加共享内存段的地址,我们可以像对待普通的C指针那样对待这个值,段与进程的虚拟内存的其他部分看起来毫无差异。通常会将shmat()的返回值赋给一个指向某个定义的结构的指针,以便在该段上设定该结构。

#include <sys/types.h> // For portability
#include <sys/shm.h>

// returns address at which shared memory is attached on success, or (void *)-1 on error
void *shmat(int shmid, const void *shmaddr, int shmflg);
  • 如果shmaddr是NULL,则段会被附加到内核所选择的一个合适的地址处。(这是附加一个段的优选方法)
  • 如果shmaddr不为NULL,且没有设置SHM_RND,则段会被附加到由shmaddr指定的地址处,它必须是系统分页大小的一个倍数(否则会发生EINVAL错误)。
  • 如果shmaddr不为NULL,且设置SHM_RND,则段会被附加到在shmaddr中提供的地址,被舍入到最近的常量SHMLBA(shared memory low boundary address)的倍数。这个常量等于系统分页大小的某个倍数。(将一个段附加到值为SHMLBA的倍数的地址处在一些架构上是有必要的,因为这样才能够提升CPU的快速缓冲性能和防止出现同一个段的不同附加操作在CPU快速缓冲中存在不一致的视图的情况)。在x86架构上,SHMLBA的值与系统分页大小是一样的,这意味着此类缓冲不一致不可能在那些架构上出现。

说明:为shmaddr指定一个非NULL值,不是一种推荐的做法,原因如下:
1. 它降低了一个应用程序的可移植性。在一个UNIX实现上有效的地址在另一个实现上可能是无效的。
2. 试图将一个共享内存段附加到一个正在使用中的特定地址处的操作会失败。

访问权限

要附加一个共享内存段以供只读访问,则需要在shmflg中指定SHM_RDONLY标记。试图更新只读段中的内容会导致段错误(SIGSEGV信号)的发生。如果没有指定SHM_RDONLY,就可以读写该内存。

shmdt

当一个进程不再需要访问一个共享内存段时,可以调用shmdt()来将该段分离出其虚拟地址空间。

#include <sys/types.h> // For portability
#include <sys/shm.h>

// returns 0 on success, or -1 on error
int shmdt(const void *shmaddr);

注意:分离一个共享内存段,与删除它是不同的。删除是通过shmctl()IPC_RMID操作来完成的。

通过共享内存传输数据(例子)

例子:使用System V共享内存和信号量。这个程序由两个程序构成,写者和读者。写者从标准输入中读取数据块,并将数据复制到一个共享内存段中。读者将共享内存段中的数据块复制到标准输出中。(类似,程序将共享内存当成了管道来处理)

TODO

共享内存在虚拟内存中的位置

在x86-32架构上,一个进程的各个部分在虚拟内存中的布局如下:其中,共享内存段被附加在向上增长的堆和向下增长的

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值