第17章 内存映射文件学习笔记
一、内存映射文件#
内存映射文件的物理存储器来自磁盘上已有的文件,而不是来自系统的页交换文件。
内存映射文件主要用于以下三种情况:
1.系统使用内存映射文件来载入并运行.exe和动态链接库(DLL)文件。
2.开发人员可以用内存映射文件来访问磁盘上的数据文件。
3.通过使用内存映射文件,我们可以在同一台机器的不同进程之间共享数据。
二、映射到内存的可执行文件和DLL#
当一个线程在调用CreateProcess的时候,系统会执行以下步骤:
1.系统会先确定CreateProcess所指定的可执行文件所在的位置。
2.系统创建一个新的进程内核对象。
3.系统为新进程创建一个私有地址空间。
4.系统预订一块足够大的地址空间来容纳.exe文件。
5.系统会对地址空间区域进行标注,表明该区域的后备物理存储器来此磁盘上的.exe文件,而并非来自系统的页交换文件。
当系统把.exe文件映射到进程的地址空间之后,会访问.exe文件中的一个段,这个段列出了一些DLL文件,它包含该.exe文件调用到的函数。系统每次调用LoadLibrary来载入一个DLL的时候,执行的操作与刚才列出的第4步和第5步相似。
把所有的.exe文件和DLL文件都映射到进程的地址空间之后,系统会开始执行.exe文件的启动代码。当完成对.exe文件的映射后,系统会负责所有的换页(paging)、缓存(buffering)、以及高速缓存(caching)操作。
1、同一个可执行文件或DLL的多个实例不会共享静态数据#
如果一个应用程序已经在运行,那么当我们为这个应用程序创建一个新的进程时,系统只不过是打开另一个内存映射视图,创建一个新的进程对象,并(为主线程)创建一个新的线程对象。
系统通过内存管理系统的写时复制特性来防止这种情况的发生。任何时候当应用程序试图写入内存映射文件的时候。系统会首先截获此类尝试,接着为应用程序试图写入内存的页面分配一块新的内存,然后复制页面内容,最后让应用程序写入到刚分配的内存块。最终的结构就是,应用程序的其他实例不会受到任何影响。
2、在同一个可执行文件或DLL的多个实例间共享静态数据#
默认情况下,同一个.exe文件或DLL的多个实例之间不会共享全局或静态数据,这样的情况时最保险的。但是,有些情况下在同一个.exe文件或DLL的多个实例之间共享一个变量不仅有用,而且方便。
除了使用编译器和链接器所创建的标准段之外,我们还可以在编译的时候使用下面的编译器指示符来创建自己的段:
#pragma data_seg("Shared")
LONG test = 0;
#pragma data_seg()
注意:编译器只会将已初始化的变量保存在这个段中。假如我们从刚才的代码中去掉初始化的部分,那么编译器就会将该变量放到Shared段以外的其他段中。
默认情况下,同一个.exe或DLL的每个实例都会有自己的一组变量。但是对那些想要在多个实例共享的变量,我们可以把它们放到一个单独的段中。一旦把变量放到单独的段中,系统就不会再在同一个可执行或DLL的每个实例中为他们创建新的实例了。
我们还需要告诉链接器要共享这个段的变量
#pragma comment(linker,"/SECTION:Shared,RWS")
二、映射到内存的数据文件(暂时跳过)#
1、方法1:一个文件,一块缓存#
2、方法2:两个文件,一块缓存#
3、方法3:一个文件,两块缓存#
4、方法4:一个文件,零个缓存#
三、使用内存映射文件#
要使用内存映射文件,需要执行下面三个步骤:
(1)创建或打开一个文件内核对象,该对象标识了我们想要用作内存映射文件的那个磁盘文件。
(2)创建一个文件映射内核对象来告诉系统文件的大小以及我们打算如何访问文件。
(3)告诉系统把文件映射对象的部分或全部映射到进程的地址空间中。
用完内存映射文件之后,必须执行下面三个步骤来做清理工作。
(1)告诉系统从进程地址空间中取消对文件映射内核对象的映射
(2)关闭文件映射内核对象
(3)关闭文件内核对象
1、第一步:创建或打开文件内核对象#
通过使用CreateFile函数来创建或打开一个文件内核对象
2、第二步:创建文件映射内核对象#
调用CreateFile是为了告诉操作系统文件映射的物理存储器所在的位置。传入的路径是文件在磁盘(也可以是网络或光盘)上所在的位置,文件映射对象的物理存储器来自该文件。现在我们必须告诉文件映像对象需要多大的物理存储器。所以我们必须使用CreateFileMapping
HANDLE CreateFileMapping(
[in] HANDLE hFile,//需要映射到进程地址空间的文件的句柄
[in, optional] LPSECURITY_ATTRIBUTES lpFileMappingAttributes,//默认为NULL
[in] DWORD flProtect,
[in] DWORD dwMaximumSizeHigh,
[in] DWORD dwMaximumSizeLow,
[in, optional] LPCWSTR lpName
);
3、第三步:将文件的数据映射到进程的地址空间#
在创建了文件映射对象之后,还需要为文件的数据预订一块地址空间区域并将文件的数据作为物理存储器调拨给区域,这里通过MapViewOfFile来实现。
LPVOID MapViewOfFile(
[in] HANDLE hFileMappingObject,//文件映射对象的句柄
[in] DWORD dwDesiredAccess,
[in] DWORD dwFileOffsetHigh,
[in] DWORD dwFileOffsetLow,
[in] SIZE_T dwNumberOfBytesToMap
);
4、第四步:从进程的地址空间撤销对文件数据的映射#
不再需要把文件的数据映射到进程的地址空间中,可以调用下面的函数来释放内存区域
BOOL UnmapViewOfFile(PVOID pvBaseAddress)//这个函数唯一的参数pvBaseAddress用来指定区域的基地址,它必须和MapViewOfFile的返回值相同。
5、第五步和第六步:关闭文件映射对象和文件对象#
CloseHandle(hFileMapping);
CloseHandle(hFile);
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!