来自Windows核心编程 – 第十七章

本章主要讲述内容是内存管理中的内存映射文件部分。

内存映射文件和前面提到的虚拟内存是有点相似的。

内存映射文件允许开发人员预定一块地址区域空间并给区域调拨物理存储器。不同之处在于内存映射文件的物理存储器来自于磁盘上已有的文件,而虚拟内存的物理存储器来自系统的页交换文件。一旦把文件映射到地址空间,我们就可以对它进行访问,就像整个内存都被载入内存一样。

 

内存映射文件主要用于以下三种情况:

  1. 系统用内存映射文件来载入并运行 .exe 和 动态链接库(.dll)文件。这大量节省了页交换文件的空间以及应用程序启动的时间。
  2. 开发人员可以用内存映射文件来访问磁盘上的数据文件。这使得我们可以避免直接对文件进行I/O操作和对文件内容进行缓存。
  3. 通过使用内存映射文件,我们可以在同一台机器的不同进程之间共享数据。Windows的确提供了一些其他方法在进程间传送数据,但是这些方法都是通过内存映射文件来完成的。如果要在一台机器的不同内存之间共享数据,内存映射文件是最高效的办法。

下面进行详细的讨论:

17.1 映射到内存的可执行文件 和 DLL

当一个线程调用 CreateProcess 的时候,系统会执行以下步骤:

  1. 系统会先确定 CreateProcess 所指定的可执行文件所在的位置。如果无法找到该 .exe 文件,那么系统将不会创建进程,这时 CreateProcess 会返回 FALSE
  2. 系统创建一个新的 进程内核对象
  3. 系统为新进程创建一个私有地址空间
  4. 系统预定一块足够大的地址空间来容纳.exe文件。待预定的地址空间区域的具体位置已经在.exe文件中指定。默认情况下,.exe文件的基地址是0x0040 0000。(对64位程序可能会不同)。可以在构建应用程序的.exe文件时使用/BASE 链接器开关指定一个地址。
  5. 系统会对地址空间区域进行标注,表明该区域的后备物理存储器来自磁盘上的.exe文件,而非来自系统的页交换文件。

当把.exe文件映射到进程的地址空间后,会访问.exe文件中的一个段,这个段列出了一些DLL文件,它们包含了该.exe文件调用到的函数。然后系统会调用 LoadLibrary 来载入每个DLL,如果一个DLL用到了另一个DLL,系统同样会再次次调用载入。

所有与 Windows一起发布的 系统DLL 文件都有不同的基地址,这样即使把它们载入到同一个地址空间,也不会发生重叠。

DLL 文件的载入和 .exe 文件的载入是有些类似的,不同的是可能无法在DLL文件制定的基地址处预定区域,因为该区域可能被另一个 DLL 或 .exe文件所占用,也可能是这个区域不够大。如果系统无法将DLL载入到指定的基地址,那么这种情况就太不走运了。

此时如果DLL包含了重定位信息,那么系统会对DLL执行重定位操作,重定位不仅需要占用页交换文件中额外的存储空间,而且会增加载入DLL所需的时间。同时会对地址空间区域进行标注,如果没有重定位则标注后备物理存储器来自磁盘上的DLL文件,如果重定位了,还会另外标注,表明DLL中有一部分物理存储器被映射到了页交换文件。

如果因为某些原因无法将 .exe 文件 和 所需的 DLL 文件映射到地址空间区域,系统会先给用户显示一个对话框,然释放进程地址空间和进程对象。这时CreateProcess 函数会返回FALSE… 可以调用 GetLastError 来查询为何无法创建进程。

当 .exe 文件 和 DLL文件都映射完后,就开始执行 .exe文件的启动代码。当完成对.exe文件的映射后,系统会负责所有的换页、缓存以及高速缓存的操作。

17.1.1 同一个可执行文件 或 DLL 的多个实例不会共享静态数据

当我们运行一个应用程序的时候,进程使用的是一个平面的地址空间。编译和链接程序的时候,所有的代码和数据都被放在一个大的实体中。在.exe文件中,数据位于代码之后,它和代码的分隔仅限于此。

下面是一个简单的视图,它描绘了如何把应用程序中的代码和数据载入到虚拟内存,并将它们映射到地址空间中。

看下面,磁盘上的可执行文件,代码页面的后面是数据页面…这个随便一记就好了。

然后重点是,它们在虚拟内存里的载入并不是顺序的…

