在涉及到IO的开发中,我们经常看到零拷贝(zero copy)、内存映射(memroy map, 以下简称mmap)等技术被用于提高IO效率,本文将介绍这两种技术的基本原理,说明它们是如何提高IO效率的。
相关概念
Zero copy和mmap涉及到操作系统中的一些基本概念,在了解它们的工作机制前,我们先来复习一下这些概念。
虚拟内存(virtual memory space)
进程对内存的读写不是直接使用物理内存地址,而是基于虚拟地址。
每个进程运行时,操作系统都会为其创建一个私有的虚拟内存,存放进程运行时代码和数据。虚拟内存大小取决与操作系统和所在机器的体系结构,对于32机器来说,空间大小为4g。
操作系统通过内存管理机制,将虚拟内存映射到物理内存。
虚拟内存使得操作系统可以同时支持多个运行进程安全共享物理内存,防止进程之间的不安全读写。
User space vs kernel space
虚拟内存分为两部分:用户空间和内核空间。用户空间存放用户代码和用户数据;内核空间存放操作系统代码。
前面说过,每个进程有自己私有的虚拟内存,不同进程的虚拟内存中的相同的地址,被映射到物理内存中的不同位置。但是内核空间是个例外,所有进程是共享内核空间的,也就是对不同进程来说,它们内核空间内的内容、地址映射实际上都是相同的。
缺页中断(page fault)
操作系统为每个进程的虚拟内存和物理内存之间建立了一张映射表,需要注意的是,虚拟内存中的内容只会一部分被装载到物理内存中。
当进程访问的虚拟地址对应的内容不在物理内存时,操作系统会触发一个缺页中断,将物理内存中不用的内容暂时置换到磁盘,将需要的内容读取道物理内存。通过这种管理模式,我们可以在同时运行多个进程的情况下,让每个进程觉得自己在独享整个内存空间。
User mode vs kernel mode
操作系统至少为进程提供两种运行模式:用户态(usermode)、内核态(kernel mode)。不同的模式,实际对应着不同的运行权限。
用户态的进程,只能访问用户空间,不能直接访问设备。而内核态的进程,可以访问用户空间和内核空间,可以访问硬件设备。
进程模式切换
一个进程可以通过系统调用在用户态和内核态之间切换,此外,中断、异常等机制也可以让进程冲用户态切换到内核态。
这里简单说明下系统调用的过程;
- 用户态进程准备好系统调用的参数;
- 进程执行
trap
指令 - cpu自动切换到内核态
- cpu读取进程事先设置的系统调用参数
- 执行指定的系统调用
- 结束后,进程重新被设置为用户态
工作过程分析
假定我们现在要实现一个读取文件,在内存中处理,然后将其输出的需求,我们看看在不同的实现方式中,底层究竟是如何工作的。
普通实现(read + write)
首先,我们使用常规的文件io操作实现需求。
- 进程通过系统调用读取数据
- 进程切换到内核态,通知设备进行读取
- 设备准备好的数据传送到内核空间
- 接收到数据后,内核态进程将数据从内核空间拷贝到用户空间
- 进程切换回用户态,继续执行用户空间的代码:处理数据
- 进程通过系统调用输出数据到设备
- 进程切换到内核态,把数据从用户空间拷贝到内核空间,通知设备进行输出操作
- 设备完成任务后,进程再次从内核态切换回用户态
在上面的过程中,涉及到4次进程模式切换,两次内存拷贝。这些操作对性能会造成一定影响。
sendfile
接下来,我们通过sendfile
调用,减少上述过程中的内存拷贝,实现零拷贝。
通过sendfile
,我们看到进程模式切换从4次减少到2次,拷贝从2次减少到1次。
但是sendfile
只能完成文件的拷贝操作,无法处理文件内容,mmap
则可以帮助我们实现零拷贝下的处理。
mmap
通过调用memory map,我们让操作系统把文件的内容映射到内存,对内存的读写将关联到对应的文件。而应用通过访问用户空间操作这部分内存,避免了内存拷贝操作。
对于内存映射,有些地方容易被误解,这里说明一下。
内存映射是文件到内存空间的映射
对于应用来说,和文件建立映射关系的是虚拟地址空间,而不是物理内存或者Heap。
当我们建立一个2g大小的映射时,并不是在heap,更不是在物理内存中分配了这么大的空间,仅仅是在虚拟地址空间中划出了这么大一个区域而已。
应用访问内存映射区域时,操作系统会把虚拟的地址映射成真正的物理内存地址和底层文件的偏移量。如果应用访问的虚拟地址对应的文件内容尚未被装入内存,操作系统通过缺页中断,将内存中的部分内容交换出去,腾出空间将文件的内容读取到内存。
内存映射对性能的提升是有条件的
通过内存映射访问文件,虽然减少了内存拷贝,减少了系统调用引起的进程模式切换,但是过程中需要承担缺页中断的负担。
对于小文件的读取,或者对于append模式的文件读写,内存映射的性能未必优于普通io操作。只有对大文件的随机访问,内存映射才可能有明显优势,不过这仍然需要更具体的分析和进一步的的benchmark测试。