虚拟内存:
每个进程有自己的虚拟内存,进程内的线程只能访问该进程的虚拟内存,而无法访问其他进程的虚拟内存,也无法访问系统的内存。进程地址空间的划分如下:
(图5 - 1)
l 空指针:
0x00000000到0x0000FFFF的区间,线程访问该区间的地址,将会引发访问违规。注意,一个没有赋初值的指针不是空指针(在Debug下,默认值为0xCCCCCCCC,是属于内核区间的地址,也会引发访问违规;在Release下,则是一个随机的地址)。
l 用户模式区间:
在X86(32位)下,用户进程可用的地址空间大小为2G,系统可用的地址空间也为2G。在x64(64位)下,用户进程可用的地址空间大小为8T,剩余的地址空间为系统可用的地址空间。
X86下的3G用户内存模式:X86下,可设置用户内存空间为3G,系统内存空间为1G(即大内存模式,与此相关的设置参见BCD 和 /LARGEADDRESSAWARE开关的资料)。但使用大内存模式,限制了内核可用的地址空间,从而限制系统所能创建的线程、栈以及其它资源的数量,因此一般情况下不提倡使用。
X64用户内存模式:用户进程可用的地址空间大小为8T,但需要打开/LARGEADDRESSAWARE链接器开关,否则,程序仍然默认使用2G的用户内存空间。
内存的分配原则:
系统分配内存时,首地址必须是分配粒度(64K)的整数倍;同时内存大小必须是页面大小(X86和X64为4K)的整数倍,因为页面时内存分配时的最小单位。
l 内存块:块是一些连续的页面,这些页面具有相同的保护属性,并以相同类型的物理存储器作为后备存储器。一个内存区域一般包括多个内存块。
l 页面的保护属性:每个内存页面可以指定不同的保护属性。保护属性大致分为两大类,即以“PAGE_*”命名的属性和以“PAGE_EXECUTE_*”命名的保护属性。
定义如下:
PAGE_NOACCESS:不能访问页面,不能执行代码;
PAGE_READONLY:只能读取页面数据,不能写入数据;不能执行代码;
PAGE_READWRITE:可读写数据,不能执行代码;
PAGE_WRITECOPY:写入数据时创建一份该页面的私有备份;不能执行代码;
PAGE_EXECUTE:不能访问页面;
PAGE_EXECUTE_READ:只能读取页面数据,不能写入数据;
PAGE_EXECUTE_READWRITE:可读写数据;
PAGE_EXECUTE_WRITECOPY:写入数据时创建一份该页面的私有备份;
(只有使用以“PAGE_EXECUTE_*”命名的保护属性时,才能执行代码。否则,最多只能操作数据,这样能够有效的防止恶意软件执行恶意代码。)
物理存储器的构成:
虚拟内存实际上是由内存和硬盘共同构成的。磁盘上的文件称为页交换文件(磁盘上的虚拟内存),页交换文件可增大应用程序可用的内存总量。系统将虚拟地址转换为物理存储器地址的流程如下:
可以看得出来,假如数据在页交换页面上,系统需要在内存和硬盘之间进行数据搬移,而数据搬移的频率越高,硬盘颠簸得越厉害,系统运行越慢。(有时候加大内存比提升CPU速度,更能有效的提升系统运行速度,就是这个道理,因为可以减少内存和硬盘之间的数据来回搬移)
(图5 - 2)
内存对齐:
只有对齐了的内存,其访问效率才是最高的,因为如果内存没有对齐,CPU访问它的时候需要修复错位,这个操作将增加时间。
获取内存信息的函数:
GetSystemInfo():获得与主机CPU相关的值(分配粒度、页面大小等);
GlobaMemoryStatus():获得当前内存状态的动态信息;
VirtualQuery():获取虚拟内存的详细信息(基地址、保护属性等)。
分配和释放内存的函数:
l 分配内存:
VirtualAlloc(
PVOID pvAddress,
SIZE_T dwSize,
DWORD fdwAllocationType,
DWORD fdwProtect);
pvAddress:内存地址(若传NULL,则系统自己选定一个地址);
dwSize:需要分配的大小;
fdwAllocationType:分配标志(MEM_RESERVE:预订; MEM_COMMIT:调拨; MEM_RESET:重置。)
fdwProtect:内存的保护属性。(同一物理存储页面,只能有一个保护属性)
说明:在分配虚拟内存时,都需要先预订,再调拨。
(1)预订:告知系统,这块内存已经被占用,不能再分配它。只是给内存打上“已占用”的标记。
(2)调拨:真正的给内存分配物理存储器。如果只是预订,是不会分配物理存储器的。只有调拨了的内存,才能用来访问数据。
(3)重置:告知系统,这块内存是闲置的,系统可以重新对该内存进行分配。
在下次调用VirtualAlloc()时,如果引用到的页面在页交换文件中,系统会直接抛弃这些页面,分配新的页面;如果页面在内存中,那么系统会将其标记为没有修改过,而这块内存可以直接拿来用,但是该内存是没有清零的(这样做的好处是:由于虚拟内存已经关联了物理存储器,就不需要再重新分配,大大提升了效率)。
对于用户来说,一旦重置了一块内存,在对其重新分配之前,都应将其看做“已释放”的内存,不应当再使用它。
内存重置的应用很广,我们在程序中释放一块内存的时候,系统通常都是使用“重置”操作,而不是实质性的去释放物理存储器。这就是为什么我们有时候会发现,一块释放了的内存,其中的数据并没有被清零。
l 释放内存:
VirtualFree(
LPVOID pvAddress,
SIZE_T dwSize,
DWORD fdwFreeType);
pvAddress:内存地址(若地址在一个页面的中间,将释放整个页面);
dwSize:释放的内存大小(传0,系统自动释放应释放的内存);
fdwFreeType:释放标志(MEM_DECOMMIT:对该地址指向的第一个页面的内存撤销调拨;MEM_RELEASE:释放该区域所有的内存)。
l 更改内存的保护属性:
VirtualProtect()。