ExeLoad

接下来我们再创建一个实例,系统只不过是打开另一个内存映射视图,创建一个新的进程对象、线程对象,分配新的进程ID和线程ID。这时系统只不过是把包含应用程序的代码和数据的 虚拟内存页面 映射到第二个实例的地址空间中,并没有在虚拟内存中申请新的空间。

ExeLoad2

再然后,如果一个应用程序改变了数据页面的数据,那么岂不是所有的应用程序的值都被改了?很明显这样是很危险很可怕的,所以系统通过内存管理系统的 写时复制(Copy-On-Write) 特性来防止这种情况的发生。

如下图,任何时候当应用程序试图写入内存映射文件的时候,系统会首先截获此类尝试,接着为应用程序试图写入的内存页面分配一块新的内存,然后复制页面内容,最后让应用程序写入到刚分配的内存块。最终结果就是,创建了一个新页面,而且其他实例不会受到影响。

CopyOnWrite

17.1.2 在同一个 可执行文件 或 DLL 的多个实例间共享静态数据

默认情况下,同一个 .exe文件 或 DLL 的多个实例间不会共享全局或静态数据,这样的设计是最保险的。但是在某些情况下,在同一个 .exe文件 或者 DLL 的多个实例间共享一个变量不仅有用,而且很方便。

例如,可以用来检查运行了多少个应用程序的实例。

本节讨论一项可以在 同一个 .exe文件 或 DLL 的多个实例之间共享变量的技术。在深入讨论之前我们谈谈一些需要了解的背景知识:

每个.exe文件或DLL 文件映像 由许多段组成。按照惯例,每个标准的段名都以点号开始。

举个例子,在编译程序的时候,编译器会将代码放在一个名叫 .text 的段中。此外编译器还会将未经初始化的数据放在.bss段中,将已经初始化的数据放在.data中。

每个段都有和他相关联的属性,如下表所示:

 属 性  含 义
 READ 可以从该段读取数据
 WRITE 可以向该段写入数据
 EXECUTE 可以执行该段的内容
 SHARED 该段的内容为多个实例共享(关闭了写时复制)

下表列出了常用的段,并解释了它的用途:

 段 名  目 的
 .bss  未经初始化的数据
 .CRT  只读的C运行时数据
 .data  已经初始化的数据
 .debug  调试信息
 .didata  延迟导入的名字表
 .edata  导出的名字表
 .idata  导入的名字表
 .rdata  只读的运行时数据
 .reloc  重定位表信息
 .rsrc  资源
 .text  .exe 或 DLL 代码
 .textbss  启用增量链接选项时,C++编译器生成
 .tls  线程本地存储
 .xdata  异常处理表

 

除了使用编译器和链接器所创建的标准段外,我们还可以在编译的时候使用细面的编译器指示符来创建自己的段。

当编译器编译这段代码的时候,就会创建一个名为 Shared 的段,并将 pragma 指示符之后所有带有初始值的变量放到这个新的段中。我们看上文的代码:

首先创建了一个名为 “Shared” 这样的段

然后将 g_lInstanceCount 这个有初始值的变量放到新的段中。

然后#pragma data_seg() 这一行告诉编译器后面的都放到默认段中。

要注意的是,如果 g_lInstanceCount 这个值没有初始化部分的话,就会被放到Shared段之外的其他段中。

当然,我们也有办法将未初始化的数据放到我们指定的段中。参考代码如下:

这段代码经我测试是有效的。测试方法的话可以用 MS Visual Studio 的 DumpBin 工具来测试。

这篇博客写完了会加一个链接…给出测试教程。

要注意的是,在使用 allocate 声明符之前,要创建相应的段。

 

接下来我们就要想办法共享变量了。

为了共享变量,仅仅告诉编译器把变量放到单独的段中是不够的,因为写时复制机制会屏蔽一个实例对数据的改变对其他实例的影响。所以我们还要告诉链接器要共享这个段中的变量。这可以通过在链接器的命令行中使用 /SECTION 开关来实现:

name 指定要改变哪个段的属性

attributes 指定想要的属性

我们可以将Shared段修改如下:

R表示READ、W表示WRITE、E表示EXECUTE、S表示SHARED

我们上文的RWS表示 可读可写且共享

如果要改变多个段的属性,则必须在命令行中使用多个 /SECTION 开关,一个开关对应一个段。

当然我们也可以用下面的写法,把开关嵌入到源代码中:注意后面没有分号哈…

这行代码会告诉编译器把其中的字符串嵌入到所生成.obj文件中的一个特殊的段中,这个段名是”.drectve”。当链接器把所有的.obj模块合并到一起的时候,链接器会检查每一个.obj模块的”.drectve”段,并将所有的字符串当做是传给链接器的命令行参数。

当然这种方法并不安全,具体不讲解。

 

17.1.3 Application Instance 示例程序

展示了应用程序如何知道在任意时刻有多少个自己的实例正在运行。

这段代码和解析会在新的帖子中展示。

 

17.2 映射到内存中的数据文件

这是前面提到的第二种方法,用内存映射文件来访问磁盘上的数据文件。

我们按照书上的以颠倒文件内容这个例子来看。有四种方法实现这个程序:

17.2.1 方法1:一个文件,一块缓存

理论上最简单的方法,分配一块足够大的内存准备载入并存放整个文件的内容。然后打开文件,读取全部数据,然后关闭文件。然后以O(n)的方法逆序,然后再次打开文件,将数据从新写入。

存在的问题是:必须根据文件大小来分配内存…如果文件大小是2GB以上,那这样的程序根本不可能在32位系统下运行。还要注意的是,如果我们在写入的时候处理被中断了,那么文件的内容也被破坏了。解决办法是先将文件拷贝一份,然后如果整个过程完成,就删除副本。很明显这样大量占用了磁盘空间。

17.2.2 方法2:两个文件,一块缓存

先打开已有文件,并创建一个新文件。然后分配较小的缓存。

然后将源文件的文件指针定位到文件末尾减去8KB的地方。

将8KB数据读取到缓存中,颠倒缓冲中的内容。

写入到新创建的文件中,然后重定位,循环。

如果文件长度不是8KB…那就要注意一些特殊的处理了,不过并不复杂。

存在的问题是,同样会消耗大量的磁盘空间。

17.2.3 方法3:一个文件,两块缓存

在程序初始化的时候分配两块大小为8kb的缓存,然后把头部8kb读入到缓存1,尾部8kb读入到缓存2.

接下来把两块缓存的内容颠倒,然后第一块缓存的内容写入到文件尾,第二块缓存的内容写入到文件头。

要注意的是文件大小不是16KB的倍数…这个需要特殊处理的。

存在的问题嘛…就是处理过程中断了,还是会导致文件被破坏。

17.2.4 方法4:一个文件,没有缓存

用内存映射文件 来颠倒文件内容。

打开文件并向系统预定一块虚拟地址空间。

接着让系统把文件的第一个字节映射到该区域的第一个字节。

然后就可以访问这块虚拟内存区域,就好像它实际上包含了文件一样。

事实上如果要颠倒的是一个文本文件,而且文件末尾字节为0,则可以把这个文件当做内存中的一个字符串来处理,这种情况下,直接调用C运行库函数_tcsrev就能颠倒文件中的数据。

这种方法的优点是系统为我们处理与文件缓存有关的操作。

但是如果中途被打断的话,仍然可能导致数据被破坏。

 

17.3 使用内存映射文件

要使用内存映射文件,需要执行下面三个步骤:

  1. 创建或打开一个文件内核对象,该对象标识了我们想要用作内存映射文件的那个磁盘文件。
  2. 创建一个文件映射内核对象来告诉系统文件的大小以及我们打算如何访问文件。
  3. 高速系统把文件映射对象的部分或全部映射到进程的地址空间中。

用完内存映射文件后,必须执行下面三个步骤来做清理工作:

  1. 告诉系统从进程地址空间中取消对文件映射内核对象的映射
  2. 关闭文件映射内核对象
  3. 关闭文件内核对象

下面给出一个链接…详细讨论这些步骤并完成FileReverse程序。

http://blog.tk-xiong.com/archives/933

 

17.4 用内存映射文件来处理大文件

我们知道,一个文件最大的大小是64位可描述的 16EB…,假设我们就有这么一个16EB的文件在磁盘上(嗯,可能在若干年后它就实现了…),那么我们该这么处理这个文件呢。

这个问题可以这么来看,就是如何把一个 16EB的文件 映射到一个较小的地址空间中。实际上是不可能的,但是有一种方法可以达到一种类似的效果:

我们映射文件的一个视图,这个视图包含文件的一小部分数据。比如统计文件中值为 0 的字节数:

