?? 010.txt
字號:
10.1 內(nèi) 存 管 理
10.1.1 內(nèi)存管理基礎(chǔ)
Win32中的內(nèi)存管理是分層次的,系統(tǒng)提供了幾組層次不同的函數(shù)來管理內(nèi)存,它們是標準內(nèi)存管理函數(shù)、堆管理函數(shù)、虛擬內(nèi)存管理函數(shù)和內(nèi)存映射文件函數(shù)。所有的這些函數(shù)都是為了讓用戶能在比較高的層次上方便地管理內(nèi)存,以便將程序和底層的內(nèi)存分頁機制隔離開來。如圖10.1所示,這幾組函數(shù)的層次是各不相同的。
圖10.1 Windows的內(nèi)存分層管理
Windows使用一個以頁為基礎(chǔ)的虛擬內(nèi)存系統(tǒng),與分頁有關(guān)的概念已經(jīng)在第1章的1.3.2小節(jié)中有所介紹。Windows充分利用了80x86處理器保護模式下的線性尋址機制和分頁機制,這些機制是Win32內(nèi)存管理的基礎(chǔ),Win32提供了一組虛擬內(nèi)存管理函數(shù)來管理虛擬內(nèi)存,主要用于保留/提交/釋放虛擬內(nèi)存,在虛擬內(nèi)存頁上改變保護方式,鎖定虛擬內(nèi)存頁以及查詢一個進程的虛擬內(nèi)存等操作,這是一組位于底層的函數(shù)。
堆管理函數(shù)相對比較高級一點,堆的主要功能就是有效地管理內(nèi)存和進程的地址空間。DOS操作系統(tǒng)下的C語言中就已經(jīng)有了“堆”的概念,這時的“堆”是程序初始化時向操作系統(tǒng)申請并預留的大塊內(nèi)存,程序通過C函數(shù)在這塊空間中申請和釋放內(nèi)存。
在Win32中,進程可以使用的整個地址空間就是一個堆。并且“堆”的概念又被引伸了一步:Win32中分兩種堆,一種是進程的“默認堆”,默認堆只有一個,指的就是可以使用的整個地址空間;另一種是“動態(tài)堆”,也稱為“私有堆”,私有堆類似于DOS下C語言中使用的那種堆,一個進程可以隨意建立多個私有堆,也可以隨意將它們釋放,私有堆全部位于默認堆中,從概念上看,它和默認堆并沒有什么不同,就像一個跨國公司和屬下的子公司同樣都是按照公司的規(guī)程操作一樣。使用堆管理函數(shù)可以對所有的私有堆和默認堆進行操作。
標準內(nèi)存管理函數(shù)總是在默認堆中分配和釋放內(nèi)存,這組函數(shù)就是常規(guī)意義上的內(nèi)存管理函數(shù)。
內(nèi)存映射文件函數(shù)相對比較獨立,它是為了文件操作的方便性而設立的,當對文件進行操作的時候,一般總是先打開文件,然后申請一塊內(nèi)存用做緩沖區(qū),再將文件數(shù)據(jù)循環(huán)讀入并處理,當文件長度大于緩沖區(qū)長度的時候需要多次讀入,每次讀入后處理緩沖區(qū)邊界位置的數(shù)據(jù)往往是個麻煩的問題。曾經(jīng)介紹過Windows可以使用磁盤文件當做虛擬內(nèi)存(參考圖1.5,虛擬內(nèi)存的實現(xiàn)),內(nèi)存映射文件函數(shù)使用同樣的辦法將一個文件直接映射到進程的地址空間中,這樣可以通過內(nèi)存指針用讀寫內(nèi)存的辦法直接存取文件內(nèi)容。
對比這些函數(shù),可以發(fā)現(xiàn)它們涉及的系統(tǒng)資源是各不相同的,如表10.1所示。
表10.1 不同內(nèi)存管理函數(shù)的操作對象
內(nèi)存管理函數(shù)
涉 及 方 面
標準內(nèi)存管理函數(shù)
一個進程的默認堆
堆管理函數(shù)
一個進程的虛擬地址空間、系統(tǒng)內(nèi)存、進程堆資源結(jié)構(gòu)
虛擬內(nèi)存管理函數(shù)
一個進程的虛擬地址空間、系統(tǒng)頁文件、系統(tǒng)內(nèi)存、硬盤空間
內(nèi)存映射文件函數(shù)
一個進程的虛擬地址空間、系統(tǒng)頁文件、系統(tǒng)內(nèi)存、硬盤空間、標準文件I/O
10.1.2 內(nèi)存的當前狀態(tài)
在第1章中已經(jīng)介紹過,一個進程可以尋址的地址空間是4 GB,但用戶可以直接管理的地址空間是多大呢?實際上,高端的2 GB是供操作系統(tǒng)內(nèi)核使用的,其中安排了操作系統(tǒng)的代碼和數(shù)據(jù)(Windows 9x中還包括共享內(nèi)存映射的地址空間),可供應用程序使用的地址空間是低端的2 GB,這2 GB除去應用程序與用戶DLL等的代碼和靜態(tài)數(shù)據(jù)段以后,余下來的才是內(nèi)存管理函數(shù)可以使用的地址空間,應用程序和用戶DLL的大小一般只有幾兆字節(jié)到上百兆字節(jié),所以可以認為能自由使用的地址空間基本上是2 GB。
既然用戶可以使用的地址空間大概為2 GB,讀者千萬不要認為就可以申請2 GB的內(nèi)存了,因為這2 GB僅是可以使用的“地址”空間,而不是可以使用的“內(nèi)存”空間,可分配內(nèi)存的大小還受制于物理內(nèi)存和磁盤交換文件的大小。因為物理內(nèi)存和磁盤交換文件是供整個系統(tǒng)和所有用戶程序使用的,所有系統(tǒng)內(nèi)核、當前執(zhí)行的所有用戶程序的代碼、數(shù)據(jù)以及分配的內(nèi)存總量并不能超過物理內(nèi)存和磁盤交換文件的總和。
當設計一個可能需要申請大量內(nèi)存的程序時,如何預先得知系統(tǒng)的配置情況呢?對此可以使用GlobalMemoryStatus函數(shù):
invoke GlobalMemoryStatus,lpBuffer
lpBuffer指向一個MEMORYSTATUS結(jié)構(gòu),結(jié)構(gòu)的定義如下:
MEMORYSTATUS STRUCT
dwLength DWORD ? ;本結(jié)構(gòu)的長度
dwMemoryLoad DWORD ? ;已用內(nèi)存的百分比
dwTotalPhys DWORD ? ;物理內(nèi)存總量
dwAvailPhys DWORD ? ;可用物理內(nèi)存
dwTotalPageFile DWORD ? ;交換文件總的大小
dwAvailPageFile DWORD ? ;交換文件中空閑部分大小
dwTotalVirtual DWORD ? ;用戶可用的地址空間
dwAvailVirtual DWORD ? ;當前空閑的地址空間
MEMORYSTATUS ENDS
在調(diào)用之前需要首先將dwLength字段設置為MEMORYSTATUS結(jié)構(gòu)的長度,當調(diào)用GlobalMemoryStatus函數(shù)后,函數(shù)會在結(jié)構(gòu)中返回對應的數(shù)值。注意:dwTotalPageFile字段返回的是交換文件的最大值,并不是當前實際建立的交換文件的大小,一般當前的交換文件大小會小于這個數(shù)值,但這個數(shù)值的大小也不是確定的,如果需要的話,系統(tǒng)會增加它的大小直到不再有空余的磁盤空間放置交換文件為止。
在所附光盤的Chapter10\MemInfo目錄中的MemInfo.asm文件利用這個功能定時獲取并顯示當前內(nèi)存的使用信息,源代碼如下:
.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
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 數(shù)據(jù)段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data?
hInstance dd ?
hWinMain dd ?
.const
szInfo db ~物理內(nèi)存總數(shù) %lu 字節(jié)~,0dh,0ah
db ~空閑物理內(nèi)存 %lu 字節(jié)~,0dh,0ah
db ~虛擬內(nèi)存總數(shù) %lu 字節(jié)~,0dh,0ah
db ~空閑虛擬內(nèi)存 %lu 字節(jié)~,0dh,0ah
db ~已用內(nèi)存比例 %d%%~,0dh,0ah
db ~————————————————~,0dh,0ah
db ~用戶地址空間總數(shù) %lu 字節(jié)~,0dh,0ah
db ~用戶可用地址空間 %lu 字節(jié)~,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 + -