📄 010.txt
字号:
10.1 内 存 管 理
10.1.1 内存管理基础
Win32中的内存管理是分层次的,系统提供了几组层次不同的函数来管理内存,它们是标准内存管理函数、堆管理函数、虚拟内存管理函数和内存映射文件函数。所有的这些函数都是为了让用户能在比较高的层次上方便地管理内存,以便将程序和底层的内存分页机制隔离开来。如图10.1所示,这几组函数的层次是各不相同的。
图10.1 Windows的内存分层管理
Windows使用一个以页为基础的虚拟内存系统,与分页有关的概念已经在第1章的1.3.2小节中有所介绍。Windows充分利用了80x86处理器保护模式下的线性寻址机制和分页机制,这些机制是Win32内存管理的基础,Win32提供了一组虚拟内存管理函数来管理虚拟内存,主要用于保留/提交/释放虚拟内存,在虚拟内存页上改变保护方式,锁定虚拟内存页以及查询一个进程的虚拟内存等操作,这是一组位于底层的函数。
堆管理函数相对比较高级一点,堆的主要功能就是有效地管理内存和进程的地址空间。DOS操作系统下的C语言中就已经有了“堆”的概念,这时的“堆”是程序初始化时向操作系统申请并预留的大块内存,程序通过C函数在这块空间中申请和释放内存。
在Win32中,进程可以使用的整个地址空间就是一个堆。并且“堆”的概念又被引伸了一步:Win32中分两种堆,一种是进程的“默认堆”,默认堆只有一个,指的就是可以使用的整个地址空间;另一种是“动态堆”,也称为“私有堆”,私有堆类似于DOS下C语言中使用的那种堆,一个进程可以随意建立多个私有堆,也可以随意将它们释放,私有堆全部位于默认堆中,从概念上看,它和默认堆并没有什么不同,就像一个跨国公司和属下的子公司同样都是按照公司的规程操作一样。使用堆管理函数可以对所有的私有堆和默认堆进行操作。
标准内存管理函数总是在默认堆中分配和释放内存,这组函数就是常规意义上的内存管理函数。
内存映射文件函数相对比较独立,它是为了文件操作的方便性而设立的,当对文件进行操作的时候,一般总是先打开文件,然后申请一块内存用做缓冲区,再将文件数据循环读入并处理,当文件长度大于缓冲区长度的时候需要多次读入,每次读入后处理缓冲区边界位置的数据往往是个麻烦的问题。曾经介绍过Windows可以使用磁盘文件当做虚拟内存(参考图1.5,虚拟内存的实现),内存映射文件函数使用同样的办法将一个文件直接映射到进程的地址空间中,这样可以通过内存指针用读写内存的办法直接存取文件内容。
对比这些函数,可以发现它们涉及的系统资源是各不相同的,如表10.1所示。
表10.1 不同内存管理函数的操作对象
内存管理函数
涉 及 方 面
标准内存管理函数
一个进程的默认堆
堆管理函数
一个进程的虚拟地址空间、系统内存、进程堆资源结构
虚拟内存管理函数
一个进程的虚拟地址空间、系统页文件、系统内存、硬盘空间
内存映射文件函数
一个进程的虚拟地址空间、系统页文件、系统内存、硬盘空间、标准文件I/O
10.1.2 内存的当前状态
在第1章中已经介绍过,一个进程可以寻址的地址空间是4 GB,但用户可以直接管理的地址空间是多大呢?实际上,高端的2 GB是供操作系统内核使用的,其中安排了操作系统的代码和数据(Windows 9x中还包括共享内存映射的地址空间),可供应用程序使用的地址空间是低端的2 GB,这2 GB除去应用程序与用户DLL等的代码和静态数据段以后,余下来的才是内存管理函数可以使用的地址空间,应用程序和用户DLL的大小一般只有几兆字节到上百兆字节,所以可以认为能自由使用的地址空间基本上是2 GB。
既然用户可以使用的地址空间大概为2 GB,读者千万不要认为就可以申请2 GB的内存了,因为这2 GB仅是可以使用的“地址”空间,而不是可以使用的“内存”空间,可分配内存的大小还受制于物理内存和磁盘交换文件的大小。因为物理内存和磁盘交换文件是供整个系统和所有用户程序使用的,所有系统内核、当前执行的所有用户程序的代码、数据以及分配的内存总量并不能超过物理内存和磁盘交换文件的总和。
当设计一个可能需要申请大量内存的程序时,如何预先得知系统的配置情况呢?对此可以使用GlobalMemoryStatus函数:
invoke GlobalMemoryStatus,lpBuffer
lpBuffer指向一个MEMORYSTATUS结构,结构的定义如下:
MEMORYSTATUS STRUCT
dwLength DWORD ? ;本结构的长度
dwMemoryLoad DWORD ? ;已用内存的百分比
dwTotalPhys DWORD ? ;物理内存总量
dwAvailPhys DWORD ? ;可用物理内存
dwTotalPageFile DWORD ? ;交换文件总的大小
dwAvailPageFile DWORD ? ;交换文件中空闲部分大小
dwTotalVirtual DWORD ? ;用户可用的地址空间
dwAvailVirtual DWORD ? ;当前空闲的地址空间
MEMORYSTATUS ENDS
在调用之前需要首先将dwLength字段设置为MEMORYSTATUS结构的长度,当调用GlobalMemoryStatus函数后,函数会在结构中返回对应的数值。注意:dwTotalPageFile字段返回的是交换文件的最大值,并不是当前实际建立的交换文件的大小,一般当前的交换文件大小会小于这个数值,但这个数值的大小也不是确定的,如果需要的话,系统会增加它的大小直到不再有空余的磁盘空间放置交换文件为止。
在所附光盘的Chapter10\MemInfo目录中的MemInfo.asm文件利用这个功能定时获取并显示当前内存的使用信息,源代码如下:
.386
.model flat, stdcall
option casemap :none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Include 文件定义
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Equ 等值定义
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
ICO_MAIN equ 1000
DLG_MAIN equ 100
IDC_INFO equ 101
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 数据段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data?
hInstance dd ?
hWinMain dd ?
.const
szInfo db ~物理内存总数 %lu 字节~,0dh,0ah
db ~空闲物理内存 %lu 字节~,0dh,0ah
db ~虚拟内存总数 %lu 字节~,0dh,0ah
db ~空闲虚拟内存 %lu 字节~,0dh,0ah
db ~已用内存比例 %d%%~,0dh,0ah
db ~————————————————~,0dh,0ah
db ~用户地址空间总数 %lu 字节~,0dh,0ah
db ~用户可用地址空间 %lu 字节~,0dh,0ah,0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 代码段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_GetMemInfo proc
local @stMemInfo:MEMORYSTATUS
local @szBuffer[1024]:byte
mov @stMemInfo.dwLength,sizeof @stMemInfo
invoke GlobalMemoryStatus,addr @stMemInfo
invoke wsprintf,addr @szBuffer,addr szInfo,\
@stMemInfo.dwTotalPhys,@stMemInfo.dwAvailPhys,\
@stMemInfo.dwTotalPageFile,\
@stMemInfo.dwAvailPageFile,\
@stMemInfo.dwMemoryLoad,\
@stMemInfo.dwTotalVirtual,@stMemInfo.dwAvailVirtual
invoke SetDlgItemText,hWinMain,IDC_INFO,addr @szBuffer
ret
_GetMemInfo endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_ProcDlgMain proc uses ebx edi esi hWnd,wMsg,wParam,lParam
mov eax,wMsg
.if eax == WM_TIMER
call _GetMemInfo
.elseif eax == WM_CLOSE
invoke KillTimer,hWnd,1
invoke EndDialog,hWnd,NULL
;********************************************************************
.elseif eax == WM_INITDIALOG
push hWnd
pop hWinMain
invoke LoadIcon,hInstance,ICO_MAIN
invoke SendMessage,hWnd,WM_SETICON,ICON_BIG,eax
invoke SetTimer,hWnd,1,1000,NULL
call _GetMemInfo
;********************************************************************
.else
mov eax,FALSE
ret
.endif
mov eax,TRUE
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -