- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
使用虚拟内存
ቤተ መጻሕፍቲ ባይዱ
虚拟内存,最适合用来管理大型对象或 结构数组。 内存映射文件,最适合用来管理大型数 据流(通常来自文件)以及在单个计算 机上运行的多个进程之间共享数据。 内存堆栈,最适合用来管理大量的小对 象。
使用虚拟内存-VirtualAlloc
通过调用Vi r t u a l A l l o c函数,可以在进程的地址空间 中保留一个区域: LPVOID VirtualAlloc(
毫无疑问,最容易的方法是设计一个C E L L D ATA结构,它的大小 只有一个页面。这时,由于始终都是每个页面使用一个结构,因此 当不再需要该结构中的数据时,就可以回收该页面的物理存储器。 即使你的数据结构是x86 CPU上的8 KB或12 KB页面的倍数(通常 这是非常大的数据结构),回收内存仍然是非常容易的。当然,如 果要使用这种方法,必须定义你的数据结构,使之符合你针对的C P U的页面大小而不是我们通常编写程序所用的结构。 更为实用的方法是保留一个正在使用的结构的记录。为了节省内存, 可以使用一个位图。这样,如果有一个1 0 0个结构的数组,你也可 以维护一个1 0 0位的数组。开始时,所有的位均设置为0,表示这些 结构都没有使用。当使用这些结构时,可以将对应的位设置为1然后, 每当不需要某个结构,并将它的位重新改为0时,你可以检查属于同 一个内存页面的相邻结构的位。如果没有相邻的结构正在使用,就 可以回收该页面。 最后一个方法是实现一个无用单元收集函数。这个方案依赖于这样 一种情况,即当物理存储器初次提交时,系统将一个页面中的所有 字节设置为0。若要使用该方案,首先必须在你的结构中设置一个B O O L(也许称为f I n U s e)。然后,每次你将一个结构放入已提 交的内存中,必须确保该fIn U s e被置于T R U E。
内存管理
当一个进程中的线程试图将数据写入一个共享内存块 时,系统就会进行干预,并执行下列操作步骤:
1) 系统查找R A M中的一个空闲内存页面。注意,当该模块 初次被映射到进程的地址空间时,该空闲页面将被页文件 中已分配的页面之一所映射。当该模块初次被映射时,由 于系统要分配所有可能需要的页文件,因此这一步不可能 运行失败。 2) 系统将试图被修改的页面内容拷贝到第一步中找到的页 面。该空闲页面将被赋予PA G E _ R E A D W R I T E或 PA G E _ E X E C U T E _ R E A D W R I T E保护属性。 原始页面的保护属性和数据不发生任何变化。 3) 然后系统更新进程的页面表,使得被访问的虚拟地址被 转换成新的R A M页面。
使用虚拟内存-VirtualAlloc
在已保留的区域中,你必须告诉Vi r t u a l A l l o c函数,你想将物理存储 器提交到何处,以及要提交多少物理存储器。为了做到这一点,可以在p v A d d r e s s参数中设定你需要的内存地址,并在d w S i z e参数中设定 物理存储器的数量(以字节为计量单位)。注意,不必立即将物理存储器 提交给整个区域。
内存管理-数据对齐的重要性
数据对齐并不是操作系统的内存结构的一部分,而是 C P U结构的一部分。 当C P U访问正确对齐的数据时,它的运行效率最高。 当数据大小的数据模数的内存地址是0时,数据是对 齐的。例如, W O R D值应该总是从被2除尽的地址 开始,而D W O R D值应该总是从被4除尽的地址开 始,如此等等。当C P U试图读取的数据值没有正确 对齐时, C P U可以执行两种操作之一。即它可以产 生一个异常条件,也可以执行多次对齐的内存访问, 以便读取完整的未对齐数据值。
使用虚拟内存-提交
保留一个关于哪些页面已经提交和哪些页面尚未提交的记录。这样 做可以使你的应用程序运行得更快,因为不必调用Vi r t u a l A l l o c 函数,你的代码能够比系统更快地确定内存是否已经被提交。它的 缺点是,必须不断跟踪页面提交的信息,这可能非常简单,也可能 非常困难,要根据你的情况而定。 使用结构化异常处理( S E H)方法,这是最好的方法。S E H是一 个操作系统特性,它使系统能够在发生某种情况时将此情况通知你 的应用程序。实际上可以创建一个带有异常处理程序的应用程序, 然后,每当试图访问未提交的内存时,系统就将这个问题通知应用 程序。然后你的应用程序便进行内存的提交,并告诉系统重新运行 导致异常条件的指令。这时对内存的访问就能成功地进行了,程序 将继续运行,仿佛从未发生过问题一样。这种方法是优点最多的方 法,因为需要做的工作最少(也就是说要你编写的代码比较少), 同时,你的程序可以全速运行。关于S E H的全面介绍,请参见第2 3、2 4和2 5章。第2 5章中的电子表格示例应用程序说明了如何按照 上面介绍的方法来使用虚拟内存。
使用虚拟内存-电子表格
1) 保留一个足够大的地址空间区域,用来存放C E L L D ATA结构的整个数组。保留一个根本不使用任何 物理存储器的区域。 2) 当用户将数据输入一个单元格时,找出C E L L D ATA结构应该进入的保留区域中的内存地址。当然, 这时尚未有任何物理存储器被映射到该地址,因此, 访问该地址的内存的任何企图都会引发访问违规。 3) 就C E L L D ATA结构来说,只将足够的物理存储 器提交给第二步中找到的内存地址(你可以告诉系统 将物理存储器提交给保留区域的特定部分,这个区域 既可以包含映射到物理存储器的各个部分,也可以包 含没有映射到物理存储器的各个部分)。 4) 设置新的C E L L D ATA结构的成员。
内存管理
内存管理
内存管理-页面
内存管理-物理存储器
在第一种情况中,线程试图访问的数据是在R A M中。在 这种情况下, C P U将数据的虚拟内存地址映射到内存 的物理地址中,然后执行需要的访问。 在第二种情况中,线程试图访问的数据不在R A M中,而 是存放在页文件中的某个地方。这时,试图访问就称为 页面失效, C P U将把试图进行的访问通知操作系统。 这时操作系统就寻找R A M中的一个内存空页。如果找不 到空页,系统必须释放一个空页。如果一个页面尚未被 修改,系统就可以释放该页面。但是,如果系统需要释 放一个已经修改的页面,那么它必须首先将该页面从R A M拷贝到页交换文件中,然后系统进入该页文件,找出 需要访问的数据块,并将数据加载到空闲的内存页面。 然后,操作系统更新它的用于指明数据的虚拟内存地址 现在已经映射到R A M中的相应的物理存储器地址中的表。 这时C P U重新运行生成初始页面失效的指令,但是这次 C P U能够将虚拟内存地址映射到一个物理R A M地址, 并访问该数据块。
使用虚拟内存-回收
若要回收映射到一个区域的物理存储器, 或者释放这个地址空间区域,可调用Vi r t u a l F r e e函数: BOOL VirtualFree(
LPVOID lpAddress, SIZE_T dwSize, DWORD dwFreeType );
使用虚拟内存-回收
内存管理
每个进程有它自己的私有地址空间。进程A可 能有一个存放在它的地址空间中的数据结构, 地址是0 x 1 2 3 4 5 6 7 8,而进程B则有一个 完全不同的数据结构存放在它的地址空间中, 地址是0 x 1 2 3 4 5 6 7 8。当进程A中运行的 线程访问地址为0 x 1 2 3 4 5 6 7 8的内存时, 这些线程访问的是进程A的数据结构。当进程 B中运行的线程访问地址为0 x 1 2 3 4 5 6 7 8 的内存时,这些线程访问的是进程B的数据结 构。进程A中运行的线程不能访问进程B的地 址空间中的数据结构。反之亦然。
LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect ); 第一个参数p v A d d r e s s包含一个内存地址,用于设定想 让系统将地址空间保留在什么地方。在大多数情况下,你为 该参数传递M U L L。 如果Vi r t u a l A l l o c函数能够满足你的要求,那么它就返 回一个值,指明保留区域的基地址。如果传递一个特定的地 址作为Vi r t u a l A l l o c的p v A d d r e s s 参数,那么该返 回值与传递给Vi r t u a l A l l o c的值相同,并被圆整为(如 果需要的话) 6 4 K B边界值。
使用虚拟内存-提交
始终设法进行物理存储器的提交。每次调用Vi r t u a l A l l o c函数的时候,不要查看物理存储器是否已经映 射到地址空间区域的一个部分,而是让你的程序设法 进行内存的提交。系统首先查看内存是否已经被提交, 如果已经提交,那么就不要提交更多的物理存储器。 这种方法最容易操作,但是它的缺点是每次改变C E L L D ATA结构时要多进行一次函数的调用,这会使程 序运行得比较慢。 使用Vi r t u a l Q u e r y函数确定物理存储器是否已经 提交给包含C E L L D ATA结构的地址空间。如果已经 提交了,那么就不要进行任何别的操作。如果尚未提 交,则可以调用Vi r t u a l A l l o c函数以便提交内存。 这种方法实际上比第一种方法差,它既会增加代码的 长度,又会降低程序运行的速度(因为增加了对Vi r t u a l A l l o c函数的调用)。
有时你可能想要在保留区域的同时,将物理存储器提交给它。只需要一 次调用Vi r t u a l A l l o c函数就能进行这样的操作,如下所示:
使用虚拟内存-电子表格
传统上,电子表格一直是用其他数据结构技术来实现 的,比如链接表等。使用链接表,只需要为电子表格 中实际包含数据的单元格创建C E L L D ATA结构。 由于电子表格中的大多数单元格都是不用的,因此这 种方法可以节省大量的内存。但是这种方法使得你很 难获得单元格的内容。如果想知道第5行第1 0列的单 元格的内容,必须遍历链接表,才能找到需要的单元 格,因此使用链接表方法比明确声明的矩阵方法速度 要慢。 虚拟内存为我们提供了一种兼顾预先声明二维矩阵和 实现链接表的两全其美的方法。运用虚拟内存,既可 以使用已声明的矩阵技术进行快速而方便的访问,又 可以利用链接表技术大大节省内存的使用量。
使用虚拟内存-改变保护属性
若要改变内存页面的保护属性,可以调 用Vi r t u a l P r o t e c t函数: BOOL VirtualProtect(
LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect );
Windows核心编程
许红星 Email:hxxu@ynu.edu.cn 云南大学软件学院
内存管理
进程的虚拟地址空间 每个进程都被赋予它自己的虚拟地址空间。对于3 2位进 程来说,这个地址空间是4 G B,因为3 2位指针可以拥 有从0 x 0 0 0 0 0 0 0 0至0 x F F F F F F F F之间的任何 一个值。这使得一个指针能够拥有4 294 967 296个值中 的一个值,它覆盖了一个进程的4 G B虚拟空间的范围。 对于6 4位进程来说,这个地址空间是1 6 E B(1 01 8字 节),因为6 4位指针可以拥有从0 x 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0至0 x F F F F F F F F F F F F F F F F之间的 任何值。这使得一个指针可以拥有18 446 744 073 709 551 616个值中的一个值,它覆盖了一个进程的1 6 E B 虚拟空间的范围。