可先看上文再理解此文会比较简单:
之前,我们研究了内核如何为用户进程管理虚拟内存,但是省略了文件和I/O。这篇文章介绍文件和内存之间的关系,以及它对系统性能的影响。
当涉及到文件时,OS必须解决两个严重的问题。第一个是相对于内存的高速读写,缓慢的硬盘驱动器,特别是磁道寻找较为耗时。第二个是需要在物理内存中加载一次文件内容,并在程序之间共享这些内容。如果您使用Process Explorer查看Windows进程,您将看到在每个进程中加载了大约15MB的通用dll。我的Windows机器现在正在运行100个进程,因此如果不共享,我将使用最多1.5 GB的物理RAM来处理普通dll。同样,几乎所有的Linux程序都需要ld.so和libc以及其他公共库。
幸运的是,这两个问题都可以一次性解决:页面缓存,内核在其中存储页面大小倍数的文件块(上文说过,页面大小是以4KB为一个基本单元)。为了演示页面缓存,我将调用一个名为render的Linux程序,来打开文件。每次读取512字节,将文件内容存储到堆分配的块中。第一个解读是这样的:
读取12KB后,render的堆和相关的页面帧如下:
首先,尽管这个程序使用常规的读调用,但现在在存储scene.dat的一部分的页面缓存中有3个4KB的页帧。人们有时会对此感到惊讶,但是所有常规的文件I/O都是通过页面缓存进行的。在x86 Linux中,内核认为文件是由4KB块组成的序列。如果从文件中读取单个字节,那么包含所请求字节的整个4KB块将从磁盘中读取并放入页面缓存中。这是有意义的,因为持续的磁盘吞吐量非常好,程序通常从文件区域读取的不仅仅是几个字节。页面缓存知道文件中每个4KB块的位置,上面描述为#0、#1等。Windows使用256KB视图,类似于Linux页面缓存中的页面。
遗憾的是,在常规的文件读取中,内核必须将页面缓存的内容复制到用户缓冲区中,这不仅会占用cpu时间并损害cpu缓存,还会使用重复数据浪费物理内存。根据上图,dat内容存储两次,程序的每个实例将额外存储一次内容。我们已经减轻了磁盘延迟问题,但是在其他方面都失败了。内存映射文件是摆脱这种疯狂的方法:
当使用文件映射时,内核将程序的虚拟页面直接映射到页面缓存中。这可以显著提高性能。Windows系统下与常规文件读取相比,这种方法运行时性能改进了30%以上,而在Unix环境中,Linux和Solaris也同样提高了性能。根据应用程序的性质,还可以节省大量重复物理内存。
内存映射在程序员的工具箱中占有一席之地。这个API也很不错,它允许你以字节的形式访问内存中的文件,并且不需要牺牲代码可读性。注意你的地址空间,在类unix系统中使用mmap进行试验,在Windows中使用CreateFileMapping,其他高级语言中也提供类型的接口调用函数。当你映射一个文件时,它的内容不会立即全部放入内存,而是通过页面错误(Page Fault)按需导入。错误处理程序在获得具有所需文件内容的页帧后 将虚拟页映射到页缓存。
--未完待续由于本人水平有限,翻译必然有很多不妥的地方,欢迎指正。
同时,欢迎关注下方微信公众号,一起交流学习:)
|