Linux 操作系统原理 — 内存 — mmap 进程虚拟内存映射
目录
mmap()
mmap() 是一个系统调用函数,本质是一种进程虚拟内存的映射方法,可以将一个文件、一段物理内存或者其它对象映射到进程的虚拟内存地址空间。实现这样的映射关系后,进程就可以采用指针的方式来读写操作这一段内存,进而完成对文件的操作,而不必再调用 read/write 等系统调用函数了。
函数原型:
void *mmap(void *adrr, size_t length, int prot, int flags, int fd, off_t offset);
- 1
- addr:建立映射区的首地址,由 Linux 内核指定。用户程序调用时直接传递 NULL。
- length:创建映射区的大小。
- prot:映射区的权限,有 PROT_READ、PROT_WRITE、PROT_READ|PROT_WRITE 类型。
- flags:标志位参数,常用于设定更新物理区域、设置共享、创建匿名映射区。
- MAP_SHARED:映射区所做的修改会反映到物理设备(磁盘)上。
- MAP_PRIVATE:映射区所做的修改不会反映到物理设备上。
- fd:用来建立映射区的文件描述符。
- offset:映射文件的偏移量(4k 的整数倍),可以映射整个文件,也可以只映射一部分内容。
返回值:
- 成功:返回创建的映射区首地址
- 失败:MAP_FAILED 宏
与使用 malloc() 来申请内存空间一样,mmap() 建立的内存映射区在使用结束后也需要调用释放函数:
int munmap(void *addr, size_t length)
- 1
返回值:
- 成功:0
- 失败:-1
mmap 与 read/write 的性能比较
对一个存有 n 个整数的文件进行读取、+1,然后写入操作。
- read/write 核心代码
/*read/write*/
gettimeofday(&tv1, NULL);
fd = open("test_rw", O_RDWR);
read(fd, (void *)array, sizeof(int) * MAX);
gettimeofday(&tv2, NULL);
printf( "Time of read: %dms\n", tv2.tv_usec-tv1.tv_usec);
gettimeofday(&tv1, NULL);
for (i=0; i<MAX; ++i) {
++array[i];
}
write(fd, (void *)array, sizeof(int) * MAX);
close( fd );
gettimeofday(&tv2, NULL);
printf( "Time of write: %dms\n", tv2.tv_usec-tv1.tv_usec );
pause();
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- mmap 核心代码
gettimeofday(&tv1, NULL);
fd = open("test_mmap", O_RDWR);
array = mmap(NULL, sizeof(int) * MAX, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
for (i=0; i<MAX; ++i) {
++array[i];
}
munmap(array, sizeof(int) * MAX);
msync(array, sizeof(int) * MAX, MS_SYNC);
close(fd);
gettimeofday(&tv2, NULL);
printf("Time of mmap: %dms\n", tv2.tv_usec-tv1.tv_usec);
pause();
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
-
read/write 执行结果
-
mmap 执行结果
可以看出,文件越大 read/write 就耗时越长,所以对于一些大文件 mmap 效率更高。这也联想到了 malloc 小于 128kb 用 brk 实现,大于用 mmap 实现。
为什么对于大文件而言 mmap 的效率要高于 read/write?
先回顾一下调用 read/write 进行的常规的文件系统操作中,函数的调用过程:
- 进程发起读文件请求。
- 内核通过查找进程文件符表,定位到内核已打开文件集上的文件信息,从而找到此文件的 inode。
- inode 在 address_space 上查找要请求的文件页是否已经缓存在页缓存中。如果存在,则直接返回这片文件页的内容。
- 如果不存在,则通过 inode 定位到文件磁盘地址,将数据从磁盘复制到页缓存。之后再次发起读页面过程,进而将页缓存中的数据发给用户进程。
所以,总结来说,常规的文件操作为了提高读写效率和保护磁盘,使用了页缓存机制。这样造成读文件时需要先将文件页从磁盘拷贝到页缓存中,由于页缓存处在内核空间,不能被用户进程直接寻址,所以还需要将页缓存中数据页再次拷贝到内存对应的用户空间中。这样,通过了两次数据拷贝过程,才能完成进程对文件内容的获取任务。写操作也是一样,待写入的 Buffer 在内核空间不能直接访问,必须要先拷贝至内核空间对应的主存,再写回磁盘中(延迟写回),也是需要两次数据拷贝。
而使用 mmap 进行的文件操作中,首先会创建新的虚拟内存区域与文件磁盘地址之间的映射关系,在之后数据访问中,如果发现内存中并无相应的数据,则发起缺页异常,通过已经建立好的映射关系,只使用一次数据拷贝就将数据从磁盘中拷贝到用户空间中,供用户态进程使用。
综上,read/write 操作需要经历磁盘文件到内核页缓存再到用户空间缓存的两次数据拷贝。而 mmap 函数只需要从磁盘文件拷贝到内核缓存,然后用户进程直接就可以通过 Share 的方式进行访问,只存在一次数据拷贝过程。因此 mmap 效率更高。所以 mmap 也常被用的 “零拷贝” 场景中。
mmap 优点总结
- 减少了数据的拷贝次数,用内存读写取代 I/O 读写,提高了文件读取效率。
- 实现了用户空间和内核空间的高效交互(映射)方式。各自的空间修改操作都会直接反映在共享(Shared)区域内,从而被对方空间及时捕捉到。
- 提供不同进程间共享内存及相互通信的方式。无论是父子进程,还是无亲缘关系的进程之间,都可以将自身的用户空间映射到同一个文件或匿名映射到同一片区域。从而通过各自对映射区域的改动,达到进程间通信和进程间共享的目的。例如:进程 A、B 都映射了区域 Z,当 A 第一次读取 C 时,通过缺页机制从磁盘中复制文件页到共享内存;当 B 再读 C 的相同页面时,虽然也会产生缺页异常,但是不再需要从磁盘中复制文件过来,而可直接使用已经保存在内存中的文件数据。
- 可用于实现高效的大规模数据传输。通常的,内存空间不足是制约大数据操作的一个方面,解决方案可以是借助硬盘空间协助操作,补充内存空间的不足。但是也会进一步的造成了大量的文件 I/O 操作,极大的影响了执行效率。这个问题可以通过 mmap 映射很好的解决,但凡需要用磁盘空间代替内存的时候,mmap 都可以发挥其功效。
文章来源: is-cloud.blog.csdn.net,作者:范桂飓,版权归原作者所有,如需转载,请联系作者。
原文链接:is-cloud.blog.csdn.net/article/details/106035634
- 点赞
- 收藏
- 关注作者
评论(0)