Windows API笔记(一)内核对象
Windows API笔记(二)进程和进程间通信、进程边界
Windows API笔记(三)线程和线程同步、线程局部存储
Windows API笔记(四)win32内存结构
Windows API笔记(五)虚拟内存
Windows API笔记(六)内存映射文件
Windows API笔记(七)堆
Windows API笔记(八)文件系统
Windows API笔记(九)窗口消息
Windows API笔记(十)动态链接库
Windows API笔记(十一)设备I/O
使用内存映射文件主要有3个目的:
当线程调用CreateProcess时,系统执行下列步骤:
系统定位在CreateProcess中指定的exe文件。如果找不到,就不会创建进程,CreateProcess返回FALSE。
系统创建一个新的进程内核对象。
系统为新进程创建一个4GB的地址空间。
系统在地址空间中保留了足够装下exe文件的一块区域。该区域的位置是在exe文件中指定的。默认,exe文件的基本地址是0x00400000。不过,在链接程序时,可以使用链接器的/BASE选项来重载这一地址。
系统注意到支持该保留区域的物理存储是磁盘上的exe文件,而不是系统的页面文件。在exe文件被映射到进程的地址空间之后,系统访问exe文件里的某一节,那里列出了包含exe调用的函数的dll文件。然后系统为每个dll调用LoadLibrary,如果某个dll还需要其他的dll,系统也会调用LoadLibrary来加载这些dll。每当调用LoadLibrary来加载一个dll时,系统执行蕾仕于上面的第4和5步的行动:
如果系统因故不能映射exe和所需的dll,CreateProcess将向调用者返回FALSE,可以调用GetLastError来弄清进程为什么不能被创建。
在所有的exe和dll文件被映射进进程的地址空间之后,系统就能开始执行exe文件的启动代码了。在exe我呢见被映射之后,系统会负责所有的页面、缓冲和缓存。例如,如果exe中的代码跳到了还没有装入内存的一条指令的地址,会产生一个错误。系统检测到错误后,会自动把该代码所在页从我呢见的映象装入到RAM的页中。而后系统把RAM页映射到进程地址空间中的正确位置。允许线程继续执行,就好像代码页早已被装入一样。当然,这些对程序是不可见的。每当进程中的线程试图访问的数据或代码没有被装入RAM时,就会重复该过程。
系统通过使用该内存管理系统和写拷贝特性来防止某一实例改变了共享的静态数据导致所有实例的内存内容都被改变。每当应用试图写它的内存映射文件时,系统捕捉到请求,对包含有被写的内存的页分配一块新内存,拷贝页的内容,然后允许应用程序写这块新分配的内存。这样,其他的实例就不会受到影响
可以在exe或dll中创建能被所有实例共享的全局遍历。简单的说,该方法要求使用#pragma data_seg()编译器指令将要共享的变量放在它们自己的节中。然后必须使用/SECTION: name,attributes开关来告诉链接器要让该节中的数据被所有的实例或文件的映象共享。
在exe或dll文件被加载时,操作系统自动使用前一节中讲述过的技术。不过,还可能在进程的地址空间中映射一个数据文件。这使得操纵大数据流非常方便。
为了理解这样使用内存映射文件的强大功能,让我们看一下实现一个程序将文件中的所有字节倒放的4中可能的方法。
流程:
缺点:
流程:
缺点:
流程:
优点:
缺点:
使用内存映射文件倒置文件内容。
流程:
优点:
缺点:
要使用内存映射文件,必须执行下列3步:
使用完内存映射文件后,必须执行下列3步进行清理工作:
调用CreateFIle创建或打开文件内核对象:
HANDLE
CreateFileA(
_In_ LPCSTR lpFileName,
_In_ DWORD dwDesiredAccess,
_In_ DWORD dwShareMode,
_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
_In_ DWORD dwCreationDisposition,
_In_ DWORD dwFlagsAndAttributes,
_In_opt_ HANDLE hTemplateFile
);
dwDesiredAccess 以何种方式访问文件,取值范围:
值 | 含义 |
---|---|
0 | 不能读写文件的内容 |
GENERIC_READ | 可读 |
GENERIC_WRITE | 可写 |
GENERIC_READ|GENERIC_WRITE | 可读可写 |
对于内存映射文件,必须以只读或读写方式打开文件。
dwShareMode 如何共享该文件,取值范围:
值 | 含义 |
---|---|
0 | 不共享,独占文件 |
FILE_SHARE_READ | 读共享,其他以带有写的方式打开文件都会失败 |
FILE_SHARE_WRITE | 写共享,其他以带有读的方式打开文件都会失败 |
FILE_SHRE_READ|FILE_SHARE_WRITE | 读写共享,其他打开文件的尝试都会成功 |
调用CreateFile是告诉操作系统文件映射的物理存储的位置。传送的路径名指出了支持文件映射的物理存储的确切位置,是在硬盘上、网络上、CD_ROM盘上等等。现在,必须告诉系统文件映射对象需要多少物理存储。这时调用CreateFileMapping:
HANDLE
CreateFileMappingA(
_In_ HANDLE hFile,
_In_opt_ LPSECURITY_ATTRIBUTES lpFileMappingAttributes,
_In_ DWORD flProtect,
_In_ DWORD dwMaximumSizeHigh,
_In_ DWORD dwMaximumSizeLow,
_In_opt_ LPCSTR lpName
);
第1个参数hFile标识了想要映射到进程的地址空间中的文件的句柄。该句柄是由CreateFile创建的。
当创建文件映射对象时,系统并不保留地址空间中的区域,并把文件的内存映射到该区域。不过,当系统要向进程的地址空间映射存储时,系统必须知道赋给物理存储页的保护属性。dwProtect允许指定保护属性,大多数时候指定的是下表中给出的3个保护属性之一:
保护属性 | 含义 |
---|---|
PAGE_READONLY | 文件映射对象为只读,必须向CreateFile传递GENERIC_READ |
PAGE_READWRITE | 文件映射对象为可读可写,必须向CreateFile传递GENERIC_READ|GENERIC_WRITE |
PAGE_WRITECOPY | 文件映射对象为写拷贝,可读可写;写时会创建一份页面的私有拷贝。必须向CreateFile传递GENERIC_READ或GENERIC_READ|GENERIC_WRITE |
dwMaximumSizeHigh和dwMaximumSizeLow是最重要的参数。因为该函数的主要目的是确保对于文件映射对象有足够的物理存储。这两个参数告诉系统文件的最大字节大小。使用两个32位值是因为Win32支持64位的文件大小,dwMaximumSizeLow指定低32位,dwMaximumSizeHigh指定高32位。对于4GB以下的文件,dwMaximumSizeHigh总是为0。
如果在调用CreateFileMapping时传递PAGE_READWRITE标志,系统将会确保在磁盘上的相关数据文件的大小至少是由dwMaximumSizeHigh和dwMaximumSizeLow所给出的大小。如果文件比指定大小要小,将会增大磁盘上的文件。
#include
int main()
{
// 创建新文件
HANDLE hFile = CreateFile("MMFTest.dat",GENERIC_READ|GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
// 将使文件大小为100byte
HANDLE hFilemap = CreateFileMapping(hFile,NULL,PAGE_READWRITE,0,100,NULL);
CloseHandle(hFilemap);
CloseHandle(hFile);
return 0;
}
最后一个参数lpName是一个字符串,用于给文件映射对象命名。该名字是用来与其他进程共享该对象的。不需要共享时,该参数一般为NULL。
在创建文件映射对象后,还要让系统保留一块地址空间区域,将文件数据作为物理存储提交到该空间。这通过调用MapViewOfFile来实现:
LPVOID
MapViewOfFile(
_In_ HANDLE hFileMappingObject,
_In_ DWORD dwDesiredAccess,
_In_ DWORD dwFileOffsetHigh,
_In_ DWORD dwFileOffsetLow,
_In_ SIZE_T dwNumberOfBytesToMap
)