Post

零拷贝技术:mmap内存映射

零拷贝技术:mmap内存映射

Direct I/O 强调文件读写不经过内核 Page Cache,数据在用户态缓冲区和设备之间通过 DMA 传输。这篇文章我们来介绍第三种零拷贝技术:mmap,即内存映射文件。

I/O 强调文件读写不经过内核 Page Cache,数据在用户态缓冲区和设备之间通过 DMA 传输。这篇文章我们来介绍第三种零拷贝技术:mmap,即内存映射文件。

前面一篇文章讲了零拷贝技术的一种:Direct I/O,Direct I/O 强调文件读写不经过内核 Page Cache,数据在用户态缓冲区和设备之间通过 DMA 传输。这篇文章我们来介绍另外一种零拷贝技术:mmap,即内存映射文件。

mmap

Linux 上的 mmap 系统调用是消除用户态/内核态间数据拷贝的方案,该方案直接把文件对应的内核 Page Cache 映射到用户态地址空间,这样应用程序就可以直接访问 Page Cache 中的文件数据,避免了一次用户态和内核态的数据拷贝。

我们先来看个例子程序,直观的感受一下 mmap。这个程序的作用是把一个文件中的某一段(也可以是全部)通过 mmap 系统调用映射到用户态地址空间,然后通过映射后的用户态地址访问这个文件中的数据。首先我们需要调用 open 系统调用打开用户指定的文件并获取 fd,然后调用 mmap 系统调用对这个 fd 进行映射,映射的范围由用户传递的映射大小(size )和映射偏移量(offset)决定的,此外还需要指定访问权限,这个访问权限不能和使用 open 打开文件时指定的访问权限有冲突。如果 mmap 执行成功,它会返回一个用户态地址,通过这个地址我们就能访问到该文件被映射区间 [offset,offset + size)的数据。当不在需要该映射了,我们需要调用 unmap 系统调用去解除这个映射关系。

当一个进程调用了 mmap 时,内核会在该进程的用户态虚拟地址空间中分配一块区域,并且建立这块区域和指定的文件区域的对应关系,需要注意的是,通常内核并不会立即将该文件指定区域的 Page Cache (通过 CPU 的 MMU 页表)映射到这块虚拟地址空间区域中,只有在进程实际访问到该映射区域的某个内存页时,触发了缺页异常,内核才负责将对应的文件块所在的 Page Cache (通过 CPU 的 MMU 页表)映射到所访问的那块虚拟地址上,如果处理缺页异常的时候该文件块并不在 Page Cache 中,内核则还需要先分配 Page Cache,并且把该文件块加载到 Page Cache 中,然后再把 Page Cache 映射到那块虚拟地址上。

下面我们看下这个例子程序(mmap)运行的效果,我们使用这个程序对一个 1G 大小的文件的不同区段进行映射,并使用 vmtouch 工具来检查该文件在 Page Cache 中的驻留情况。我们首先使用 vmtouch -e 把该文件从 Page Cache 中踢出去,然后使用例子程序 mmap 映射该文件区间 [512M, 512M + 4K),然后使用 vmtouch 发现该文件正中间有一段被加载到 Page Cache 中了,这一段的大小为 32 个页面,但是我们只映射了一个页面大小(4K),这是因为通过 Page Cache 访问文件时,内核会提前预读(read ahead)一部分文件数据到 Page Cache 中,以备后面访问。然后我们又使用例子程序分别对文件的第一个 4K 文件块和最后一个 4K 文件块进行映射,然后使用 vmtouch 发现这两块内容也被加载到 Page Cache 中了。

前面我们说了,内核在执行 mmap 系统调用的时候通常并不会把文件的 Page Cache 映射到进程的用户态地址空间中,只有当进程真正访问这块虚拟内存的时候,触发了缺页异常,内核在处理缺页异常的时候才会去修改 CPU 的 MMU 页表,真正的完成从虚拟地址空间到 Page Cache 的映射。这样往往会导致我们在访问 mmap 映射得到的虚拟地址的时候频繁的触发缺页异常而降低访问效率。幸运的是 mmap 提供了一个 MAP_POPULATE flag,这个 flag 可以让内核在执行 mmap 的时候就把 Page Cache 和用户态虚拟地址空间的映射关系建立好,这样就避免了后面访问这块虚拟地址空间的时候触发缺页异常从而提供访问效率。下面我们就来做个实验看下mmap 是否使用 MAP_POPULATE,对后面的访问效率的影响。我们在访问映射得到的虚拟地址的那个 for 循环前后记录一下 tsc 时间戳,这样就能算出来访存所花的时间。

下面的代码是为 mmap 加上 MAP_POPULATE flag 的代码。

下面是测试结果的对比,可以看到使用了 MAP_POPULATE 之后,访问效率提升了一倍。

总结

最后总结一下,Linux 上使用 mmap 访问文件的主要优势在于通过内存映射实现零拷贝访问,利用操作系统的按需加载和页缓存机制,简化随机访问。这些特性使得它在处理大文件、需要频繁随机访问或进程间共享文件数据的场景下极具吸引力,能带来显著的性能提升和编程便利性。然而,它并非适用于所有场景,开发者需要根据具体需求(文件大小、访问模式、同步要求等)权衡利弊。

This post is licensed under CC BY 4.0 by the author.