`
wrf44wrf
  • 浏览: 14947 次
社区版块
存档分类
最新评论

内存映射文件

阅读更多

内存映射文件
2010年07月30日
  线程的堆栈
  每当创建一个线程时,系统就会为线程的堆栈(每个线程有它自己的堆栈)保留一个堆栈空间区域,并将一些物理存储器提交给这个已保留的区域。按照默认设置,系统保留1 MB的地址空间并提交两个页面的内存。但是,这些默认值是可以修改的,方法是在你链接应用程序时设定M i c r o s o f t的链接程序的/ S TA C K选项: 当创建一个线程的堆栈时,系统将会保留一个链接程序的/ S TA C K开关指明的地址空间区域。但是,当调用C r e a t e T h r e a d或_ b e g i n t h r e a d e x函数时,可以重载原先提交的内存数量。这两个函数都有一个参数,可以用来重载原先提交给堆栈的地址空间的内存数量。如果设定这个参数为0,那么系统将使用/ S TA C K开关指明的已提交的堆栈大小值。后面将假定我们使用默认的堆栈大小值,即1 MB的保留区域,每次提交一个页面的内存。 16.2 C/C++运行期库的堆栈检查函数 对文件进行操作几乎是所有应用程序都必须进行的,并且这常常是人们争论的一个问题。应用程序究竟是应该打开文件,读取文件并关闭文件,还是打开文件,然后使用一种缓冲算法,从文件的各个不同部分进行读取和写入呢?M i c r o s o f t提供了一种两全其美的方法,那就是内存映射文件。
  与虚拟内存一样,内存映射文件可以用来保留一个地址空间的区域,并将物理存储器提交给该区域。它们之间的差别是,物理存储器来自一个已经位于磁盘上的文件,而不是系统的页文件。一旦该文件被映射,就可以访问它,就像整个文件已经加载内存一样。
  内存映射文件可以用于3个不同的目的:
  系统使用内存映射文件,以便加载和执行. e x e和D L L文件。这可以大大节省页文件空间和应用程序启动运行所需的时间。
  可以使用内存映射文件来访问磁盘上的数据文件。这使你可以不必对文件执行I / O操作,并且可以不必对文件内容进行缓存。
  可以使用内存映射文件,使同一台计算机上运行的多个进程能够相互之间共享数据。Wi n d o w s确实提供了其他一些方法,以便在进程之间进行数据通信,但是这些方法都是使用内存映射文件来实现的,这使得内存映射文件成为单个计算机上的多个进程互相进行通信的最有效的方法。 [/b][b] 17.3 使用内存映射文件
  
若要使用内存映射文件,必须执行下列操作步骤:
  1) 创建或打开一个文件内核对象,该对象用于标识磁盘上你想用作内存映射文件的文件。
  2) 创建一个文件映射内核对象,告诉系统该文件的大小和你打算如何访问该文件。
  3) 让系统将文件映射对象的全部或一部分映射到你的进程地址空间中。
  当完成对内存映射文件的使用时,必须执行下面这些步骤将它清除:
  1) 告诉系统从你的进程的地址空间中撤消文件映射内核对象的映像。
  2) 关闭文件映射内核对象。
  3) 关闭文件内核对象。
  下面将详细介绍这些操作步骤。 17.3.1 步骤1:创建或打开文件内核对象
  
若要创建或打开一个文件内核对象,总是要调用C r e a t e F i l e函数:
  HANDLE CreateFile( 
  PCSTR pszFileName, 
  DWORD dwDesiredAccess, 
  DWORD dwShareMode, 
  PSECURITY_ATTRIBUTES psa, 
  DWORD dwCreationDisposition, 
  DWORD dwFlagsAndAttributes, 
  HANDLE hTemplateFile); C r e a t e F i l e函数拥有好几个参数。这里只重点介绍前3个参数,即p s z F i l e N a m e,d w D e s i r e d A c c e s s和d w S h a r e M o d e。
  你可能会猜到,第一个参数p s z F i l e N a m e用于指明要创建或打开的文件的名字(包括一个选项路径)。第二个参数d w D e s i r e d A c c e s s用于设定如何访问该文件的内容。可以设定表1 7 - 3所列的4个值中的一个。
  表17-3 dwDesiredAccess的值  值 含义 
  0 不能读取或写入文件的内容。当只想获得文件的属性时,请设定0 
  G E N E R I C _ R E A D 可以从文件中读取数据 
  G E N E R I C _ W R I T E 可以将数据写入文件 
  GENERIC_READ |GENERIC_WRITE 可以从文件中读取数据,也可以将数据写入文件 
  当创建或打开一个文件,将它作为一个内存映射文件来使用时,请选定最有意义的一个或多个访问标志,以说明你打算如何访问文件的数据。对内存映射文件来说,必须打开用于只读访问或读写访问的文件,因此,可以分别设定G E N E R I C _ R E A D 或GENERIC_READ |G E N E R I C _ W R I T E。
  第三个参数d w S h a r e M o d e告诉系统你想如何共享该文件。可以为d w S h a r e M o d e设定表1 7 - 4所列的4个值之一。
  表17-4 dwShareMode 的值  值 含义 
  0 打开文件的任何尝试均将失败 
  F I L E _ S H A R E _ R E A D 使用G E N E R I C _ W R I T E打开文件的其他尝试将会失败 
  F I L E _ S H A R E _ W R I T E 使用G E N E R I C _ R E A D打开文件的其他尝试将会失败 
  FILE_SHARE_READ FILE_SHARE_WRITE| 打开文件的其他尝试将会取得成功 
  如果C r e a t e F i l e函数成功地创建或打开指定的文件,便返回一个文件内核对象的句柄,否则返回I N VA L I D _ H A N D L E _ VA L U E。
  注意能够返回句柄的大多数Wi n d o w s函数如果运行失败,那么就会返回N U L L。但是,C r e a t e F i l e函数将返回I N VA L I D _ H A N D L E _ VA L U E,它定义为((H A N D L E)- 1)。 [/b]调用C r e a t e F i l e函数,就可以将文件映像的物理存储器的位置告诉操作系统。你传递的路径名用于指明支持文件映像的物理存储器在磁盘(或网络或光盘)上的确切位置。这时,必须告诉系统,文件映射对象需要多少物理存储器。若要进行这项操作,可以调用C r e a t e F i l e M a p p i n g函数: 第一个参数h F i l e用于标识你想要映射到进程地址空间中的文件句柄。该句柄由前面调用的C r e a t e F i l e函数返回。p s a参数是指向文件映射内核对象的S E C U R I T Y _ AT T R I B U T E S结构的指针,通常传递的值是N U L L(它提供默认的安全特性,返回的句柄是不能继承的)。
  本章开头讲过,创建内存映射文件就像保留一个地址空间区域然后将物理存储器提交给该区域一样。因为内存映射文件的物理存储器来自磁盘上的一个文件,而不是来自从系统的页文件中分配的空间。当创建一个文件映射对象时,系统并不为它保留地址空间区域,也不将文件的存储器映射到该区域(下一节将介绍如何进行这项操作)。但是,当系统将存储器映射到进程的地址空间中去时,系统必须知道应该将什么保护属性赋予物理存储器的页面。C r e a t e F i l e M a p p i n g函数的f d w P r o t e c t参数使你能够设定这些保护属性。大多数情况下,可以设定表1 7 - 5中列出的3个保护属性之一。
  表17-5 使用fdwProtect 参数设定的部分保护属性  保护属性 含义 
  PA G E _ R E A D O N LY 当文件映射对象被映射时,可以读取文件的数据。必须已经将G E N E R I C _ R E A D传递给C r e a t e F i l e函数 
  PA G E _ R E A D W R I T E 当文件映射对象被映射时,可以读取和写入文件的数据。必须已经将GENERIC_READ | GENERIC_WRITE传递给C r e a t e F i l e 
  PA G E _ W R I T E C O P Y 当文件映射对象被映射时,可以读取和写入文件的数据。如果写入数据,会导致页面的私有拷贝得以创建。必须已经将G E N E R I C _ R E A D或G E N E R I C _ W R I T E传递给C r e a t e F i l e 
  C r e a t e F i l e M a p p i n g的另外两个参数是d w M a x i m u m S i z e H i g h和d w M a x i m u m S i z e L o w,它们是两个最重要的参数。C r e a t e F i l e M a p p i n g函数的主要作用是保证文件映射对象能够得到足够的物理存储器。这两个参数将告诉系统该文件的最大字节数。它需要两个3 2位的值,因为Wi n d o w s支持的文件大小可以用6 4位的值来表示。d w M a x i m u m S i z e H i g h参数用于设定较高的3 2位,而d w M a x i m u m S i z e L o w参数则用于设定较低的3 2位值。对于4 GB 或小于4 GB的文件来说,d w M a x i m u m S i z e H i g h的值将始终是0。
  使用6 4位的值,意味着Wi n d o w s能够处理最大为1 6 E B(1 01 8字节)的文件。如果想要创建一个文件映射对象,使它能够反映文件当前的大小,那么可以为上面两个参数传递0。如果只打算读取该文件或者访问文件而不改变它的大小,那么为这两个参数传递0。如果打算将数据附加给该文件,可以选择最大的文件大小,以便为你留出一些富裕的空间。如果当前磁盘上的文件包含0字节,那么可以给C r e a t e F i l e M a p p i n g函数的d w M a x i m u m S i z e H i g h和d w M a x i m u mS i z e L o w传递两个0。这样做就可以告诉系统,你要的文件映射对象里面的存储器为0字节。这是个错误,C r e a t e F i l e M a p p i n g将返回N U L L。
  如果你对我们讲述的内容一直非常关注,你一定认为这里存在严重的问题。Wi n d o w s支持最大为1 6 E B的文件和文件映射对象,这当然很好,但是,怎样将这样大的文件映射到3 2位进程的地址空间( 3 2位地址空间是4 G B文件的上限)中去呢?下一节介绍解决这个问题的办法。当然,6 4位进程拥有16 EB的地址空间,因此可以进行更大的文件的映射操作,但是,如果文件是个超大规模的文件,仍然会遇到类似的问题。 若要真正理解C r e a t e F i l e和C r e a t e F i l e M a p p i n g两个函数是如何运行的,建议你做一个下面的实验。建立下面的代码,对它进行编译,然后在一个调试程序中运行它。当你一步步执行每个语句时,你会跳到一个命令解释程序,并执行C:\目录上的“d i r”命令。当执行调试程序中的每个语句时,请注意目录中出现的变化。 int WINAPI _tWinMain(HINSTANCE hinstExe, HINSTANCE, PTSTR pszCmdLine, int nCmdShow) { //Before executing the line below, C:\ does not have //a file called "MMFTest.Dat." HANDLE hfile = CreateFile("C:\\MMFTest.dat", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); //Before executing the line below, the MMFTest.Dat //file does exist but has a file size of 0 bytes. HANDLE hfilemap = CreateFileMapping(hfile, NULL, PAGE_READWRITE, 0, 100, NULL); //After executing the line above, the MMFTest.Dat //file has a size of 100 bytes. //Cleanup CloseHandle(hfilemap); CloseHandle(hfile); //When the process terminates, MMFTest.Dat remains //on the disk with a size of 100 bytes. return(0); }
  C r e a t e F i l e M a p p i n g函数的最后一个参数是p s z N a m e。它是个以0结尾的字符串,用于给该文件映射对象赋予一个名字。该名字用于与其他进程共享文件映射对象(本章后面展示了它的一个例子。第3章详细介绍了内核对象的共享操作)。内存映射数据文件通常并不需要被共享,因此这个参数通常是N U L L。
  系统创建文件映射对象,并将用于标识该对象的句柄返回该调用线程。如果系统无法创建文件映射对象,便返回一个N U L L句柄值。记住,当C r e a t e F i l e运行失败时,它将返回I N VA L I D _H A N D L E _ VA L U E(定义为-1),当C r e a t e F i l e M a p p i n g运行失败时,它返回N U L L。请不要混淆这些错误值。 [b]
当创建了一个文件映射对象后,仍然必须让系统为文件的数据保留一个地址空间区域,并将文件的数据作为映射到该区域的物理存储器进行提交。可以通过调用M a p Vi e w O f F i l e函数来进行这项操作: 参数h F i l e M a p p i n g O b j e c t用于标识文件映射对象的句柄,该句柄是前面调用CreateFile Mapping或O p e n F i l e M a p p i n g(本章后面介绍)函数返回的。参数d w D e s i r e d A c c e s s用于标识如何访问该数据。不错,必须再次设定如何访问文件的数据。可以设定表1 7 - 6所列的4个值中的一个。
  表17-6 值及其含义  值 含义 
  F I L E _ M A P _ W R I T E 可以读取和写入文件数据。C r e a t e F i l e M a p p i n g函数必须通过传递PA G E _ R E A D W R I T E标志来调用 
  F I L E _ M A P _ R E A D 可以读取文件数据。C r e a t e F i l e M a p p i n g函数可以通过传递下列任何一个保护属性来调用:PA G E _ R E A D O N LY、PA G E _ R E A D W R I T E或PA G E _ W R I T E C O P Y 
  F I L E _ M A P _ A L L _ A C C E S S 与F I L E _ M A P _ W R I T E相同 
  F I L E _ M A P _ C O P Y 可以读取和写入文件数据。如果写入文件数据,可以创建一个页面的私有拷贝。在Windows 2000中,C r e a t e F i l e M a p p i n g函数可以用PA G E _ R E A D O N LY、PA G E _ R E A D W R I T E或PA G E _ W R I T E C O P Y等保护属性中的任何一个来调用。在Windows 98中,C r e a t e F i l e M a p p i n g必须用PA G E _ W R I T E C O P Y来调用 
  Wi n d o w s要求所有这些保护属性一次又一次地重复设置,这当然有些奇怪和烦人。我认为这样做可以使应用程序更多地对数据保护属性进行控制。
  剩下的3个参数与保留地址空间区域及将物理存储器映射到该区域有关。当你将一个文件映射到你的进程的地址空间中时,你不必一次性地映射整个文件。相反,可以只将文件的一小部分映射到地址空间。被映射到进程的地址空间的这部分文件称为一个视图,这可以说明M a p Vi e w O f F i l e是如何而得名的。
  当将一个文件视图映射到进程的地址空间中时,必须规定两件事情。首先,必须告诉系统,数据文件中的哪个字节应该作为视图中的第一个字节来映射。你可以使用d w F i l e O ff s e t H i g h和d w F i l e O ff s e t L o w参数来进行这项操作。由于Wi n d o w s支持的文件最大可达1 6 E B,因此必须用一个6 4位的值来设定这个字节的位移值。这个6 4位值中,较高的3 2位传递给参数d w F i l e O ff s e t H i g h,较低的3 2位传递给参数d w F i l e O ff s e t L o w。注意,文件中的这个位移值必须是系统的分配粒度的倍数(迄今为止,Wi n d o w s的所有实现代码的分配粒度均为64 KB)。第1 4章介绍了如何获取某个系统的分配粒度。
  第二,必须告诉系统,数据文件有多少字节要映射到地址空间。这与设定要保留多大的地址空间区域的情况是相同的。可以使用d w N u m b e r O f B y t e s To M a p参数来设定这个值。如果设定的值是0,那么系统将设法把从文件中的指定位移开始到整个文件的结尾的视图映射到地址空间。 [/b]当不再需要保留映射到你的进程地址空间区域中的文件数据时,可以通过调用下面的函数将它释放: 该函数的唯一的参数p v B a s e A d d r e s s用于设定返回区域的基地址。该值必须与调用M a p Vi e w O f F i l e函数返回的值相同。必须记住要调用U n m a p Vi e w O f F i l e函数。如果没有调用这个函数,那么在你的进程终止运行前,保留的区域就不会被释放。每当你调用M a p Vi e w O f F i l e时,系统总是在你的进程地址空间中保留一个新区域,而以前保留的所有区域将不被释放。
  为了提高速度,系统将文件的数据页面进行高速缓存,并且在对文件的映射视图进行操作时不立即更新文件的磁盘映像。如果需要确保你的更新被写入磁盘,可以强制系统将修改过的数据的一部分或全部重新写入磁盘映像中,方法是调用F l u s h Vi e w O f F i l e函数: 第一个参数是包含在内存映射文件中的视图的一个字节的地址。该函数将你在这里传递的地址圆整为一个页面边界值。第二个参数用于指明你想要刷新的字节数。系统将把这个数字向上圆整,使得字节总数是页面的整数。如果你调用F l u s h Vi e w O f F i l e函数并且不修改任何数据,那么该函数只是返回,而不将任何信息写入磁盘。 [b]
不用说,你总是要关闭你打开了的内核对象。如果忘记关闭,在你的进程继续运行时会出现资源泄漏的问题。当然,当你的进程终止运行时,系统会自动关闭你的进程已经打开但是忘记关闭的任何对象。但是如果你的进程暂时没有终止运行,你将会积累许多资源句柄。因此你始终都应该编写清楚而又“正确的”代码,以便关闭你已经打开的任何对象。若要关闭文件映射对象和文件对象,只需要两次调用C l o s e H a n d l e函数,每个句柄调用一次:
  让我们更加仔细地观察一下这个进程。下面的伪代码显示了一个内存映射文件的例子: 上面的代码显示了对内存映射文件进行操作所用的“预期”方法。但是,它没有显示,当你调用M a p Vi e w O f F i l e时系统对文件对象和文件映射对象的使用计数的递增情况。这个副作用是很大的,因为它意味着我们可以将上面的代码段重新编写成下面的样子: 当对内存映射文件进行操作时,通常要打开文件,创建文件映射对象,然后使用文件映射对象将文件的数据视图映射到进程的地址空间。由于系统递增了文件对象和文件映射对象的内部使用计数,因此可以在你的代码开始运行时关闭这些对象,以消除资源泄漏的可能性。
  如果用同一个文件来创建更多的文件映射对象,或者映射同一个文件映射对象的多个视图,那么就不能较早地调用C l o s e H a n d l e函数――以后你可能还需要使用它们的句柄,以便分别对C r e a t e F i l e M a p p i n g和M a p Vi e w O f F i l e函数进行更多的调用。 [/b]上一节讲过我要告诉你如何将一个16 EB的文件映射到一个较小的地址空间中。当然,你是无法做到这一点的。你必须映射一个只包含一小部分文件数据的文件视图。首先映射一个文件的开头的视图。当完成对文件的第一个视图的访问时,可以取消它的映像,然后映射一个从文件中的一个更深的位移开始的新视图。必须重复这一操作,直到访问了整个文件。这使得大型内存映射文件的处理不太方便,但是,幸好大多数文件都比较小,因此不会出现这个问题。
  让我们看一个例子,它使用一个8 GB的文件和一个3 2位的地址空间。下面是一个例程,它使用若干个步骤来计算一个二进制数据文件中的所有0字节的数目: __int64 Count0s(void) { //Views must always start on a multiple //of the allocation granularity SYSTEM_INFO sinf; GetSystemInfo(&sinf); //Open the data file. HANDLE hFile = CreateFile("C:\\HugeFile.Big", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL); //Create the file-mapping object. HANDLE hFileMapping = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL); DWORD dwFileSizeHigh; __int64 qwFileSize = GetFileSize(hFile, &dwFileSizeHigh); qwFileSize += (((__int64) dwFileSizeHigh) 32); //We no longer need access to the file object's handle. CloseHandle(hFile); __int64 qwFileOffset = 0, qwNumOf0s = 0; while (qwFileSize > 0) { // Determine the number of bytes to be mapped in this view DWORD dwBytesInBlock = sinf.dwAllocationGranularity; if(qwFileSize sinf.dwAllocationGranularity) dwBytesInBlock =(DWORD) qwFileSize; PBYTE pbFile = (PBYTE) MapViewOfFile(hFileMapping, FILE_MAP_READ, (DWORD)(qwFileOffset >> 32), // Starting byte (DWORD)(qwFileOffset & 0xFFFFFFFF), // in file dwBytesInBlock); // # of bytes to map // Count the number of Js in this block. for(DWORD dwByte = 0; dwByte dwBytesInBlock; dwByte++) { if(pbFile[dwByte] == 0) qwNumOf0s++; } // Unmap the view; we don't want multiple views // in our address space. UnmapViewOfFile(pbFile); // Skip to the next set of bytes in the file. qwFileOffset += dwBytesInBlock; qwFileSize -= dwBytesInBlock; } CloseHandle(hFileMapping); return(qwNumOf0s); }
  这个算法用于映射64 KB(分配粒度的大小)或更小的视图。另外,要记住, M a p Vi e wO f F i l e函数要求文件的位移是分配粒度大小的倍数。当每个视图被映射到地址空间时,对0的扫描不断进行。当每个64 KB的文件块已经映射和扫描完毕时,就要通过关闭文件映射对象来对每个文件块进行整理。
  [b]
系统允许你映射一个文件的相同数据的多个视图。例如,你可以将文件开头的10 KB映射到一个视图,然后将同一个文件的头4 KB映射到另一个视图。只要你是映射相同的文件映射对象,系统就会确保映射的视图数据的相关性。例如,如果你的应用程序改变了一个视图中的文件内容,那么所有其他视图均被更新以反映这个变化。这是因为尽管页面多次被映射到进程的虚拟地址空间,但是系统只将数据放在单个R A M页面上。如果多个进程映射单个数据文件的视图,那么数据仍然是相关的,因为在数据文件中,每个R A M页面只有一个实例――正是这个R A M页面被映射到多个进程的地址空间。
  注意Wi n d o w s允许创建若干个由单个数据文件支持的文件映射对象。Wi n d o w s不能保证这些不同的文件映射对象的视图具有相关性。它只能保证单个文件映射对象的多个视图具有相关性。
  然而,当对文件进行操作时,没有理由使另一个应用程序无法调用C r e a t e F i l e函数以打开由另一个进程映射的同一个文件。这个新进程可以使用R e a d F i l e和Wr i t e F i l e函数来读取该文件的数据和将数据写入该文件。当然,每当一个进程调用这些函数时,它必须从内存缓冲区读取文件数据或者将文件数据写入内存缓冲区。该内存缓冲区必须是进程自己创建的一个缓冲区,而不是映射文件使用的内存缓冲区。当两个应用程序打开同一个文件时,问题就可能产生:一个进程可以调用R e a d F i l e函数来读取文件的一个部分,并修改它的数据,然后使用Wr i t e F i l e函数将数据重新写入文件,而第二个进程的文件映射对象却不知道第一个进程执行的这些操作。由于这个原因,当你为将被内存映射的文件调用C r e a t e F i l e函数时,最好将d w S h a r e M o d e参数的值设置为0。这样就可以告诉系统,你想要单独访问这个文件,而其他进程都不能打开它。
  只读文件不存在相关性问题,因此它们可以作为很好的内存映射文件。内存映射文件决不应该用于共享网络上的可写入文件,因为系统无法保证数据视图的相关性。如果某个人的计算机更新了文件的内容,其他内存中含有原始数据的计算机将不知道它的信息已经被修改。 [/b]正如你可以使用Vi r t u a l A l l o c函数来确定对地址空间进行倒序所用的初始地址一样,你也可以使用M a p Vi e w O f F i l e E x函数而不是使用M a p Vi e w O f F i l e函数来确定一个文件被映射到某个特定的地址。请看下面的代码: 该函数的所有参数和返回值均与M a p Vi e w O f F i l e函数相同,唯一的差别是最后一个参数p v B a s e A d d r e s s有所不同。在这个参数中,你为要映射的文件设定一个目标地址。与Vi r t u a l A l l o c一样,你设定的目标地址应该是分配粒度边界( 64 KB)的倍数,否则M a p Vi e w O f F i l e E x将返回N U L L,表示出现了错误。 [b]17.7 实现内存映射文件的具体方法
  
让我们再来观察另一个实现代码的差别。下面是一个小程序,它映射了单个文件映射对象的两个视图: #include int WINAPI WinMain(HINSTANCE hinstExe, HINSTANCE, PTSTR pszCmdLine, int nCmdShow) { //Open an existing file-it must be bigger than 64 KB. HANDLE hFile = CreateFile(pszCmdLine, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); //Create a file-mapping object backed by the data file. HANDLE hFileMapping = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, NULL); //Map a view of the whole file into our address space. PBYTE pbFile = (PBYTE) MapViewOfFile(hFileMapping, FILE_MAP_WRITE, 0, 0, 0); //Map a view of the file (starting 64 KB in) into our address space PBYTE pbFile2 = (PBYTE) MapViewOfFile(hFileMapping, FILE_MAP_WRITE, 0, 65536, 0); if((pbFile + 65536) == pbFile2) { // If the addresses overlap, there is one address // space region for both views: this must be Windows 98. MessageBox(NULL, "We are running under Windows 98", NULL, MB_OK); } else { // If the addresses do not overlap, each view has its own // address space region: this must be Windows 2000. MessageBox(NULL, "We are running under Windows 2000", NULL, MB_OK); } UnmapViewOfFile(pbFile2); UnmapViewOfFile(pbFile); CloseHandle(hFileMapping); CloseHandle(hFile); return(0); }
  在Windows 98中,当文件映射对象的视图被映射时,系统将为整个文件映射对象保留足够的地址空间。即使调用M a p Vi e w O f F i l e函数时它的参数指明你想要系统只映射文件映射对象的一小部分,系统也会为它保留足够的地址空间。这意味着即使你规定只映射文件映射对象的一个64 KB的部分,也不能将一个1 GB的文件映射对象映射到一个视图中。
  每当进程调用M a p Vi e w O f F i l e时,该函数将返回一个为整个文件映射对象保留的地址空间区域中的地址。因此,在上面的代码段中,第一次调用M a p Vi e w O f F i l e函数时返回包含整个映射文件的区域的基地址,第二次调用M a p Vi e w O f F i l e函数时返回离同一个地址空间区域64 KB位置上的地址。
  Windows 2000的实现代码在这里同样存在很大的差别。在上面的代码段中,两次调用M a p Vi e w O f F i l e函数将导致Windows 2000保留两个不同的地址空间区域。第一个区域的大小是文件映射对象的大小,第二个区域的大小是文件映射对象的大小减去64 KB。尽管存在两个不同的区域,但是它们的数据能够保证其相关性,因为两个视图都是从相同的文件映射对象映射而来的。在Windows 98下,各个视图具有相关性,因为它们位于同一个内存中。 [b][/b]数据共享方法是通过让两个或多个进程映射同一个文件映射对象的视图来实现的,这意味着它们将共享物理存储器的同一个页面。因此,当一个进程将数据写入一个共享文件映射对象的视图时,其他进程可以立即看到它们视图中的数据变更情况。注意,如果多个进程共享单个文件映射对象,那么所有进程必须使用相同的名字来表示该文件映射对象。
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics