共享内存区介绍
共享内存区 是可用IPC形式中 最快 的。一旦内存映射到共享它的进程的地址空间,这些进程间的数据传递就 不再涉及内核 。当然对共享内存区的数据的存取操作需要某种形式的同步:JakeLin's Blog - Unix同步方式。
服务器-客户端文件复制程序的通常步骤如下图:
一组数据传递,内核空间 和 进程空间 之间的数据复制有 4次 。
通过使用进程间共享内存区,一组数据传递,内核空间 和 进程空间 之间的数据复制有 2次 。如下图:
映射到进程地址空间
mmap
函数把一个 文件 或一个 Posix共享内存对象 映射到调用进程的 进程地址空间 。该函数有三个目的:
- 使用 普通文件 以提供 内存映射I/O ;
- 使用 特殊文件 以提供 匿名内存映射 ;
- 使用
shm_open
以提供 无亲缘关系进程间的Posix共享内存区 。
内存映射文件的例子:
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
int munmap(void *addr, size_t length);
【注意】不是所有的文件都能进行内存映射,例如:访问终端或套接字的描述符。
-
addr
:指定映射到进程空间的 起始地址 。通常被指定为NULL
,由内核自己选择起始地址。 -
length
:映射到调用进程地址空间中的 字节数,从 offset 偏移处开始算。 -
prot
:内存映射区的保护prot 说明 PROT_READ
数据可读 PROT_WRITE
数据可写 PROT_EXEC 数据可执行 PROT_NONE 数据不可访问 -
flags
:内存映射区属性-
MAP_PRIVATE
:调用进程对映射区数据的修改仅对该进程可见,并且不改变其底层支撑对象(文件或共享内存区对象)。 -
MAP_SHARED
:调用进程对映射区数据的修改,在所有共享该对象的所有进程都可见,并且改变同步到其底层支撑对象。 -
MAP_ANON
或MAP_ANONYMOUS
:匿名内存映射 - MAP_FIXED :准确的解释 addr 参数,从移植性上考虑,该参数不应该指定。
-
-
fd
:底层对象描述符。 -
offset
:底层对象映射从 offset 偏移处开始的 length 长度的字节映射到到调用进程地址空间中。
在设置了 MAP_SHARED
属性的前提下,修改了内存区中的数据,内核将在稍后某个时刻相应的更新底层支撑对象。如果我们期望底层支撑对象与内存映射区中的内容一致,使用 msync 来执行同步。
int msync(void *addr, size_t length, int flags);
-
addr
和length
通常指代整个内存映射区,不过也可以指定该内存区的一个子集。 -
flags
:MS_ASYNC
和MS_SYNC
必须指定一个,且仅指定一个。flags 说明 MS_ASYNC
执行异步写(立即返回) MS_SYNC
执行同步写(等待写完返回) MS_INVALIDATE
使高速缓存的数据失效(与最终副本不一致的都失效,后续从底层支撑中取)
匿名内存映射
-
-
4BSD提供
MAP_ANON
或MAP_ANONYMOUS
,彻底避免了文件的创建和打开。把
mmap
的 flags 参数指定成MAP_SHARED | MAP_ANON
,fd = -1
,offset 参数将被忽略。这样内存区初始化为0.
-
-
SVR4提供
/dev/zero
设备文件从该设备读时返回的字节全为0,写往该设备的任何字节则被丢弃。
映射区内存
内核的内存保护是以页面为单位的! 内核允许我们读写最后一页中映射区以外的部分,但是我们写在映射区以外这一部分的任何内容都不会同步到底层支撑(如文件)。
页面大小4096,mmap大小 = 文件大小(5000):
页面大小4096,mmap大小(15000) > 文件大小(5000):
Posix共享内存区
考虑 无亲缘关系 进程间共享内存的方法:
- 内存映射文件;
- 共享内存区对象。
共享内存区对象操作
shm_open
操作类似于文件 open ,创建 或 打开 一个共享内存区对象。shm_unlink
删除一个共享内存区对象的名字。
int shm_open(const char *name, int oflag, mode_t mode);
int shm_unlink(const char *name);
-
name
参数指定共享内存区对象的名称,由/
开头,且仅有一个斜杠组成。 -
shm_unlink
函数类似于其他的 unlink 函数,删除一个名字不会影响对于其底层支撑对象的现有引用,直到该对象的引用全部关闭为止。(删掉名字 != 析构清除)
【注意】Posix共享内存区 具有 随内核 的持续性。
【疑问】Posix共享内存区,调用 shm_unlink
删除名称,但还未未释放时,再次 shm_open
会如何?
答:manpage 给出了说明,shm_open
将会失败,除非指定了 O_CRAET
。同时,新建的 Posix共享内存内对象 区别于已存在的。
After a successful shm_unlink(), attempts to shm_open() an object with the same name will fail (unless O_CREAT was specified, in which case a new, distinct object is created).
ftruncate和fstat
int ftruncate(int fd, off_t length);
// 获取属性信息
int fstat(int fd, struct stat *buf);
-
ftruncate
用于修改普通文件或共享内存区对象的大小。- 须是以写入模式打开的;
-
普通文件:
- 如果原文件大小大于 length,则额外的数据将被丢弃;如果原来的文件大小比参数length小,则文件将被扩展,与 lseek 系统调用类似,文件的扩展部分将以 '\0' 填充。
- 扩展文件时,此函数并未实质性的向磁盘写入数据,只是分配了一定的空间供当前文件使用。
- 共享内存区对象:把该对象的大小设置成 length 字节。