我们先映射前面 N个字节,然后处理,处理完后撤销对这部分的映射,再映射后面 N – 2N 个字节的数据。虽然的确很不方便,但是这是对付这种大文件的最好的办法了…吧。

要注意的是,MapViewOfFile 要求文件的偏移量必须是分配粒度的整数倍。

随着一个又一个的视图被映射到地址空间中,统计过程会持续进行。

完成对文件每个64KB块的映射和处理后,程序就关闭文件映射对象,执行清理工作。

 

17.5 内存映射文件和一致性

系统允许我们吧同一个文件中的数据映射到多个视图中。例如,我们可以先把一个文件的前10KB映射到一个视图中,然后再把同一个文件的前 4KB 映射到另一个视图中。只要我们映射的是同一个文件映射对象,那么系统会确保各视图中的数据是一致的

举个简单的例子,应用程序把一个文件的前4KB映射到视图A,然后又将前4KB映射到视图B甚至更多的视图,我们在视图A中修改了文件的内容,那么系统会更新所有其他视图以反映修改后的文件内容。

这是因为即使该页面被多次映射到进程的虚拟地址空间中,系统也还是在同一个内存页面中保存被映射的数据。如果多个进程把同一个数据文件映射到多个视图中,那么数据也仍然会是一致的。这是因为数据文件的每个页面在内存中只有一份 —— 但是这个唯一的一份内存页面会被映射到多个进程的地址空间中。

在处理文件的时候,完全有可能出现这样的情况:就是两个应用程序同时对一个文件进行读写处理,第一个进程读取数据,修改,写回文件。但是第二个进程对第一个进程的操作却一无所知。出于这个原因,如果打算将打开的文件用于内存映射,那么在调用CreateFile的时候最好是传 0 给dwShareMode参数,这等于是在告诉系统我们想要独占对文件的访问,使其他进程无法打开同一个文件。

只读文件是不存在一致性的问题的,它非常适合用于内存映射文件。

要注意的是,绝对不能用 内存映射文件 跨网络共享可写文件,因为系统无法保持 数据视图 的一致性!如果一台机器更新了文件的内容,另一台机器的进程是无法知道数据已经修改过的,从而导致它继续使用的是内存中的原始数据!

 

17.6 给内存映射文件制定基地址

在调用 VirtualAlloc 的时候,我们可以建议系统在指定的基地址预定地址空间。同样也可以用 MapViewOfFileEx 函数来代替 MapViewOfFile 函数,这样我们就能建议系统吧文件映射到指定地址:

除了最后一个参数之外,其他的所有参数都和返回值都与 MapViewOfFile 函数相同的。

最后一个参数:给要映射的文件指定一个目标地址。同样的,这里的目标地址必须是分配粒度(64KB)的整数倍,否则函数会直接返回 NULL,表示有错误发生,此时GetLastError 会返回 1132(ERROR_MAPPED_ALIGNMENT).

如果无法将文件映射到指定的地址(比如文件太大,因此与其他地址重叠了),那么函数会失败并返回NULL。而且它不会尝试去找另一块能够容纳文件的空间。当然我们可以给最后一个参数传递NULL,那样的话就和 MapViewOfFile 函数的行为一模一样了。

在我们使用内存映射文件共享数据的时候,MapViewOfFileEx 非常有用。举个例子,如果两个或多个应用程序共享一组数据结构,而这些数据结构中有一些指针指向其他数据结构,那么我们可能需要给内存映射文件指定一个地址了。

链表就是一个绝佳的例子,在一个链表中,链表中每个元素包含了链表中另一个元素的内存地址。要遍历链表必须知道第一个元素的地址,然后引用元素总指向下一个元素的成员。在使用内存映射文件的时候,这可能是一个问题:如果一个进程在内存映射文件中准备了一个链表,然后把这个内存映射文件共享给另一个进程,那么第二个个进程很可能会把内存映射文件映射到地址空间中一个完全不同的地方。这样当第二个进程试图遍历链表的时候,它会先得到链表的第一个元素,然后取得下一个元素的内存地址,并试图访问这个元素。但是对第二个进程来说,它得到的第二个元素的内存地址是错误的。

我们可以通过两种方法来解决这个问题:

1. 让第二个进程把包含链表的内存映射文件映射到自己的空间的时候,调用 MapViewOfFileEx函数,然后映射到第一个进程映射的内存地址,这样就可以保证它们的数据地址是一样的了。问题在于第二个进程必须知道第一个进程在构造链表的是,把内存映射文件映射到了哪里。如果两个应用程序已经被设计成为协同工作的,那么这就不成问题,可以直接在代码中写死这个地址,也可以让一个进程通过进程间通信机制来通知另一个进程,比如给一个窗口发送消息。

