内存映射文件:最适合管理大的数据流和在多个进程间共享数据
“内存映射文件”与虚似内存相似,内存映射文件允许保留一块地址空间区域和向该区域提交物理存储,这是相同之处。不同点是:它的物理存储来自于磁盘文件,而不是系统的页面文件。一旦文件被成功映射,那么就可以像整个文件被装入内存一样来访问它。
一、使用内存映射文件的目的:
1.系统使用内存映射文件来装入和执行EXE和DLL文件。
2.可以使用内存映射文件来访问磁盘上的数据文件,这样做就不用进行文件I/D操作或缓冲文件的内容。
3.允许远行在同一台计算机上的多个进程共享数据。
二、内存映射EXE和DLL文件时系统所要做的一些事情:
1.系统定位在CreateProcess中指定的EXE文件。
2.系统创建一个新的进程内核对象。
3.系统为新进程创建一个4GB的地址空间。
4.系统在地址空间中保留了足够装下EXE文件的一块区域。该区域的位置由EXE文件中的信息来指定。
5.系统知道该保留区域的物理存储是在磁盘上的EXE文件,而不是系统中的页面文件。
三、系统通过“内存管理系统和写拷贝特性”来管理应用程序对其数据页中的全局变量进行写操作。每当应用程序试图写内存映射文件时,系统会捕捉到该请求,拷贝要写的内存页中的内容到一个新分配的内存空间,然后允许应用程序对该块内存进行写操作。
图中的“实例1”要对它的“数据页2”中的数据进行写操作,为了不影响其它实例对该页的访问,系统使用“内存管理系统和写拷贝特性”来为实例1新分配一个“新页”。图中的新页就是系统为它新分配的一个对应于数据页2 的一个新页(它与数据页2的内容是相同的),实例1就可以对该新页进行写操作了,这样做不会影响实例2对内存中数据2的访问。
四、使用内存映射文件:
要使用内存映射文件必须执行下面几步:
1.创建或打开一个文件内核对象(用来标识磁盘上的想用来做为内存映射文件的文件)。创建或打开一个文件内核对象的方法:使用函数CreateFile,函数的原形:
HANDLE CreateFile(LPCSTR lpFileName,DWORD dwDesiredAccess,DWORD dwShareMode,LPSECURITY_ATTRIBUTES lpSecurityAttributes,DWORD dwCreationDisposition,DWORD dwFlagsAndAttributes,HANDLE hTemplateFile);
注意在16位机上使用OpenFile来打开文件,而在32位中都是使用CreateFile来打开文件,但是在32位机上还保留着OpenFile函数,见义在Win32中避免使用OpenFile函数。如果CreateFile成功地打开文件或创建了文件,将返回一个文件句柄,否则将返回INVALID_HANDLE_VALUE。CreateFile是告诉操作系统文件映射的物理存储的位置(如:硬盘上、网络上、CD-ROM上等)。
2.创建一个文件映射内核对象(用于告诉系统文件的大小和访问文件的方法)。创建文件映射内核对象方法:使用CreateFileMapping函数。它的原形如下:
HANDLE CreateFileMapping(HANDLE hFile,LPSECURITY_ATTRIBUTES lpsa,DWORD fdwProtect,DWORD dwMaximumSizeHigh,DWORD dwMaximumSizeLow,LPSTR lpszMapName);各参数说明:
hFile:是要映射到进程地址空间的文件句柄。该句柄由CreateFile函数返回。调用CreateFileMapping函数时,必须指定参数fdwProtect ,该参数用于指定保护属性(属性值可以为:PAGE_READONLY、PAGE_READWRITE、PAGE_WRITECOPY等)。参数dwmaximumSizeHigh和dwMaximumSizeLow告诉系统文件的最大字节大小,前一个指定高32位,后一个指定低32位,对于4GB以下的文件,dwmaximumSizeHigh总是为0。参数lpszMapName是文件映射对象的名字。
如果系统不能创建文件映射对象,CreateFileMapping函数将返回NULL。
知识复习资料:PE,Protable executable,是Win32可移植的可执行文件。
下面列举一例来说明CreateFile和CreateFileMapping两个函数的用法:
int WINAPI WinMain(HINSTANCE hinstExe,HINSTANCE hinstExePrev,LPSTR pszCmdLine,int nCmdShow){
HANDLE hfile = CreateFile(“C:MMFTest.dat”,GENERIC_READ|GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
HANDLE hfilemap = CreateFileMapping(hfile,NULL,PAGE_READWRITE,0,100,NULL);
CloseHandle(hfilemap);
CloseHandle(hfile);
Return(0);
}
3.告诉系统把文件映射对象的全部或部分映射到进程的地址空间。
在创建完文件映射对象后,还要让系统保留一块地址空间区域,将文件数据作为物理存储提交到该区域。这一过程通过调用函数:MapViewOfFile实现:
LPVOID MapViewOfFile(HANDLE hFileMappingObject,DWORD dwDesiredAccess,DWORD dwFileOffsetHigh,DWORD dwFileOffsetLow,DWORD dwNumberOfBytesToMap);
下面对各个参数依次进行说明:hFileMappingObject标识了文件映射对象的句柄,它是由CreateFileMapping或OpenFileMapping的调用返回的。参数dwDesiredAccess标识了数据可以怎样被访问(该参数值可以为:FILE_MAP_WRITE、FILE_MAP_READ、FILE_MAP_ALL_ACCESS、FILE_MAP_COPY)。
当向进程地址空间映射文件时,不必一次映射整个文件。被映射到进程地址空间的那部分文件被称为视图。当把文件的视图映射到进程的地址空间时,必须指定两件事,一、必须告诉系统,数据文件中的哪个字节应当被映射为视图中的第一个字节。这是通过使用参数dwFileOffsetHigh和dwFileOffsetLow来进行设置的。前者用于存储64位偏移量(因为Win32支持的文件大小为180亿GB,所以偏移量就是64位)中的高32位,后者用于存储低32位。最后一个参数dwNumberOfBypesToMap是用来告诉系统要有多少数据文件要映射到进程地址空间中。如果该值为0那意味着系统将试图将从指定的偏移量开始到文件尾的视图进行映射。
注意:文件的偏移量必须是系统分配单元的整数倍。Win32所分配的单元是64KB。
下面举例说明:
HANDLE hFile,hFileMapping;
BYTE bSomeByte,*pbFile;
……
hFile = CreateFile(lpszName,GENRIC_READ|GENRIC_WRITE,0
,NULL,OPEN_ALAWYS,FILE_ATTRIBUTE_NORMAL,NULL);
hFileMapping = CreateFileMapping(hFile,NULL,PAGE_WRITECOPY,0,0,NULL);
pbFile = (PBYTE)MapViewOfFile(hFileMapping,FILE_MAP_COPY,0,0,0);
bSomeByte = pbFile[0];
pbFile[0] = 0;
pbFile[1] = 0;
UnMapViewOfFile(pbFile);
CloseHandle(hFileMapping);
CloseHandle(hFile);
4.在使用完内存映射文件后要执行下面几步来清除:
1.告诉系统文件映射对象从进程的地址空间解除映射。当不在需要在进程的地址空间区域保留文件的数据映射时,可以使用函数BOOL UnMapViewOfFile(LPVOID lpBaseAddress);来释放该区域。参数lpBaseAddress用于指定要释放区域的基地址。注意:该参数的值必须与调用MapViewOfFile函数所返回的值相同。另外一个需要注意的是:在系统使用文件视图时,由于速度的原因吧,系统缓冲了文件的数据页,系统并没有及时将你对文件视图中的修改写入磁盘。如果想让你对文件视图的修改立即写到磁盘,可以调用函数:FlushViewOfFile来实现。不过,当调用函数UnMapViewOfFile后,系统会自动将你对文件视图的修改写入磁盘,也就是说当你需要手动对文件视图进行写磁盘操作时,才用到函数FlushViewOfFile。该函数的原型为:BOOL FlushViewOfFile(LPVOID lpBaseAddress,DWORD dwNumberOfBytesToFlush);其中参数lpBaseAddress是调用MapViewOfFile时所返回的被映射的视图地址,dwNumberOfBytesToFlush是要写入磁盘的字节数。
2.关闭文件映射内核对象。
3.关闭文件内核对象。
使用CloseHandle函数来关闭各个对象。如果忘记关闭这些对象,就会造成资源句柄。当然,就算你不关闭对象,当进程结束时,系统会自动关闭所有你忘记关闭的对象。下面简单说明一个文件从打开、使用、到关闭这一过程:
HANDLE hFile,hFileMapping;
PVOID pFile;
hFile = CreateFile();//该函数告诉系统“要打开或创建的文件在哪里”
hFileMapping = CreateFileMapping(hFile,….);//告诉系统“要打开或创建的文件有多大”
pFile = MapViewOfFile(hFileMapping,…);//“将文件视图映射到进程的地址空间”
pFile = MapViewOfFile(hFileMapping,…);//“将文件视图映射到进程的地址空间”
//现在就可使用“内存映射文件”了
UnMapViewOfFile(pFile);//释放进程地址空间上的文件映射
CloseHandle(hFileMapping);//关闭文件映射内核对象句柄
CloseHandle(hFile);//关闭文件内核对象句柄
…..
上面这段代码就是使用文件映射的方法来实现“虚似内存”的整个过程。不过有一点需要注意:在调用CreateFileMapping和mapViewOfFile这两个函数时分别增加了文件对象和文件映射对象的引用计数。
在使用内存映射对象时,通常是打开文件,创建文件映射对象,然后使用文件映射对象向进程的地址空间映射一个文件的数据的视图。
五、用内存映射文件处理大文件:如何来处理大于4GB的文件呢?首先,从文件开始处映射文件的一部分得到一个视图,当访问完这块映射文件的视图后,可以对它解除映射,然后从文件的更深的偏移量来映射一个新的视图,继续重复这个过程直到访问完整个文件为止。
六、内存映射文件和一致性:Jeffrey Richer推荐使用CreateFile来打开文件时,指定0做为fdwShareMode参数的值,这样会告诉系统该进程将独立访问文件,其它进程不能访问该文件。只读文件不存在一致性问题。决不应使用内存文件在网络上共享可写的文件,因为系统不能保证数据视图的一致性。
七、内存映射文件和Win32实现:在Windows95和Windows NT下对对内存映射文件实现是不同的。在Windows95下所有进程调用MapViewOfFile返回的地址是相同的,而在Windows NT下是不同的,即使两个进程映射的是同一个文件映射对象的映射视图。
1.使用内存映射文件在进程间共享数据:在Win16位下共享数据太简单了,应用程序经常操纵不属于它的数据,而导制其它应用程序崩溃。不过在Win32下共享数据,实际上都是通过“内存映射文件来实现进程间共享数据”,诸如SendMessage(包括使用SendMessage函数传递Win32的WM_COPYDATA消息)和PostMessage等技术共享数据,其本质上都是使用“内存映射文件的机制来实现的”。使用内存映射文件来共享数据实际上是:让两个或多个进程映射同一个文件对象映射的视图,也就是说进程们在共享同一个物理存储页。
2.下面说明在多个进程间共享文件映射对象的一些技术:
一、使用CreateFileMapping()函数:在调用该函数时,可以为文件映射对象指定一个名字,如果这个名称的文件映射对象不存在的话,系统就会创建一个新的文件映射对象,如果这个名称的文件映射对象已经存在了,那么CreateFileMapping不创建新的文件对象,而是增加这个文件映射对象的使用计数,并返回一个标识该文件映射对象的一个进程相关句柄。
二、使用OpenFileMapping()函数:在使用这个函数前,系统中的某个进程必须调用CreateFileMapping函数来产生一个文件映射对象,之后其它进程才可以使用OpenFileMapping函数来共享数据。该函数返回第一个进程创建的文件映射对象的进程相关句柄。在成功调用OpenFileMapping函数后,就可以使用MapViewOfFile函数或MapViewOfFileEx函数将数据映射到自己进程的地址空间里了。
三、继承:利用一个进程来创建一个可继承的文件映射对象句柄,而后派生出一个新的子进程来继承父进程的文件映射对象句柄。(注:这样子进程的文件映射对象句柄与父进程的文件映射对象句柄是相同的)。创建一个可继承的文件映射对象句柄方法:在调用CreateFileMapping函数时,要指定一个结构SECURITY_ATTRIBUTES,并如下初始化该结构:
SECURITY_ATTRIBUTES sa;
Sa.nLength = sizeof(sa);
Sa.lpSecurityDescriptor = NULL;
Sa.bInheritHandle = True;
hFileMap = CreateFileMapping(hFile,&sa,…);
之后当父进程创建子进程时,它必须在调用CreateProcess时,将其参数fInheritHandle的值设置为TRUE,这样做后就增加文件映射对象的使用计数。新进程可以使用该文件映射对象的句柄了,但是新的进程不知道这个句柄的值是多少。可以发送消息或传递命令行参数来使子进程得到该句柄。
内存映射文件
最新推荐文章于 2023-05-19 21:39:51 发布