一、概念
内存映射文件是一块预订的地址空间区域并给区域调拨物理存储器。它的作用与虚拟内存相似,但不同的是,内存映射文件是磁盘上的文件,而虚拟内存是来自系统的页交换文件。
二、用途
1、使用内存映射文件载入并运行.exe文件和动态链接库(DLL)文件。
2、可以用来访问磁盘上的数据文件,避免直接对文件进行I/O操作和对文件内容进行缓存
3、可以在同一台机器上的不同进程间共享数据,这也是最高效的方法。
三、映射到内存的可执行文件和DLL
当线程调用C r e a t e P r o c e s s时,系统将执行下列操作步骤:
1)
系统找出在调用C r e a t
e P r o c e s s时设定的. e x e文件。如果找不到这个. e x e文件,进程将无法创建,C r e a t e P r o c e s s将返回FA L S E。
2)
系统创建一个新进程内核对象。
3)
系统为这个新进程创建一个私有地址空间。
4)
系统保留一个足够大的地址空间区域,用于存放该. e x e文件。该区域需要的位置在. e x e文件本身中设定。按照默认设置, . e x e文件的基地址是0 x 0 0 4 0 0 0 0 0(这个地址可能不同于在6 4位Windows 2000上运行的6
4位应用程序的地址),但是,可以在创建应用程序的. e x e文件时重载这个地址,方法是在链接应用程序时使用链接程序的/ B A S E选项。
5)
系统注意到支持已保留区域的物理存储器是在磁盘上的. e x e文件中,而不是在系统的页文件中。
当. e x e文件被映射到进程的地址空间中之后,系统将访问. e
x e文件的一个部分,该部分列出了包含. e x e文件中的代码要调用的函数的D L L文件。然后,系统为每个D L L文件调用L o a d L i b r a r y函数,如果任何一个D L L需要更多的D L L,那么系统将调用L o a d L i b r a r y函数,以便加载这些D L L。每当调用L o a d L i b r a r y来加载一个D L L时,系统将执行下列操作步骤,它们均类似上面的第4和第5个步骤:
1)
系统保留一个足够大的地址空间区域,用于存放该D L L文件。该区域需要的位置在D L L文件本身中设定。按照默认设置, M i c r o s o f t的Visual C++ 建立的D L L文件基地址是0 x 1 0 0 0 0 0 0 0(这个地址可能不同于在6 4位Windows 2000上运行的6
4位D L L的地址)但是,你可以在创建D L L文件时重载这个地址,方法是使用链接程序的/ B A S E选项。Wi n d o w s提供的所有标准系统D L L都拥有不同的基地址,这样,如果加载到单个地址空间,它们就不会重叠。
2)
如果系统无法在该D L L的首选基地址上保留一个区域,其原因可能是该区域已经被另一个D L L或. e x e占用,也可能是因为该区域不够大,此时系统将设法寻找另一个地址空间的区域来保留该D L L。
3)
系统会注意到支持已保留区域的物理存储器位于磁盘上的D L L文件中,而不是在系统的页文件中。
如果由于某个原因系统无法映射. e x e和所有必要的D L L文件,那么系统就会向用户显示一个消息框,并且释放进程的地址空间和进程对象。
当所有的. e x e和D L L文件都被映射到进程的地址空间之后,系统就可以开始执行. e x e文件的启动代码。当. e x e文件被映射后,系统将负责所有的分页、缓冲和高速缓存的处理.
由于exe文件和DLL映射到进程空间当中,所以它们最终是用虚拟存储器进行管理的。
四、同一个可执行文件或
DLL
的多个实例之间的数据共享
1
、防止共享数据
如果一个应用程序已经运行一个实例了,当我们再为这个应用程序创建一个新的进程时,系统只不过是打开另一个内存映射视图,创建一个新的进程对象,并创建一个新的线程对象。
在编译和链接程序的时候,所有的代码和数据都被放在一个大的实体中,即
exe
文件或
DLL
库。
在
.exe
文件中,数据位于代码之后。
当应用程序的第二个实例运行时,系统只不过是把包含应用程序代码和数据的虚拟内存页面映射到第二个实例的地址空间中。
如果某一个应用程序实例需要修改数据页面的一些全局变量等时,系统是怎样进行管理的呢?
系统通过内存管理系统的写时复制特性来防止这种情况发生。
当应用程序尝试写入内存映射文件的时候,系统会首先截获此类尝试,然后为应用程序试图写入的内存页面分配一块新的内存,然后复制页面内容,最后让应用程序写入到刚分配的内存块。
2
、共享数据
在多个实例之间共享静态数据。
在编译程序的时候,编译器会将代码放在一个名叫
.text
的段中。将未经初始化的数据放在
.bss
段中,已初始化的数据放在
.data
段中。
而每个段都有一些与之相关联的属性
READ
可以从该段读取数据
WRITE
可以从该段写入数据
EXECUTE
可以执行该段内容
SHARED
该段的内容为多个实例所共享
(
即关闭了写时复制机制
)
#pragma
data_seg("Shared") //
创建一个名为shared的段,并将pragma指示符之间所有带初始值的变量放到这个新的段中。
volatile
LONG g_lApplicationInstances = 0; //
该变量必须初始化
#pragma
data_seg()//
告诉编译器停止把已初始化的变量放到shared段中,
#pragma
comment(linker, "/Section:Shared,S")
//
设置该shared段的属性, S为共享 R 为只读 W为写入
虽然可以创建共享段,但并不鼓励使用共享段。原因一是可能会导致潜在的安全漏洞。二是共享变量意味着一个应用程序中的错误可能会影响到另一个程序。