《Windows核心编程系列》十三谈谈在应用程序中使用虚拟内存
在应用程序中使用虚拟内存
Windows提供了以下三种机制对内存进行操控:
一:虚拟内存。最适合来管理大型对象数据或大型结构数组。
二:内存映射文件。最适合用来管理大型数据流,以及在同一机 器上运行的多个进程之间共享数据。
三:堆。最适合用来管理大量的小型对象。
很多人都对VirtualAlloc和malloc 或new的区别不是很清楚,我也一样。今天搜索下了,发现这句话说的很清楚了:
VirtualAlloc要进入内核模式,算法特复杂,比较慢,而且分配粒度是4k,用来分配小块内存很浪费
malloc先用VirtualAlloc弄一大块内存,后面在堆上分配时就不用进入内核模式,算法也简单些,而且分配粒度比较小
VirtualAlloc只能分配4KB为单位的页面,适合大型数据或者内存映射文件等用途。而堆的申请分配就没有这个限制,更为灵活。
有的人嫌malloc还不够精简,于是又在堆上面开辟自己的内存池,更加轻量级
本文将主要介绍虚拟内存。
Windows提供了一些用来操纵虚拟内存 的函数,我们可以通过这些函数直接预订地址空间区域,并给这些预订的区域调拨来自页交换文件的物理存储器。
预定地址空间区域。
可以通过调用VirtualAlloc函数来运行:
- PVOID VirtualAlloc(
- PVOID pvAddress,
- SIZE_T dwSize,
- DWORD fdwAllocationType,
- DWORD fdwProtect);
pvAddress是内存地址。用来告诉我们想要运行地址空间中的哪一块。
当传入NULL时,系统会自动找到一块闲置区域。
如果在pvAddress标识的内存块中找不到闲置区域,或闲置区域不够大函数将返回NULL。
如果VirtualAlloc能满足我们的要求,它会预定一块区域并返回该区域的基地址。
dwSize用来指定我们想要预订的区域大小。以字节为单位。 系统始终以cpu页面大小整数倍来预定区域。且起始地址是按照分配粒度64kB的整数倍来预定的。
fdwAllocationType用来告诉系统我们到底是要预订还是要调拨物理存储器。如果要预订区域可以传入:MEM_RESERVE。如果我们想让系统从尽可能高的内存地址来预定区域,必须传入NULL给pvAddress,同时对MEM_TOP_DOWN和MEM_RESERVE标志进行按位或操作。
fdwProtect给区域指定保护属性。区域的保护属性对调拨给该区域的物理存储器不起任何作用。无论指定何种保护属性,只要还未给该区域调拨物理存储器都会导致访问违规。
预订时指定的属性应该跟调拨时指定的属性相同,这样系统内部处理效率会更高。
调拨物理存储器
预定区域后还需要给该区域调拨物理存储器。系统会从页交换文件中调拨物理存储器给该区域。在调拨物理存储器时,起始地址和区域大小始终都是页面大小的整数倍。调拨物理存储器同样需要调用VirtualAlloc。但这次我们需要传入MEM_COMMIT来作为fdwAllocationType的值。
pvAddress标识要调拨物理存储器的区域的起始地址。
dwSize表示物理存储器的大小。以字节为单位。我们并不需要给整个区域都调拨物理存储器。
有了起始地址和大小就可以标识一段区域。
同时预定和调拨物理存储器
有时我们项同时预定区域并给该区域调拨物理存储器。同样需要调用VirtualAlloc。但是MEM_RESERVE要和MEM_COMMIT按位或并将它们传给fdwAllocationType。此时系统会为整个区域调拨物理存储器。
撤销调拨物理存储器及释放区域。
要撤销调拨给区域的物理存储器或是释放地址空间中的一整块区域。可以调用VirtualFree函数:
- BOOL VirtualFree(
- LPVOID pvAddress,
- SIZE_T dwSize,
- DWORD fdwFreeType);
pvAddress参数必须是区域的基地址。该地址就是预定区域时VirtualAlloc返回的地址。由于系统在内部会记录该地址处的区域大小,因此我们可以且必须传入0给dwSize。
当传入MEM_RELEASE给fdwFreeType时是想告诉系统撤销调拨给该区域的所有物理存储器,并释放该区域。
我们可以撤销调拨给该区域的一部分物理存储器。此时需要传入pvAddress来告诉系统我们想要撤销调拨的区域的起始地址。dwSize传入想要撤销调拨区域的物理存储器大小,并传入MEM_DECOMMIT给fdwFreeType。
和调拨物理存储器一样,撤销调拨也是基于页面粒度的。也就是说,如果给定的地址位于一个页面中,那么系统会撤销调拨整个页面。如果dwSize为0,而pvAddress又是该区域的基地址,那么VirtualFree会调拨给该区域的所有页面。
改变保护属性
实际中我们很少需要改变已调拨物理存储器的保护属性。比如,我们可以写一个管理链表的程序,把链表中的节点保存在一个已预订的区域中。我们可以设计这样的链表处理函数,让它们在每个函数的开头把物理存储器的保护属性改成PAGE_READWRITE,访问后,再把保护属性改回FPAGE_NOACCESS。这样就可以保护链表数据免受程序中其他缺陷的影响。
我们可以调用VirtualProtect函数来改变一个内存页面的保护属性:
- BOOL VirtualProtect(
- PVOID pvAddress,
- SIZE_T dwSize,
- DWORD flNewProtect,
- PDWORD pflOldProtect);
pvAddress指向内存基地址。
dwSize表示要改变保护区域的大小,以字节为单位。
flNewProtect可以是除了PAGE_WRITECOPY和PAGE_EXECUTE_WRITECOPY之外的任何PAGE_*属性。
pflOldProtect是一个指针,返回原来的属性。一定不可以传入NULL,必须传入一个有效的地址给VirtualProtect,否则函数执行失败。
由于保护属性是与整个物理存储页相关联的,VirtualProtect会改变pvAddress和dwSize跨越的所有页面的属性。而不会仅仅对一个字节改变属性,这是没有意义的。
重置物理存储器的内容
当我们修改物理内存页时,系统会尽量把改动把持在内存中。当应用程序在运行时,可能需要将数据载入内存,系统会在内存中查找可用的页面。如果找到闲置页面,就将数据载入此页面。如果没有找到,系统就会采用一定的算法,置换一些页面。如果该页面已经被修改过,系统会将这些页面置换到页交换文件。
Windows提供一项特性,使得应用程序能够提高自身的性能。这一特性就是重置物理存储器。重置物理存储器的意思是告诉系统一个或几个物理存储器页中数据没有被修改过。在需要闲置页面时,可以直接将它们覆盖,而不需要将它们写入页交换文件。有些应用程序需要在一小段时间内使用存储器,之后就不需要保存存储器中的内容。为了提高性能,应用程序应该告诉系统此页面没有被修改过,不要在页交换文件中保存存储页。
为了重置存储器,应用程序应该调用VirtualAlloc函数并在第二个参数中传MEM_RESET标志。
- PINT pnData=(PINT)VirtualAlloc(NULL,1024,
- MEM_RELEASE|MEM_COMMIT,PAGE_READWRITE);
- pnData[0]=100;
- pnData[1]=200;
- VirtualAlloc((PVOID)pnData,sizeof(int),MEM_RESET,PAGE_READWRITE);
这段代码首先调拨了一块存储器,然后将这块内存的前4个字节标记为可以被重置。但是重置存储器的操作会调用失败。第二个VirtualAlloc会返回NULL。GetLastError会返回ERROR_INVALID_ADDRESS。
这是因为在传入MEM_RESET给VirtualAlloc时,函数会把基地址向下取整到页面大小的整数倍。(其他标志时都是向上去整,此处要注意。)其目的是确保在同一页面中还有其他重要数据的情况下,不会把它们丢弃。前面的例子,向下去整到页面整数倍是0,这是没有意义的。也就是说我们重置的页面必须是一个或几个完整的页面。不足一页的为了保护数据就不将它们设为可重置。
还要注意MEM_RESET不能和其他标志一起使用,它不合群,只能单独使用。否则调用会失败。
VirtualQuery函数。
VirtualQuery可以用来查询与地址空间有关的特定信息。比如大小,存储器类型及保护属性。
- DWORD VirtualQuery(
- LPCVOID pvAddress,
- PMEMORY_BASIC_INFORMATION pmbi,
- DWORD dwLength);
pvAddress指定需要查询的虚拟内存地址。
dwLength指定MEMORY_BASIC_INFORMATION结构大小。
pmbi返回MEM_BASIC_INFORMATION结构地址。该地址定义如下:
- typedef struct _MEMORY_BASIC_INFORMATION
- {
- PVOID BaseAddress;//等于pvAddress向下取整到页面大小。
- PVOID AllocationBase;//区域基地址。
- DWORD AllocationProtect;//预定区域时的保护属性。
- SIZE_T RegionSize;//区域大小。以字节为单位。以BaseAddress为起始地址。
- DWORD State;//区域页面的状态。
- DWORD Protect;//保护属性
- DWORD Type;//
- }MEMORY_BASIC_INFORMATION,*PMEMORY_BASIC_INFROMATION;
VirtualQueryEx与VirtualQuery的区别就是它可以传入一个进程句柄。也就意味着它可以查询其他进程地址空间的信息。
GlobalMemoryStatus可以用来取得当前内存状态的动态信息:
- VOID GlobalMemoryStatus(LPMEMORYSTATUS lpBuffer);
MEMORYSTATUS结构定义如下:
- typedef struct _MEMORYSTATUS
- {
- DWORD dwLenght;//此结构大小。调用函数前必须初始化。
- DWORD dwMemoryLoad;//
- SIZE_T dwTotalPhys;
- SIZE_T dwAvailablePhys;
- SIZE_T dwTotalPageFile;
- SIZE_T dwAvailablePageFile;
- SIZE_T dwTotalVirtual;
- SIZE_T dwAvailVirtual;
- }MEMORYSTATUS,*LPMEMORYSTATUS;
dwMemoryLoad成员告诉我们内存管理系统有多忙,它可以是0-100之间的任何值。实际中,这个值没有什么用处。
dwTotalPhys成员表示物理内存总量。
dwAvailPhys成员表示可分配的内存总理。都是以字节为单位。
dwTotalPageFile表示硬盘页交换文件最多能存放多少字节的数据。
dwTotalVirtual表示地址空间中各进程私有的那部分字节数。比2G少128k.从0x00000000-0x0000FFFF(空指针赋值分区)和从0x7FFF0000-0x7FFFFFFF(64KB进入分区)这两个分区应用程序不能访问。
dwAvailVirtual是唯一一个与进程有关的成员。它表示还有多少闲置的地址空间可以被使用。也就说dwTotalVirtual-进程预定的地址空间就等于dwAvailVirtual。
系统信息
操作系统的许多值是由系统所运行的主机决定的,如页面大小和分配粒度。使用GetSystemInfo可以获得与主机有关的值:
- VOID GetSystemInfo(LPSYSTEM_INFO psi);
- SYSTEM_INFO结构定义如下:
- typedef struct _SYSTEM_INFO{
- union{
- struct {
- WORD wProcessorArchitecture;
- WORD wReserved;
- };
- };
- DWORD dwPageSize;
- LPVOID lpMinimumApplicationAddress;
- LPVOLID lpMaximumApplicationAddress;
- DWORD_PTR dwActiveProcessorMask;
- DWORD dwNumberOfProcessor;
- DWORD dwProcessorType;
- DWORD dwAllocationGranularity;
- WORD wProcessorLevel;
- WORD wProcessorRevision;
- }SYSTEM_INFO,*LPSYSTEM_INFO;
上述这么多成员只有四个与内存有关。
dwPageSize表示cpu页面大小在x86和x64机器中,该值为4K。
lpMinimumApplicationAddress给出进程中可用地址空间最小的内存地址。由于每个进程地址空间最开始的64K是闲置的,因此该值为64K。
lpMaximumApplicationAddress给出每个进程私有地址空间中最大的
可用内存地址。
dwAllocationGranularity表示用于预定地址空间区域的分配粒度
其他的成员稍微做下介绍:
wReserved为今后拓展保留,不要使用。
dwNumOfProcessor机器cpu的数量。
dwActiveProcessorMask位掩码,用来表示哪些cpu处于活动状态。可以用来运行线程。
dwProcessorType已经作废。不再使用。
wProcessorArchitecture表示处理器的体系结构。如x86,x64;
wProcessorLevel进一步细分处理器的体系结构,比如Intel奔腾2或奔腾4.
wProcessorRevision进一步对wProcessLevel进行细分。
//以下代码演示了可重置内存及以上介绍的各个函数的使用。
- #include"windows.h"
- #include<iostream>
- #include"tchar.h"
- TCHAR TextData[]=TEXT("c:\Users\yangyang\documents\visual studio 2010\Projects\MEM_RESET\MEM_RESET\main.cppc:\Users\yangyang\documents\visual studio 2010\Projects\MEM_RESET\MEM_RESET\main.cpp");
- int main(int argc,char**argv)
- {
- PTSTR pszData=(PTSTR)VirtualAlloc(NULL,1024,MEM_RESERVE|MEM_COMMIT,PAGE_READWRITE);
- if(pszData==NULL)
- {
- MessageBox(NULL,TEXT("分配失败"),TEXT(""),MB_OK);
- }
- _tcscpy_s(pszData,1024,TextData);
- int ret=MessageBox(NULL,TEXT("以后是否还要访问该段内存?"),TEXT(""),MB_YESNO);
- if(ret==IDNO)
- {
- MEMORY_BASIC_INFORMATION mbi;
- VirtualQuery(pszData,&mbi,sizeof(mbi));
- VirtualAlloc(pszData,mbi.RegionSize,MEM_RESET,PAGE_READWRITE);
- }
- MEMORYSTATUS mst;
- GlobalMemoryStatus(&mst);
- PVOID pvAddress=VirtualAlloc(NULL,mst.dwAvailVirtual,MEM_RESERVE|MEM_COMMIT,PAGE_READWRITE);
- if(pvAddress)
- {
- ZeroMemory(pvAddress,mst.dwAvailVirtual);
- }
- if(!_tcscmp(TextData,pszData))
- {
- MessageBox(NULL,TEXT("已经保存!"),TEXT(""),MB_YESNO);
- }
- else
- {
- MessageBox(NULL,TEXT("未保存!"),TEXT(""),MB_YESNO);
- }
- getchar();
- VirtualFree(pvAddress,0,MEM_RELEASE);
- VirtualFree(pszData,0,MEM_RELEASE);
- return 0;
- }
《Windows核心编程系列》十三谈谈在应用程序中使用虚拟内存的更多相关文章
- 读书笔记——Windows核心编程(15)在应用程序中使用虚拟内存
微软的Windows提供了三种机制对内存进行操控 1 虚拟内存(最适合管理大型对象数组或大型结构数组) 2 内存映射文件(大型数据流/文件,共享数据) 3 堆(大量的小型对象) 预订地址空间区域Vi ...
- 《windows核心编程系列》十八谈谈windows钩子
windows应用程序是基于消息驱动的.各种应用程序对各种消息作出响应从而实现各种功能. windows钩子是windows消息处理机制的一个监视点,通过安装钩子能够达到监视指定窗体某种类型的消息的功 ...
- 《windows核心编程系列》十九谈谈使用远程线程来注入DLL。
windows内的各个进程有各自的地址空间.它们相互独立互不干扰保证了系统的安全性.但是windows也为调试器或是其他工具设计了一些函数,这些函数可以让一个进程对另一个进程进行操作.虽然他们是为调试 ...
- 《Windows核心编程系列》二十谈谈DLL高级技术
本篇文章将介绍DLL显式链接的过程和模块基地址重定位及模块绑定的技术. 第一种将DLL映射到进程地址空间的方式是直接在源代码中引用DLL中所包含的函数或是变量,DLL在程序运行后由加载程序隐式的载入, ...
- 《windows核心编程系列》十七谈谈dll
DLL全称dynamic linking library.即动态链接库.广泛应用与windows及其他系统中.因此对dll的深刻了解,对计算机软件开发专业人员来说非常重要. windows中所有API ...
- 《windows核心编程系列》二谈谈ANSI和Unicode字符集 .
http://blog.csdn.net/ithzhang/article/details/7916732转载请注明出处!! 第二章:字符和字符串处理 使用vc编程时项目-->属性-->常 ...
- 《windows核心编程系列》二十一谈谈基址重定位和模块绑定
每个DLL和可执行文件都有一个首选基地址.它表示该模块被映射到进程地址空间时最佳的内存地址.在构建可执行文件时,默认情况下链接器会将它的首选基地址设为0x400000.对于DLL来说,链接器会将它的首 ...
- 《windows核心编程系列》十六谈谈内存映射文件
内存映射文件允许开发人员预订一块地址空间并为该区域调拨物理存储器,与虚拟内存不同的是,内存映射文件的物理存储器来自磁盘中的文件,而非系统的页交换文件.将文件映射到内存中后,我们就可以在内存中操作他们了 ...
- 《Windows核心编程系列》十四谈谈默认堆和自定义堆
堆 前面我们说过堆非常适合分配大量的小型数据.使用堆可以让程序员专心解决手头的问题,而不必理会分配粒度和页面边界之类的事情.因此堆是管理链表和数的最佳方式.但是堆进行内存分配和释放时的速度比其他方式都 ...
随机推荐
- Oracle Spatial中的空间索引
转自cryolite原文 Oracle Spatial中的空间索引 Oracle Spatial可对空间数据进行R-tree索引,每个空间图层(Spatial Layer)的空间索引元信息都可以在US ...
- 基于commons-net实现ftp创建文件夹、上传、下载功能
原文:http://www.open-open.com/code/view/1420774470187 package com.demo.ftp; import java.io.FileInputSt ...
- Office WORD WPS如何设置PPT播放全屏
1 在设计-页面设置中,幻灯片大小改成自定义,高度和宽度如下图所示.(我个人的笔记本是15.6存的宽屏笔记本,你可以根据自己笔记本的比例修改宽度和高度的数据来或者不同的比例值),注意在HDMI的输出方 ...
- 让你的 EditText 所有清除
原文地址:让你的 EditText 所有清除 參考原文:Giving your Edit Texts the All Clear 项目地址(欢迎 Star):ClearEditText 在输入文本的时 ...
- Windows-安装composer
安装laravel之前必须先安装componser,点击:下载Windows安装程序 全部下一步,直到完成 目前,遇到过两个问题(国内防火墙) 还有就是Win10不支持PHP+Composer的组合, ...
- Bootstrap的js插件之弹出框(popover)
data-toggle="popover"--使弹出框可以切换状态: title--设置弹出框的标题: data-content--设置弹出框的内容部分: data-placeme ...
- C# LINQ Unity 单例
C# LINQ 1. 自定义 Master,Kongfu 类 1 class Master 2 { 3 4 public int Id { get; set; } 5 public string ...
- 遍历数据库全部表,将是datetime类型的列的值进行更新
declare @tablename nvarchar(80) declare @cloumn nvarchar(80) declare @sql nvarchar(400) declare ...
- 我的家乡:三河古镇已经登上央视CCTV-1新闻联播啦!
在烟雨朦胧时走在古镇的青石街上,别有一番风味!第一幅图为央视的直播车,第二副图为漂亮的三河夜景色!
- Android Material Design-Maintaining Compatibility(保持兼容性)-(七)
转载请注明出处:http://blog.csdn.net/bbld_/article/details/40634829 翻译自: http://developer.android.com/traini ...