Windows核心编程 第十七章 -内存映射文件(下)
17.3 使用内存映射文件
若要使用内存映射文件,必须执行下列操作步骤:
1) 创建或打开一个文件内核对象,该对象用于标识磁盘上你想用作内存映射文件的文件。
2) 创建一个文件映射内核对象,告诉系统该文件的大小和你打算如何访问该文件。
3) 让系统将文件映射对象的全部或一部分映射到你的进程地址空间中。
当完成对内存映射文件的使用时,必须执行下面这些步骤将它清除:
1) 告诉系统从你的进程的地址空间中撤消文件映射内核对象的映像。
2) 关闭文件映射内核对象。
3) 关闭文件内核对象。
下面将详细介绍这些操作步骤。
17.3.1 步骤1:创建或打开文件内核对象
若要创建或打开一个文件内核对象,总是要调用C r e a t e F i l e函数:
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 dA 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个值中的一个。
当创建或打开一个文件,将它作为一个内存映射文件来使用时,请选定最有意义的一个或
多个访问标志,以说明你打算如何访问文件的数据。对内存映射文件来说,必须打开用于只读访问或读写访问的文件,因此,可以分别设定 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个值之一。
如果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)。
17.3.2 步骤2:创建一个文件映射内核对象
调用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个保护属性之一。
Windows 98 在Windows 98下,可以将PA G E _ W R I T E C O P Y标志传递给C r e a t e F i l eM a p p i n g,这将告诉系统从页文件中提交存储器。该页文件存储器是为数据文件的数据拷贝保留的,只有修改过的页面才被写入页文件。你对该文件的数据所作的任何修
改都不会重新填入原始数据文件。其最终结果是, PA G E _ W R I T E C O P Y标志的作用在Windows 2000和Windows 98上是相同的。
除了上面的页面保护属性外,还有 4个节保护属性,你可以用 O R将它们连接起来放入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参数中。节只是用于内存映射的另一个术语。
节的第一个保护属性是 S E C _ N O C A C H E,它告诉系统,没有将文件的任何内存映射页面放入高速缓存。因此,当将数据写入该文件时,系统将更加经常地更新磁盘上的文件数据。这个标志与PA G E _ N O C A C H E保护属性标志一样,是供设备驱动程序开发人员使用的,应用程序通常不使用。
Windows 98 Windows 98将忽略S E C _ N O C A C H E标志。
节的第二个保护属性是 S E C _ I M A G E,它告诉系统,你映射的文件是个可移植的可执行(P E)文件映像。当系统将该文件映射到你的进程的地址空间中时,系统要查看文件的内容,以确定将哪些保护属性赋予文件映像的各个页面。例如, P E文件的代码节( . t e x t)通常用PA G E _ E X E C U T E _ R E A D 属 性进 行映 射, 而 P E文 件的 数据 节 ( . d a t a ) 则通 常用PA G E _ R E A D W R I T E属性进行映射。如果设定的属性是 S E C _ I M A G E,则告诉系统进行文件映像的映射,并设置相应的页面保护属性。
Windows 98 Windows 98将忽略S E C _ I M A G E标志。
最后两个保护属性是 S E C _ R E S E RV E和S E C _ C O M M I T,它们是两个互斥属性,当使用内存映射数据文件时,它们不能使用。这两个标志将在本章后面介绍。当创建内存映射数据文件时,不应该设定这些标志中的任何一个标志。C r e a t e F i l e M a p p i n g将忽略这些标志。
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 0 1 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的地址空间,因此可以进行更大的文件的映射操作,但是,如果文件是个超大规模的文件,仍然会遇到类似的问题。
书中给了个例子,让自己一步一步调看文件大小变化:
上面是我写的,CreadeFile会创建一个0字节大小的aaaa.dat,然后下面那个CreateFileMapping会把文件大小编程100字节。里面内容是空(NULL)。
如果调用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标志,那么系统将设法确保磁盘上的相关数据文件的大小至少与 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函数将扩展该文件的大小,使磁盘上的文件变大。这种扩展是必要的,这样,当以后将该文件作为内存映射文件使用时,物理存储器就已经存在了。如果正在用 PA G E _ R E A D O N LY或PA G E _ W R I T E C O P Y标志创建该文件映射对象,那么C r e a t e F i l e M a p p i n g特定的文件大小不得大于磁盘文件的物理大小。这是因为你无法将任何数据附加给该文件。
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。请不要混淆这些错误值。
17.3.3 步骤3:将文件数据映射到进程的地址空间
当创建了一个文件映射对象后,仍然必须让系统为文件的数据保留一个地址空间区域,并将文件的数据作为映射到该区域的物理存储器进行提交。可以通过调用 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个值中的一个。
剩下的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,那么系统将设法把从文件中的指定位移开始到整个文件的结尾的视图映射到地址空间。
如果在调用M a p Vi e w O f F i l e函数时设定了F I L E _ M A P _ C O P Y标志,系统就会从系统的页文件中提交物理存储器。提交的地址空间数量由 d w N u m b e r O f B y t e s To M a p参数决定。只要你不进行其他操作,只是从文件的映像视图中读取数据,那么系统将决不会使用页文件中的这些提交的页面。但是,如果进程中的任何线程将数据写入文件的映像视图中的任何内存地址,那么系统将从页文件中抓取已提交页面中的一个页面,将原始数据页面拷贝到该页交换文件中,然后将该拷贝的页面映射到你的进程的地址空间。从这时起,你的进程中的线程就要访问数据的本地拷贝,不能读取或修改原始数据。
17.3.4 步骤4:从进程的地址空间中撤消文件数据的映像
当不再需要保留映射到你的进程地址空间区域中的文件数据时,可以通过调用下面的函数将它释放:
17.3.5 步骤5和步骤6:关闭文件映射对象和文件对象
不用说,你总是要关闭你打开了的内核对象。如果忘记关闭,在你的进程继续运行时会出现资源泄漏的问题。当然,当你的进程终止运行时,系统会自动关闭你的进程已经打开但是忘记关闭的任何对象。但是如果你的进程暂时没有终止运行,你将会积累许多资源句柄。因此你始终都应该编写清楚而又“正确的”代码,以便关闭你已经打开的任何对象。若要关闭文件映射对象和文件对象,只需要两次调用C l o s e H a n d l e函数,每个句柄调用一次:
17.8 使用内存映射文件在进程之间共享数据
Wi n d o w s总是出色地提供各种机制,使应用程序能够迅速而方便地共享数据和信息。这些机制包括R P C、C O M、O L E、D D E、窗口消息(尤其是 W M _ C O P Y D ATA)、剪贴板、邮箱、管道和套接字等。在Wi n d o w s中,在单个计算机上共享数据的最低层机制是内存映射文件。不错,如果互相进行通信的所有进程都在同一台计算机上的话,上面提到的所有机制均使用内存映射文件从事它们的烦琐工作。如果要求达到较高的性能和较小的开销,内存映射文件是举手可得的最佳机制。
数据共享方法是通过让两个或多个进程映射同一个文件映射对象的视图来实现的,这意味着它们将共享物理存储器的同一个页面。因此,当一个进程将数据写入一个共享文件映射对象的视图时,其他进程可以立即看到它们视图中的数据变更情况。注意,如果多个进程共享单个文件映射对象,那么所有进程必须使用相同的名字来表示该文件映射对象。
让我们观察一个例子,启动一个应用程序。当一个应用程序启动时,系统调用 C r e a t e F i l e函数,打开磁盘上的. e x 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 E x函数(它带有 S E C _ I M A G E标志),这样, . e x e文件就可以映射到进程的地址空间。这里调用的是 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,这样,文件的映像将被映射到存放在 . e x e文件映像中的基地址中。系统创建该进程的主线程,将该映射视图的可执行代码的第一个字节的地址放入线程的指令指针,然后C P U启动该代码的运行。
如果用户运行同一个应用程序的第二个实例,系统就认为规定的 . e x e文件已经存在一个文件映射对象,因此不会创建新的文件对象或者文件映射对象。相反,系统将第二次映射该文件的一个视图,这次是在新创建的进程的地址空间环境中映射的。系统所做的工作是将相同的文件同时映射到两个地址空间。显然,这是对内存的更有效的使用,因为两个进程将共享包含正在执行的这部分代码的物理存储器的同一个页面。与所有内核对象一样,可以使用 3种方法与多个进程共享对象,这 3种方法是句柄继承性、句柄命名和句柄复制。关于这3种方法的详细说明,参见第3章的内容。
17.9 页文件支持的内存映射文件
到现在为止,已经介绍了映射驻留在磁盘驱动器上的文件视图的方法。许多应用程序在运行时都要创建一些数据,并且需要将数据传送给其他进程,或者与其他进程共享。如果应用程序必须在磁盘驱动器上创建数据文件,并且将数据存储在磁盘上以便对它进行共享,那么这将是非常不方便的。
M i c r o s o f t公司认识到了这一点,并且增加了一些功能,以便创建由系统的页文件支持的内存映射文件,而不是由专用硬盘文件支持的内存映射文件。这个方法与创建内存映射磁盘文件所用的方法几乎相同,不同之处是它更加方便。一方面,它不必调用 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函数,并且传递I N VA L I D _ H A N D L E _ VA L U E作为h 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函数,并传递一个以0结尾的字符串作为p s z N a m e参数。然后,想要访问该存储器的其他进程就可以调用C r e a t e F i l e M a p p i n g或O p e n F i l e M a p p i n g函数,并传递相同的名字。
当进程不再想要访问文件映射对象时,该进程应该调用 C l o s e H a n d l e函数。当所有句柄均被关闭后,系统将从系统的页文件中收回已经提交的存储器。
Windows核心编程 第十七章 -内存映射文件(下)的更多相关文章
- Windows核心编程 第十七章 -内存映射文件(上)
第1 7章 内存映射文件 对文件进行操作几乎是所有应用程序都必须进行的,并且这常常是人们争论的一个问题.应用程序究竟是应该打开文件,读取文件并关闭文件,还是打开文件,然后使用一种缓冲算法,从文件的各个 ...
- 《windows核心编程》 17章 内存映射文件
内存映射文件主要用于以下三种情况: 系统使用内存映射文件载入并运行exe和dll,这大量节省了页交换文件的空间以及应用程序的启动时间 开发人员可以使用内存映射文件来访问磁盘上的数据文件.这使得我们可以 ...
- 【Windows核心编程】一个使用内存映射文件进行进程间通信的例子
进程间通信的方式有很多种,其底层原理使用的都是内存映射文件. 本文实现了Windows核心编程第五版475页上的demo,即使用内存映射文件来在进程间通信. 进程1 按钮[Create mappin ...
- windows核心编程 第8章201页旋转锁的代码在新版Visual Studio运行问题
// 全局变量,用于指示共享的资源是否在使用 BOOL g_fResourceInUse = FALSE; void Func1() { //等待访问资源 while(InterlockedExcha ...
- windows核心编程 第5章job lab示例程序 解决小技巧
看到windows核心编程 第5章的最后一节,发现job lab例子程序不能在我的系统(win8下)正常运行,总是提示“进程在一个作业里” 用process explorer程序查看 ...
- Windows核心编程 第四章 进程(下)
4.3 终止进程的运行 若要终止进程的运行,可以使用下面四种方法: • 主线程的进入点函数返回(最好使用这个方法) . • 进程中的一个线程调用E x i t P r o c e s s函数(应该避免 ...
- Java编程的逻辑 (61) - 内存映射文件及其应用 - 实现一个简单的消息队列
本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http:/ ...
- 第17章 内存映射文件(3)_稀疏文件(Sparse File)
17.8 稀疏调拨的内存映射文件 17.8.1 稀疏文件简介 (1)稀疏文件(Sparse File):指的是文件中出现大量的0数据,这些数据对我们用处不大,但是却一样的占用空间.NTFS文件系统对此 ...
- Windows核心编程 第七章 线程的调度、优先级和亲缘性(下)
7.6 运用结构环境 现在应该懂得环境结构在线程调度中所起的重要作用了.环境结构使得系统能够记住线程的状态,这样,当下次线程拥有可以运行的C P U时,它就能够找到它上次中断运行的地方. 知道这样低层 ...
随机推荐
- CMDB项目要点之技术点(面试题)
1.单例模式 日志对象用单例模式 django admin中注册类是,用到单例模式 为什么要用单例模式 同一个对象操作 维护全局变量 + 对全局变量做一些操作 # __new__ import thr ...
- ResNet论文笔记
其实ResNet这篇论文看了很多次了,也是近几年最火的算法模型之一,一直没整理出来(其实不是要到用可能也不会整理吧,懒字头上一把刀啊,主要是是为了将resnet作为encoder嵌入到unet架构中, ...
- Linux内核模块驱动加载与dmesg调试
因为近期用到了Linux内核的相关知识,下面随笔将给出内核模块的编写记录,供大家参考. 1.运行环境 Ubuntu 版本:20.04 Linux内核版本:5.4.0-42-generic gcc版本: ...
- linux 系统用户与用户组管理
关于/etc/passwd和/etc/shadow /etc/passwd 第1个字段为用户名(第一行中的root就是用户名)第2个字段存放的是该账号的口令.第3个字段为一个数字,这个数字代表的用户标 ...
- PTA 将数组中的数逆序存放
7-1 将数组中的数逆序存放 (20 分) 本题要求编写程序,将给定的n个整数存入数组中,将数组中的这n个数逆序存放,再按顺序输出数组中的元素. 输入格式: 输入在第一行中给出一个正整数n(1). ...
- salesforce零基础学习(一百零二)Limitation篇之 CPU Limit
本篇参考: https://help.salesforce.com/articleView?id=000339361&type=1&mode=1 https://developer.s ...
- 「HTML+CSS」--自定义按钮样式【003】
前言 Hello!小伙伴! 首先非常感谢您阅读海轰的文章,倘若文中有错误的地方,欢迎您指出- 哈哈 自我介绍一下 昵称:海轰 标签:程序猿一只|C++选手|学生 简介:因C语言结识编程,随后转入计算机 ...
- epoll poll select区别
函数依赖 ( Functional Dependency,FD) select:http://www.cnblogs.com/Anker/archive/2013/08/14/3258674.html ...
- rancher的ssl部署
前言 因为我司有多套k8s环境,管理起来过于麻烦,需要一个统一的管理平台,又因为没有预留时间自己开发,经过选择后,使用rancher来进行多k8s环境的统一管理平台. 部署 1.在阿里云上申请免费的证 ...
- MySQL巩固学习记录(一)
mysql下载安装 一.采用图形化界面安装 (初期只安装server服务端就可以了,别的不多赘述) 二.采用压缩版安装 1.将文件解压缩到自己想要的路径 2. 添加环境变量,即mysql的bin目录 ...