WIN32汇编语言教程:第10章 内存管理和文件操作 · 10.4 内存映射文件(1)
首先看10.2.1节的WordCount例子中读文件的操作,与在DOS操作系统下所有文件操作代码的结构一样,例子中用分块读入的办法读取文件,每次读取的内容受限于缓存区的大小,对上一次读入的内容处理完毕后,程序才能继续读入下一块内容,代码结构如下:
.while TRUE .if 缓冲区中还有未处理的数据 ;从缓冲区中取出数据并处理 ;递减未处理字节数 .else ;读取文件,并重新设置未处理字节数 .break .if 到达文件尾部 .endif .endw
经常使用这种结构处理文件的读者一定知道它的严重缺陷就是缓冲区边界的处理问题。假如需要每次先把单词提取出来后再处理(在这个例子中用每次处理单个字符的办法回避了这个问题),当一个单词刚好跨越缓冲区边界的时候,就要先保存单词的上半部分,等继续将后续数据读到缓冲区后再将单词的下半部分连接在一起;把这个问题再扩展开来,假如每次要提取是整个行的内容,而遇到某个行的长度等于3个、4个甚至很多个缓冲区的长度,又要如何处理呢?这就已经涉及有名的边界判断问题了,实际上程序中许许多多的错误就是由此引起的。
解决这个问题最简单的办法就是一次性把文件全部读进内存,这样就不存在边界问题了,但在DOS操作系统下,程序能用的最大内存一般只有几百KB,又有多少个程序能保证自己要处理的文件一定能够一次性全部读进内存呢?毕竟包括操作系统在内的所有可寻址的地址空间只有1 MB。
在Windows中,每个进程可以自由使用的地址空间达到了2 GB,这就为将整个文件读进内存打好了基础,但程序还是需要预先分配一块大小等于文件长度的内存块,所以限制还是不少,因为在内存分配一节中已经发现,当要分配的内存块大小远远超过可用物理内存的时候,分配工作并不一定会成功。
Win32中内存映射文件的引入,使这些问题得到较好的解决,更使Win32程序员们信心大增,笔者也认为,内存映射文件是Win32 中最有实用价值的新特征之一。
10.4.1 内存映射文件简介
1. 内存映射文件的概念
内存映射文件提供了一组独立的函数,使应用程序能够通过内存指针像访问内存一样对磁盘上的文件进行访问。通过内存映射文件函数可以将磁盘上文件的全部和部分映射到进程虚拟地址空间的某个位置,一旦完成了映射,对文件内容的访问就如同在该地址区域内直接对内存访问一样简单。这样,向文件中写入数据的操作就是直接对内存进行赋值,而从文件的某个特定位置读取数据也就是直接从内存中取数据。
当内存映射文件提供了对文件某个特定位置的直接读写时,真正对磁盘文件的读写操作是由系统底层处理的。而且在写操作时,数据也并非在每次操作时都即时写入到磁盘,而是通过缓冲处理来提高系统的整体性能。
使用内存映射文件的一个好处是系统对所有的数据传输都是通过4 KB大小的数据页面来实现的,这意味着一些小的文件操作将被缓冲入一次大的操作之中,也就是说首次存取文件中某段数据的时候,会引发一次磁盘操作并将数据所在的一个页面全部读入,到以后对附近的数据进行操作时,所需的数据已经被前一次的页面操作读入到内存,无需再进行一次磁盘操作,从而提高了系统的性能。
使用内存映射文件的另一个好处是程序代码以标准的内存地址形式来访问文件数据,按页面大小周期性从磁盘读入数据的操作发生在后台,由操作系统底层来实现,这个过程对应用程序是完全透明的。虽然用内存映射文件最终还是要将文件从磁盘读入内存,实质上并没有省略掉什么操作,整体性能可能并没有获得什么提高,但是程序的结构将会从中受益,缓冲区边界等问题将不复存在。而且,对文件内容更新后的写入操作也由操作系统自动完成,操作系统判断内存中的页面是否为脏页面并仅将脏页面写入磁盘,比程序自己将全部数据写入文件的效率要高了很多。
2. 内存映射文件的实现原理
Windows使用的是页式虚拟存储管理,在Windows中,地址空间中的每个页面在任一给定时刻都可以是三种状态之一:空闲的、保留的或者是已经提交物理内存的。这些页面根据需要由操作系统交换进内存或换出内存。当内存中的某个页面不再需要时,操作系统将取消原来拥用该页面的应用程序对它的控制权,并释放该页面以供其他应用程序使用;当该页面再次成为需求页面时,它将被从物理存储器中重新读入内存,物理存储器既可以是物理内存,也可以是磁盘上的页文件。
内存映射文件的实现基于同样的原理,内存映射文件是Windows内部已有的内存管理组件的一个扩充,与实现虚拟内存一样,内存映射文件保留了一个地址空间的区域,并根据需要将物理存储器提交给该区域。它们之间的区别在于,当内存映射文件用来存取一个磁盘文件的时候,它提交的物理存储器就来自于这个文件。
不仅应用程序使用内存映射文件来访问磁盘上的数据文件,Windows操作系统同样使用内存映射文件加载和执行exe和dll文件,这样可以大大节省页文件空间和应用程序启动运行所需的时间。如图10.7所示,对于每个进程,系统将可执行的代码页提交到磁盘中的可执行文件中,而数据页(包括进程的静态数据段以及动态分配的内存)则被提交到虚拟内存中。
除了加载文件,使用内存映射文件也可以在同一台计算机上运行的多个进程之间共享数据,而且内存映射文件是多个进程互相进行通信的最有效的方法。那么如何实现数据共享呢,其实原理很简单,如图10.8所示,对于不同进程间共享的数据页,只要将它们提交到虚拟内存的同样页面就可以了,这样,当一个进程改变了数据页的内容时,通过分页映射机制,其他进程的共享数据区的内容就会同时改变,因为它们实际上存储在同一个地方。
图10.7 用内存映射文件加载执行文件
图10.8 用内存映射文件实现进程间共享数据
上页:第10章 内存管理和文件操作 · 10.3 驱动器和目录(2) 下页:第10章 内存管理和文件操作 · 10.4 内存映射文件(2)