2. 另一种方法是让进程在创建链表的时候,在每一个结点中保存一个偏移量,我们不通过指针地址来访问下一个节点,而是通过地址偏移量来访问下一个节点,是不是很棒!这样的话我们每次访问下一个节点的时候吧内存映射对象的基地址加上偏移量就得到了我们要找的下一个节点的地址。但是这种方法有些不好…一是比较慢,然后程序因为要进程额外的计算,使程序变得更大,第三就是因为这种方法比较少用,容易出错。

不管怎样,总有办法的…

 

17.7 内存映射文件的实现细节

在进程能够从自己的地址空间中访问内存映射文件的数据之前,Windows要求进程线调用 MapViewOfFile。如果一个进程调用了这个函数,那么系统会再调用进程的地址空间中为视图预定一块区域,其他任何进程都无法看到这个视图。如果另一个进程想要访问同一个文件映像中的数据,那么第二个进程也必须调用 MapViewOfFile,这样系统就会在第二个进程的地址空间中为视图预定一块区域。

还有一点非常重要,即第一个进程调用 MapViewOfFile 时返回的内存地址,与第二个进程调用 MapViewOfFile 时返回的内存地址,很可能是不相同的!及时两个进程都映射同一个文件映射对象的视图,情况还是如此。

现在我们来看看另外一个实现细节。下面这个小程序映射了同一个文件映射对象的两个视图:

上面的代码在VS下可以直接运行的。结果是正数负数都正常,但是就是不能是 64KB…

注意,文件大小必须大于64KB…

要记住一点:虽然他们是两个不同的区域,也不重叠,但是由于他们是同一个文件映射对象的视图,因此系统会保证其中的数据始终都是一致的。

 

17.8 用内存映射文件在进程间共享数据

Windows提供了很多机制,使得应用程序之间能够快速、方便地共享数据和信息。

这些机制包括: RPC(远程过程调用)、COM(应该是COM组件,若有错,请留言,谢谢。)、OLE(对象连接与嵌入)、DDE(动态数据交换)、Windows 消息(尤其是 WM_COPYDATA)、剪切板、邮件槽(mailslot)、管道(pipe)、套接字(socket)等。

在Windows中,在同一台机器上共享数据的最底层机制就是 内存映射文件

如果在同一台机器上的多个进程进行通信的话,那么刚才提到的所有机制归根结底都会用到内存映射文件。如果要求低开销和高性能,那么内存映射文件无疑是最好的选择。

这种共享机制是通过 让两个或多个进程映射同一个文件映射对象的视图 来实现的,这意味着在进程间共享相同的物理存储页面。因此,当一个进程在文件映射对象的视图中 写入数据的时候,其他进程会在它们的视图中立刻看到变化。注意:对多个进程共享一个文件映射对象来说,所有进程使用的文件映射对象的名称必须完全相同。

同所有的内核对象一样,我们可以通过三种技术来跨进程共享对象:句柄继承、命名 和 句柄复制。

 

17.9 以页交换文件为后备存储器的内存映射文件

我们前面可能提到了,页交换文件的后备存储器是磁盘上的文件。而虚拟内存的后备存储器是页交换文件。

这一小节我们会讲到创建以页交换文件为后备存储器的内存映射文件。这种做法的好处是不需要磁盘上专门的文件来做后备存储器。这种方法和为磁盘文件创建内存映射文件的方法几乎完全想同,甚至更简单。

一方面由于不必创建或打开一个专门的磁盘文件,因此不需要调用 CreateFile 函数。我们只需要像原来那样调用 CreateFileMapping 并将 INVALID_HADNLE_VALUE 作为 hFile 参数传入。这告诉系统我们创建的文件映射对象的物理存储器不是磁盘上的文件,而是希望系统从页交换文件中调拨物理存储器。

所需分配的大小由 CreateFileMapping 函数的 dwMaximumSizeHigh 和 dwMaximumSizeLow 参数决定。

一旦创建了文件映射对象,并把一个视图映射到了进程的地址空间中,我们就可以像使用任何内存地址一样使用它了。如果想要和其他进程共享数据,那么可以在调用 CreateFileMapping 的时候将一个以 0 为终止符的字符串作为 pszName 参数传入。这样其他想要访问共享数据的进程就能够以同一个名称为参数来调用 CreateFileMapping 或者 OpenFileMapping 函数。

