内存映射文件
内存映射文件(Memory-mapped file),或称“文件映射”、“映射文件”,是一段虚内存逐字节对应于一个文件或类文件的资源,使得应用程序处理映射部分如同访问主内存。
主要用处是增加I/O性能,特别是用于大文件。对于小文件,内存映射文件会导致碎片空间浪费,[1]因为内存映射总是要对齐页边界,这起码是4 KiB。因而一个5 KiB文件将会映射占用8 KiB内存,浪费了3 KiB内存。访问内存映射文件比直接文件读写要快几个数量级。
内存映射文件可以只加载一部分内容到用户的逻辑内存空间。这对非常大的文件特别有用。
使用内存映射文件可以避免颠簸:把相当大的文件直接加载到内存时,由于可用内存不足,使得一边读取文件内存,同时把部分已经加载的文件从内存写入硬盘虚存文件中。
内存映射文件由操作系统的内存管理程序负责,因此绕过了硬盘虚存的分页文件(page file)。[2]
mmap函数
//用户空间函数
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
//内核空间函数
int mmap(struct file *filp, struct vm_area_struct *vma)
概念类的在之前的那篇文章已经写过了,这里把一些细致的点再梳理下。先看看函数参数:
start: 指定被映射到的进程内空间的起始地址。它通常被指定为一个空指针,这样告诉内核自己去选择起始地址。无论哪种情况下,该函数的返回值都是描述符fd所映射到内存的起始地址.
len: 是映射到调用进程地址空间中的字节数,它从被映射文件开头第offset个字节处开始算.offset通常被被设置为0
prot: 内存映射区的保护由prot参数指定.
prot | 说明 |
---|---|
PROT_READ | 数据可读 |
PROT_WRITE | 数据可写 |
PROT_EXEC | 数据可执行 |
PROT_NONE | 数据不可访问 |
flags: MAP_SHARED和MAP_PRAVITE必须指定一个,并可有选择的或上MAP_FIXED
MAP_PRIVATE:映射区域的修改只对当前进程有效,修改的时候类似写时复制,会拷贝一个映射区域的副本。如果内存不足,该区域将正常交换。由于私有映射在写入时会有效地还原为普通内存,因此,如果将此模式配合使用,则必须具有足够的虚拟内存来存储整个映射区域
MAP_SHARED:对映射区域的修改对所有共享的进程有效,实际是直接修改映射区在内存raw中的值,并且会写回到底层对象(文件)中,但不一定及时写。如果底层对象是只读打开,则会出现问题。
从移植性上考虑,MAP_FIXED不应该指定。如果没有指定该标志,但是start不是一个空指针,那么start如何处置取决于实现。不为空的start值通常被当作有关该内存区应如何具体定位的线索。可移植的代码应把start指定成一个空指针,并且不指定MAP_FIXED
flags | 说明 |
---|---|
MAP_SHARED | 变动是共享的 |
MAP_PRIVATE | 变动是私自的 |
MAP_ANONYMOUS | 建立匿名映射,此时会忽略参数fd |
MAP_FIXED | 准确的解释start参数 |
fd: 文件描述符,可以为-1.表示匿名映射
offset: 偏移值,从文件的起始位置偏移多少字节
mmap分类
- 使用普通文件以提供内存映射IO
- 使用特殊文件提供匿名内存映射
- 使用shm_open提供无亲缘进程间的Posix共享内存区(和第一种差不多)
匿名内存映射
看下程序启动阶段的mmap:
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f7484b16000
1 |
|
MAP_PRIVATE
child:
0x7f09d5b71000
100
parent:
0x7f09d5b71000
100
MAP_SHARED
child:
0x7fba08864000
100
parent:
0x7fba08864000
200
实现匿名映射:
有的系统可能不支持匿名映射,可以通过open /dev/zero来自己实现.
/dev/zero在类UNIX系统中是一个特殊的设备文件,当你读它的时候,它会提供无限的空字符(NULL, ASCII NUL, 0x00)。其中的一个典型用法是用它提供的字符流来覆盖信息,另一个常见用法是产生一个特定大小的空白文件。BSD就是通过mmap把/dev/zero映射到虚地址空间实现共享内存的。可以使用mmap将/dev/zero映射到一个虚拟的内存空间,这个操作的效果等同于使用一段匿名的内存(没有和任何文件相关)。
1 |
|
posix共享内存
posix共享内存的实现也是映射文件,不过会给文件路径加上前缀:/dev/shm/.
从使用上来说就是把open替换成shm_open,不过shm_open内部还是调用了open,花里胡哨的
shm_open
1 |
|
测试程序
1 |
|
[root@localhost ~]# ./a.out
2020 04 11 22 54 59
2020 04 11 22 55 00
2020 04 11 22 55 01
2020 04 11 22 55 08
2020 04 11 22 55 08
2020 04 11 22 55 09
2020 04 11 22 55 49
2020 04 11 22 55 50
ok
[root@localhost ~]# ./a.out
2020 04 11 22 54 59
2020 04 11 22 55 00
2020 04 11 22 55 01
2020 04 11 22 55 08
2020 04 11 22 55 08
2020 04 11 22 55 09
2020 04 11 22 55 49
2020 04 11 22 55 50
2020 04 11 22 56 19
ok
[root@localhost ~]# ./a.out
2020 04 11 22 54 59
2020 04 11 22 55 00
2020 04 11 22 55 01
2020 04 11 22 55 08
2020 04 11 22 55 08
2020 04 11 22 55 09
2020 04 11 22 55 49
2020 04 11 22 55 50
2020 04 11 22 56 19
2020 04 11 22 56 31
ok
[root@localhost shm]# pwd
/dev/shm
[root@localhost shm]# ls -lrt
总用量 24
-rw------- 1 postgres postgres 19916 4月 11 17:28 PostgreSQL.1900869883
---------- 1 root root 1024 4月 11 22:56 ShareMemory
[root@localhost shm]# cat ShareMemory
2020 04 11 22 54 59
2020 04 11 22 55 00
2020 04 11 22 55 01
2020 04 11 22 55 08
2020 04 11 22 55 08
2020 04 11 22 55 09
2020 04 11 22 55 49
2020 04 11 22 55 50
2020 04 11 22 56 19
2020 04 11 22 56 31
2020 04 11 22 56 32
总结
- MAP_PRIVATE会创建内存副本,只对单个进程有效,不可用于共享
- MAP_SHARED在内存只有一个映射对象,修改对所有共享进程生效。并且修改后会写入底层对象(文件)中
- mmap匿名文件映射fd赋值-1, flag或上MAP_ANON.或者通过/dev/zero自己实现
- shm_open作为posix标准的共享内存实现,内部也是open打开文件/dev/shm/youfilename来实现的
- 编译的时候 -lrt