【文起】从蟹儿家回来了,期待跟蟹儿一起的生活,这周五一起回家,幸福。谢谢宝贝儿一如既往的对我的鼓励和支持。
内存映射文件
一、
内存映射文件可以用来保留一个地址空间的区域,并将物理存储器提交给该区域。
内存映射文件可以用于3个不同的目的:
1、 系统使用内存映射文件,以便加载和执行.exe和DLL文件。这样可以大大节省页文件空间和应用程序启动运行所需的时间。
2、 可以使用内存映射文件来访问磁盘上的数据文件,这样可以不必对文件进行I/O操作,并且可以不必对文件内容进行缓存。
3、 可以使用内存映射文件,使同一台计算机上运行的多个进程能相互之间共享数据。
Windows通过内存映射文件来实现进程之间的数据通信。
二、
线程调用CreateProcess时,执行步骤:
1、 系统找出调用CreateProcess时设定的.exe文件,如果找不到这个.exe文件,进程将无法创建,函数返回FALSE;
2、 系统创建一个新进程的内核对象;
3、 系统为这个新进程创建一个私有地址空间;
4、 系统保留一个足够大的地址空间区域,用于存放该.exe文件;
5、 系统注意到支持已保留区域的物理存储器是在磁盘上的.exe文件中,而不是在系统的页文件中。
.exe文件被映射到进程的地址空间中之后,系统将访问.exe文件的一个部分,该部分列出包含.exe文件中的代码要调用的函数的DLL文件。然后系统为每个DLL文件调用LoadLibrary函数。步骤为:
1、系统保留一个足够大的地址空间区域,用户存放该DLL文件。区域需要的位置在DLL文件本身中设定,Windows提供的所有标准系统DLL都拥有不同的基地址,这样加载到单个地址空间,它们不会重叠。
2、如果系统无法在该DLL的首选基地址上保留一个区域。原因可能是该区域已经被另一个DLL或.exe占用,也可能是因为该区域不够大,此时系统将设法寻找另一个地址空间的区域来保留该DLL。如果DLL无法加载到它的首选基地址,需要在DLL中执行某些再定位操作。
如果系统无法映射.exe和所有必要的DLL文件,那么系统就会向用户显示一个消息框,并且释放进程的地址空间和进程对象。
当所有的.exe和DLL文件都被映射到进程的地址空间之后,系统就可以开始执行.exe文件的启动代码。如果.exe文件中的代码使它跳转到一个尚未加载到内存的指令地址,那么就会出现一个错误,系统能够发现这个错误,并自动将这页代码从该文件的映像加载到一个RAM页面,然后系统将这个RAM页面映射到进程的地址空间中相应的位置,且让线程继续运行。这些操作都是透明的。
三、可执行文件或DLL的多个实例不能共享静态数据
当为正在运行的应用程序创建新进程时,系统将打开用于标识可执行文件映像的文件映射对象的另一个内存映射视图,并创建一个新进程对象和一个新线程对象。通过使用内存映射文件,同一个应用程序的多个正在运行的实例就能够共享RAM中的相同代码和数据。
如果有一个进程A修改了数据,那么系统会新建一个数据页面,将其指向进程A。没做修改的进程仍然指向旧的数据页面。
四、在可执行文件或DLL的多个实例之间共享静态数据
全局数据和静态数据不能被同一个.exe或DLL文件的多个映像共享。
每个.exe或DLL文件的映像都由许多节组成。每个标准的节的名字均以圆点开头,编译器将所有代码放入一个名为.text的节中,未经初始化的数据放入一个.bss节,已经初始化的所有数据放入.data节中。
我们可以通过下面的函数,创建一个自己的节,并将变量放入其中。
//创建一个新的节Shared
#pragma data_seg("Shared")
//将已经初始化的变量放入节中
ULONG g_lInstanceCount = 0;
//没有初始化的变量不会放入节中
ULONG g_lInstanceCountTest;
//停止将变量放入Shared节中
#pragma data_seg();
我们可以通过如下代码,可以将没有初始化的变量也放入Shared节中
__declspec(allocate("Shared")) intc;
这样操作的原因是,要在.exe和DLL文件的多个映像之间共享这些变量。我们还需要告诉链接程序,某个节中的变量是需要加以共享的。链接程序的命令行上的/SECTION开关:
/SECTION:name,attributes
也可以使用下面的句法将链接程序开关嵌入源代码中:
#pragma comment(linker,"/SECTION:Shared,RWS")
可以创建共享节,但是有两个原因Microsoft不鼓励使用共享节。一:这种方法可能破坏系统的安全性。二:共享变量意味着一个应用程序中的错误可能影响另一个程序的运行。
五、使用内存映射文件
若要使用内存映射文件,必须执行下列操作:
1、 创建或打开一个文件内核对象,该对象用于标识磁盘上想用作内存映射文件的文件
2、 创建一个文件映射内核对象,告诉系统该文件的大小和你打算如何访问该文件。
3、 让系统将文件映射对象的全部或一部分映射到你的进程地址空间中。
完成使用时,执行下面步骤将其清除:
1、 告诉系统从你的进程的地址空间中撤销文件映射内核对象的映像
2、 关闭文件映射内核对象
3、 关闭文件内核对象
Ø 创建或打开一个文件内核对象:
总是需要调用CreateFile函数,如果函数成功创建或打开指定的文件,便返回一个文件内核对象的句柄,否则返回INVALID_HANDLE_VALUE(返回句柄的大多数Windows函数失败时都是返回NULL)
Ø 创建一个文件映射内核对象:
调用CreateFile函数,可以将文件映像的物理存储的位置告诉操作系统。即指明的路径名用于指明支持文件映射的物理存储器在磁盘上的确切位置。此时需要告诉系统,文件映射对象需要多少物理存储器。调用CreateFileMapping函数
Ø 将文件数据映射到进程的地址空间:
创建文件映射对象之后,必须让系统为文件的数据保留一个地址空间区域,并将文
件的数据作为映射到该区域的物理存储器进行提交。可以调用MapViewOfFile函数来进行这项操作。我们不必一次性映射整个文件,可以通过参数传入映射的第一个字节以及总共需要映射多少个字节
Ø 从进程的地址空间中撤销文件数据的映像
当不再需要保留映射到你的进程地址空间区域中的文件数据时,可以通过调用下面
的函数将释放:BOOL UnmapViewOfFile(PVOID pvBaseAddress);
六、使用内存映射文件处理大文件
每次映射一小部分文件数据,完成访问后取消它的映像,然后再继续映射更深的视图。
七、内存映射文件与数据视图的相关性
系统允许同时映射同一个文件,如一个进程映射文件的前10kb,另一个进程映射文件的前200kb。但是一个进程修改了数据之后,另一个进程同样受影响。因为虽然被映射到多个进程的虚拟地址空间,但是系统只将数据放在单个RAM页面上。如果不想其他进程映射该文件,那么在CreateFile时,将dwShareMode参数设置为0.
八、设定内存文件的基地址
使用函数MapViewofFileEx,可以将文件映射到一个特定的地址。这样做的目的:
进程A建立了内存映射文件的链表,然后共享给进程B。进程B可能将文件映射在不同的内存地址空间,通过内存映射文件找到第一个元素的地址,检索下一个元素的地址时,该地址并不是链表的下一个元素地址。
九、使用内存映射文件在进程中共享数据
同一台设备上,不通进程之间共享数据,最好的方法就是使用内存映射。他们将共享物理存储器的同一个页面。我们可能不需要在磁盘上创建一个文件,Windows提供的方法是,不调用CreateFile函数,直接使用CreateFileMapping函数,并传入INVALID_HANDLE_VALUE作为hFile的参数。这样系统不会创建物理存储器,相反,系统从页文件中提交物理存储器。
所以,CreateFile函数调用完之后一定要记得检查返回值。如果调用失败,没有检查返回值就继续写CreateFileMapping函数,那么可能导致的结果是,打开文件映射失败,但是映射到RAM页成功。
十、稀疏提交的内存映射文件
有时候,我们想根据自己的情况使用物理存储器,而不是一次性提交所有内存。CreateFileMapping函数提供这样一个功能。参数fdwProtect传递为SEC_RESERVE或SEC_COMMIT标志(只有当创建由系统的页文件支持的文件映射对象时才有意义)。SEC_COMMIT标志能使CreateFileMapping从系统的页文件中提交存储器,如果传递SEC_RESERVE标志,系统并不从它的页文件中提交物理存储器,只是返回文件映射对象的一个句柄。NTFS文件系统提供了对稀疏文件的支持。
【文尾】文章讲述是内存映射的知识,文中涉及的3个举例代码会稍侯编写并上传。如果对您有帮助,请留下对我和蟹儿的祝福,多谢。