当一个进程不再需要访问文件映射对象的时候,应该调用 CloseHandle。

当所有的句柄都已关闭的时候,系统会从页交换文件中收回所有已调拨的存储器。

 

Tips:这里有一个有趣的问题,你能看出下面的代码有什么问题吗?

 

解析:

如果调用 CreateFile 函数失败的话,会返回 INVALID_HANDLE_VALUE。但是,写这段代码的程序员不够细心,他没有检查文件的创建是否成功。在调用 CreateFileMapping 时,会将 INVALID_HANDLE_VALUE 作为 hFile 参数传入。这使得系统在创建文件映射的时候以 页交换文件 为后备存储器,而不是所希望的磁盘文件。

所有涉及内存映射文件的代码依旧可以正常工作,但是当系统销毁文件映射对象的时候,所有写入到文件映射对象的物理存储器(页交换文件)中的数据也会被一起销毁。

有点懵逼,简单来说就是,我写入了数据,但是根本没有创建新文件…心痛。我的数据全丢了…

有很多原因可能导致 CreateFile 失败,因此我们应该总是检查 CreateFile 的返回值,以了解是否有错误发生。

 

17.10 稀疏调拨的内存映射文件

到目前为止,我们看到的内存映射文件的后备存储器,要么是磁盘上的数据文件,要么是页交换文件。

这意味着对存储器的使用可能并不如我们希望的那么高效。

前面我们提到了电子表格的情况,比如 Excel表格。它可能最终会需要很大的空间,但是刚开始是不需要分配很大的空间的,这个还记得吗。比如绝大多数用户都只会在少数的几个单元格连存放信息。

很显然我们可能会把电子表格作为一个文件映射对象来共享。但又不希望在一开始就给它调拨所有的物理存储器。CreateFileMapping 为我们提供了一种方法,即在 fdwProtect 参数中指定 SEC_REVERSE 或 SEC_COMMIT 标志。

只有当以页交换文件为后备存储器来创建文件映射的时候,这些标志才有意义。SEC_COMMIT 标志让 CreateFileMapping 从页交换文件中调拨物理存储器。这和不指定任何标志具有相同的效果。

如果在传入 CreateFileMapping 的时候传入 SEC_REVERSE 标志,那么系统不会从页交换文件中调拨物理存储器,它只返回文件映射对象的句柄。现在我们可以调用 MapViewOfFile 或 MapViewOfFileEx 来给这个文件映射对象创建一个视图。MapViewOfFile(Ex) 函数会预定一块地址空间区域,但是不会给区域调拨任何物理存储器。试图访问区域中的内存地址将会引发违规访问。

现在我们有了一块地址空间区域 和 一个用来标识该区域的 文件映射对象的句柄。其他进程可以用同一个文件映射对象来把同一块地址空间映射到自己的视图中。但由于我们还没有给区域调拨物理存储器,因此如果其他进程试图访问映射到各自视图中的内存地址,同样会引发违规访问。

接下来,我们要给共享区域调拨物理存储器了…使用 Virtual Alloc函数。

第十五章已经对这个函数进行了详细的介绍。

http://blog.tk-xiong.com/archives/900

 

NT文件系统 (NTFS) 提供了对稀疏矩阵(sparse file)的支持。这是一项非常棒的特性。我们可以用这项特性来创建和处理稀疏内存映射文件,这样一来,存储器就不必总是在页交换文件中,而可以在普通磁盘中。

举个例子哈:假设我们要创建一个内存映射文件来存储录音的数据。当用户说话的时候,我们把数字音频数据写入到内存缓存,并以磁盘文件作为内存缓存的后备存储器。一个部分调拨的内存映射文件当然是最简单和最高效的方式。问题在于我们并不知道用户在停止录制之前会说多久,可能是五分钟,可能是五小时…这差距还是很大的。所以我们需要一个足够大的文件来保存这些数据。单是,在使用稀疏调拨的内存映射文件时,大小并没有多大关系。

 

接下来会是一些示例程序的教程:

DumpBin测试使用教程

 

Application Instance 示例程序

【Windows核心编程】内存映射文件
Tagged on:
0 0 投票数
Article Rating
订阅评论
提醒

0 评论
内联反馈
查看所有评论