进程间共享内存通信有 POSIX 和 SystemV 两种实现方法,其中,SystemV API 广泛应用与 X windows 系统及其扩展版本中。
本文章简单介绍 Linux 进程间通信——共享内存(Posix 共享内存区)
共享内存区是可用 IPC 形式中最快的。
一旦这样的内存区映射到共享它的进程的地址空间,这些进程间数据的传递就不再涉及内核。这里说的“不再涉及内核”的含义是:进程不再通过执行任何进入内核的系统调用来彼此传递数据。显然,内核必须建立允许各个进程共享该内存区的内存映射关系,然后一直管理该内存区(处理页面故障等)。
mmap、munmap 和 msync 函数
mmap 函数
mmap 函数把一个文件或一个 Posix 共享内存区对象映射到调用进程的地址空间。使用该函数有三个目的:
- 使用普通文件以提供内存映射 I/O
- 使用特殊文件以提供匿名内存映射
- 使用 shm_open 以提供无亲缘关系进程间的 Posix 共享内存区
1 |
|
内存映射区的保护由 prot 参数指定。该参数的常见值是代表读写访问的 PROT_READ | PROT_WRITE。
| prot | 说明 |
|---|---|
| PROT_READ | 数据可读 |
| PROT_WRITE | 数据可写 |
| PROT_EXEC | 数据可执行 |
| PROT_NONE | 数据不可访问 |
| Flags | 说明 |
|---|---|
| MAP_SHARED | 如果参数 start 所指的地址无法成功建立映射时,则放弃映射,不对地址做修正。通常不鼓励用此旗标 |
| MAP_PRIVATE | 变动是私有的 |
| MAP_FIXED | 准确地解释 addr 参数 |
| MAP_LOCKED | 将映射区域锁定住,这表示该区域不会被置换(swap) |
| MAP_DENYWRITE | 只允许对映射区域的写入操作,其他对文件直接写入的操作将会被拒绝 |
| MAP_ANONYMOUS | 建立匿名映射。此时会忽略参数 fd,不涉及文件,而且映射区域无法和其他进程共享。 |
从移植性上考虑,MAP_FIXED 不应该指定。如果没有指定该标志,但是 addr 不是一个空指针,那么 addr 如何处置取决于实现。不为空的 addr 值通常被当作有关该内存区应如何具体定位的线索。可移植的代码应把 addr 指定成一个空指针,并且不指定 MAP_FIXED。
munmap 函数
为从某个进程的地址空间删除一个映射关系,我们调用 munmap。
1 |
|
如果被映射区是使用 MAP_PRIVATE 标志映射的,那么调用进程对它所作的变动都会被丢弃掉。
msync 函数
如果我们修改了处于内存映射到某个文件的内存区中某个位置的内容,那么内核将在稍后某个时刻相应地更新文件。然而有时候我们希望确信硬盘上的文件内容与内存映射区中的内容一致,于是调用 msync 来执行这种同步。
1 |
|
| 常值 | 说明 |
|---|---|
| MS_ASYNC | 执行异步写 |
| MS_SYNC | 执行同步写 |
| MS_INVALIDATE | 使高速缓存地数据失效 |
为什么使用 mmap
mmap 的描述间接说明了内存映射文件:open 它之后调用 mmap 把它映射到调用进程地址空间的某个文件。使用内存映射文件所得到的奇妙特性是,所有的 I/O 都在内核的掩盖下完成,只需编写存取内存映射区中各个值的代码。 决不调用 read、write 或 lseek。这么一来往往可以简化代码。
mmap 的另一个用途是在无亲缘关系的进程间提供共享的内存区。
Posix 共享内存区
Posix 共享内存区涉及两个步骤要求:
- 指定一个名字参数调用 shm_open,以创建一个新的共享内存区对象或打开一个已经存在的共享内存区对象;
- 调用 mmap 把这个共享内存区映射到调用进程的地址空间。
shm_unlink 函数删除一个共享内存区对象的名字。跟所有其他 unlink 函数(删除文件系统中一个路径名的 unlink,删除一个 Posix 消息队列的 mq_unlink,以及删除一个 Posix 有名信号量的 sem_unlink)一样,删除一个名字不会影响对于其底层支撑对象的现有引用,直到对于该对象的引用全部关闭为止。删除一个名字仅仅防止后续的 open、mq_open 或 sem_open 调用取得成功。
对于一个普通文件:如果该文件的大小大于 length 参数,额外的数据就被丢弃掉。如果该文件的大小小于 length,那么该文件是否修改以及其大小是否增长是未加说明的。实际上对于一个普通文件,把它的大小扩展到 length 字节的可移植方法是:先 lseek 到偏移为 length−1 处,然后 write 1 个字节的数据。所幸的是几乎所有 Unix 实现都支持使用 ftruncate 扩展一个文件。对于一个共享内存区对象:ftruncate 把该对象的大小设置成 length 字节。我们调用 ftruncate 来指定新创建的共享内存区对象的大小,或者修改已存在的对象的大小。当打开一个已存在的共享内存区对象时,我们可调用 fstat 来获取有关该对象的信息。
1 |
|
1 |
|
简单示例
写共享内存示例代码
1 |
|
读共享内存示例代码
1 |
|