如何在32位程序中突破地址空间限制使用超过4G的内存
眾所周知,所有的32位應用程序都有4GB的進程地址空間,因為32位地址最多可以映射4GB的內存(對于虛擬地址空間概念不太熟悉的朋友建議去看一下《Windows核心編程》這本書)。對于Microsoft Windows操作系統,應用程序可以訪問2GB的進程地址空間(32位Linux可以訪問3GB地址空間),這就是稱為用戶模式的虛擬地址空間。這2GB的用戶模式虛擬地址空間位于4GB地址空間的低一半,而與之相對應的高一半2GB地址空間由操作系統內核使用,因此被成為內核模式的虛擬地址空間。在一個進程中,所有的線程讀共享相同的2GB用戶模式虛擬地址空間。3 u/ C, p7 ]% w! [& t" D, b* O
? ? 對于一般的應用程序來說,2GB的地址空間是足夠使用的了,但是對于一些特殊的需要使用海量內存的應用程序(典型的例子是數據庫系統)來說,2GB的地址空間就遠遠不夠了。為了緩解地址空間的不足,微軟提供了一個權宜的解決方案,所有從Windows 2000 Server開始的操作系統版本都提供了一個boot.ini啟動開關(/3GB),可以為應用程序提供訪問3GB的進程地址空間的能力,從而將內核模式的地址空間限定為1GB。以下就是一個開啟了3GB選項的boot.ini文件示例:) e* M- e( W; v, t8 K$ Z4 L1 s6 e
[boot loader]
timeout=30; N/ b??]0 s6 j/ U$ e" J9 m+ a7 b4
default=multi(0)disk(0)rdisk(0)partition(1)WINDOWS
[operating systems]
multi(0)disk(0)rdisk(0)partition(1)WINDOWS="Windows Server 2003, Enterprise" /fastdetect??/3GB
雖然使用/3GB選項能夠將用戶模式的地址空間擴大50%(從2GB增加到3GB),但是對于數據庫系統這樣的應用程序來說,這1GB的地址空間的增加只能是杯水車薪,并不能解決多少問題,而且由于操作系統內核只能使用1GB地址空間,這樣可能會給操作系統的運行帶來一定的負面影響,因此除非沒有更好的解決方案,是不建議使用/3GB方式的。% l/ O2 {& s6 j1 C
" Z; l$ Z/ ]??C
鑒于像數據庫系統這樣的應用程序對海量內存的需求,Intel公司也覺得4GB的內存不夠用,因此就將CPU芯片中內存地址線由32根擴展到了36根(即最多64GB),這就是所謂的物理地址擴展(PAE:Physical Address Extension)。PAE使得操作系統或應用程序能夠最多使用64GB的物理內存,對于Windows系統(2000以上)來說,只需在boot.ini文件中使用/PAE選項即可(類似于上面的/3GB選項)。需要提醒大家的是,如果沒有在boot.ini文件中使用/PAE選項,那么即使計算機已經配置了超過4GB的物理內存,在Windows操作系統中也不能使用超過4GB的那些內存(事實上,根據我的經驗,如果沒有使用/PAE選項,Windows系統最多只能識別3.25GB的物理內存,我也不清楚為什么不是4GB?如果有知道的,請告訴我一聲)。( W% X3 E1 }: ?$ G+ C- f
? ? 雖然PAE使得在應用程序中使用超過4GB的物理內存成為可能,但是由于32位應用程序的虛擬地址空間并不隨著物理內存的增大而有任何變化,這意味著你不可能使用類似VirtualAlloc( GetCurrentProcess,2GB,...,...)這樣的函數=調直接分配接近用戶模式地址空間大小的內存區域。為了突破32位地址空間的限制,需要使用一種被成為地址窗口擴展(AWE:Address Windowing Extensions)的機制(參見上圖)。& p2 {" y$ c. N. R' G4 S9 P* a
? ? AWE是Windows的內存管理功能的一組擴展,它使應用程序能夠使用的內存量超過通過標準32位尋址可使用的2~3GB內存。AWE允許應用程序獲取物理內存,然后將非分頁內存的視圖動態映射到32位地址空間。雖然32位地址空間限制為4GB,但是非分頁內存卻可以遠遠大于4GB。這使需要大量內存的應用程序(如大型數據庫系統)能使用的內存量遠遠大于32位地址空間所支持的內存量。% Z% o. B1 K3 j
? ? 在使用AWE機制時,需要注意以下幾點:: f9 N5 r! P8 m* }5 l
? ? (1)AWE允許在32位體系結構上分配超過4GB的物理內存,只有當系統可用物理內存大于用戶模式的虛擬地址空間時,才應該使用AWE。9 }/ f7 u9 `, m- P
? ? (2)若要使32位操作系統支持4GB以上的物理內存,必須在Boot.ini文件啟用/PAE選項。
??? (3)若在Boot.ini文件中啟用了/3GB選項,則操作系統最多能夠使用16GB的物理內存,因此如果實際的物理內存超過16GB,必須確保不使用/3GB選項。
??? (4)使用AWE分配的內存是非分頁的物理內存,這意味著這部分內存只能由分配的應用程序獨占使用,不能由操作系統或其他程序使用,直到這些內存被釋放為止,這與通常的VirtualAlloc函數分配的虛擬內存存在顯著的不同,它不會參與分頁替換。3 l9 P* y5 S: I
?
在Windows中,跟AWE相關的API函數有以下幾個:# m) M# N. d. N( {5 W5 C9 ?5 M% ?2 C
/ a( `$ |$ u5 C??v7 r& h5 b
?
[cpp]?view plaincopy
各個函數的具體參數含義可以參考MSDN,其中AllocateUserPhysicalPagesNuma是Windows Vista和Windows 2008 Server新增的函數,用于支持NUMA(非一致性內存訪問)。以下就簡單說一下如何使用這幾個API函數來達到使用超過4GB的內存。8 n/ E??r8 H! K+ }0 @5 R
? ? 使用AllocateUserPhysicalPages函數分配需要的物理內存,使用方式如下:
ULONG_PTR NumberOfPages = xxx; // 需要分配的內存頁數
ULONG_PTR *aPFNs? ?? ?? ?? ?? ?= new ULONG_PTR[NumberOfPages];
BOOL bResult? ?? ?? ?? ?? ?? ?? ?? ?? ? = AllocateUserPhysicalPages( GetCurrentProcess(),&NumberOfPages,aPFNs);& \0 h$ H6 f3 R. Q* t
?檢查分配內存是否成功
if(!bResult)- |: j& k. G% x& R( }
{$ D6 k. a5 I; s. s4 g) `
? ?// 分配識別,錯誤處理
? ?// .....8 ~5 B" {0 d' ~/ \1 F
}6 T$ X1 u% E5 P
檢查實際分配的內存頁數$ \??U: S% w$ u' L- Z9 J
if( NumberOfPages != xxx ): H4 z# e( R" N1 Z/ a4 N. |
{
? ?// ....8 P' a- H( n??o6 L* c
}
需要注意的是,調用上述代碼的用戶必須具有“Lock Pages in Memory”(內存中鎖定頁面)的權限。此權限使得用戶可以使用進程將數據保持在物理內存中,這樣可防止系統將數據分頁到磁盤上的虛擬內存中。行使此權限會因降低可用隨機存取內存(RAM)的數量而顯著影響系統性能。需要在本地安全策略管理程序中給用戶賦予該權限,如下圖所示:
.? ?0 E! @1 Y2 W; A/ X( u6 m, P+ v
? ? 給用戶分配了上述權限之后,需要在程序中使用代碼啟用該權限,如下所示:4 |( \7 k( P/ V" p1 ^; `8 `4 z
7 @4 [1 g( X+ h8 x8 O) e+ M
// 設置鎖住物理內存的權限,此代碼在調用AllocateUserPhysicalPages之前執行2 H% e9 U- p: ]; k9 P5 i
if( !AWESetLockPagesPrivilege( GetCurrentProcess(), TRUE) ). }7 ~: \4 s( ?' ^
{4 J: B0 W??@7 H& P3 {! S; X! m: B
? ? // 輸出錯誤信息
}
E" l/ A1 J
??? ///? ?? ???設置或清除啟用AWE( Address Windowing Extensions )所需要的鎖住內存的權限。
: E! |9 U. B
??? ///? ?? ???進程句柄。??y: S& R& q( q" w
?? ?///?7 D% m6 c$ K??H6 x! g8 x
? ? ///?
??? ///? ?? ???設置或者清除標志。
??? ///?( s* [- [! P5 ^8 G2 `0 D% u
??? ///?
??? ///? ?? ???如果成功,則返回TRUE,否則返回失敗。/ P) G) ^: `0 E0 i) R# s1 _+ O
? ? ///?5 r5 W: D8 E- g
BOOL AWESetLockPagesPrivilege( HANDLE hProcess, BOOL Enable )
{9 K, R' J( M+ \0 a
? ? HANDLE? ?? ?? ?? ?? ? Token? ? = NULL;# M* l, K0 g: X1 w2 E+ N+ j' _' e" m
? ? BOOL? ?? ?? ?? ?? ? Result? ? = FALSE;8 {& H1 n* O( H# V" U. d
? ? TOKEN_PRIVILEGES? ? Info? ? = { 0 };' v7 W7 ?8 g) `( Q" ?! o1 }
6 r4 S5 D3 ^1 A( E
? ? // 打開令牌( h% ~% I7 W. G??h0 I
? ? Result = OpenProcessToken ( hProcess, TOKEN_ADJUST_PRIVILEGES, &Token );
?? ?if( !Result )??H& G2 B* y, N8 b9 P5 n$ D. A
? ?? ???return FALSE;
??? // 設置權限信息
?? ?Info.PrivilegeCount = 1;( B. M??N( Q6 _, c9 X; Z
? ? Info.Privileges[0].Attributes = Enable? SE_PRIVILEGE_ENABLED : 0;1 ?' B- d: ~# M
?? ?// 獲得鎖定內存權限的ID$ w5 F; n- B) n/ c1 W' n??_9 r
? ? Result = LookupPrivilegeValue ( NULL,SE_LOCK_MEMORY_NAME,&(Info.Privileges[0].Luid));
??? if( !Result )?
??? {
?????? CloseHandle( Token );/ C5 ?; {4 F' p
? ?? ???return FALSE;5 j8 ^% \% s& S( _, o8 C5 C
? ? }2 C& E. I5 _2 T/ L/ [
1 m# g' f- P$ h" C- d- X# c
? ? // 調整權限* A# o2 N* Q) K6 N1 ^) M9 e
? ? Result = AdjustTokenPrivileges ( Token, FALSE,(PTOKEN_PRIVILEGES) &Info,0, NULL, NULL);" ^! C: Y) l) T9 `& m2 o, W. U5 J
? ? if( ( !Result )??|| (??GetLastError() != ERROR_SUCCESS ) ); }# G* G. J. g5 V5 [+ D' Q8 }
? ? {3 d8 F" p6 l+ Q: L
? ?? ???CloseHandle( Token );
??????? return FALSE;. x3 m( ]/ h# A% t??T
? ? }
??? // 成功返回
?? CloseHandle( Token );. R0 O( Y0 @, w3 y
? ? return TRUE;& M+ K. J) Y4 X6 T% ]
}
使用AllocateUserPhysicalPages分配了物理內存之后,下一步就是使用MapUserPhysicalPages或MapUserPhysicalPagesScatter函數將物理內存映射進用戶模式地址空間內,這兩個函數用法差不多,只是第一個參數有差別。由于分配的物理內存的大小超過了用戶模式地址空間的大小,因此顯然不可能一次將所有的物理內存都映射到地址空間中。通常的做法是在用戶模式地址空間內分配一小塊連續的區域(即地址窗口),然后根據使用的需要動態將部分的物理內存映射到地址空間,這也就是“地址窗口擴展”一詞的真實含義。代碼示例如下:1 [) \. {??r7 B8 f3 o7 t
( [1 L& e+ e( e$ S1 I
// 定義16M的地址窗口: ]: A. a9 i, Y2 v" ]( e: j
#define MEMORY_REQUESTED (16*1024*1024)/ P, [- D! ^: Z* G$ H
, e$ U5 L/ e$ |
// 分配地址窗口# o$ n) o# S5 @4 S* z" p9 [
PVOID lpMemReserved = VirtualAlloc( NULL,MEMORY_REQUESTED, MEM_RESERVE | MEM_PHYSICAL,PAGE_READWRITE );9 l/ a) [- B9 H( ]??v7 h3 R. j
// 將物理內存映射到地址空間(根據需要,每次映射的頁面會不同,
// 即下面函數的第三個參數aPFNs會指向不同的物理頁)
= MapUserPhysicalPages( lpMemReserved,NumberOfPages,aPFNs);
// 以下就像普通的內存一樣使用lpMemReserved 指針來操作物理內存了
使用完了之后,可以使用FreeUserPhysicalPages來釋放分配的物理內存,示例如下:( V, Q4 C9 s8 L, o8 E2 U
4 y" p3 @: C; Z4 o* ?
// 取消內存映射?6 O0 u" U& ]- G, p
bResult = MapUserPhysicalPages( lpMemReserved,NumberOfPages,NULL );; B* a??q" H1 ^/ j" ]; ^0 J
??j) M! w4 m7 F, r
// 釋放物理內存: t$ A; V0 p9 b3 A: D! ]3 t
bResult = FreeUserPhysicalPages( GetCurrentProcess(),&NumberOfPages,aPFNs );
// 釋放地址窗口
bResult = VirtualFree( lpMemReserved,0,MEM_RELEASE );
// 釋放物理頁號數組/ w+ M& y5 M2 t, ]
delete[] aPFNs;
AWE機制被使用最多的一個場合是數據庫系統的緩存管理器(BufferManager),例如SQL Server的內存管理器。雖然以上代碼都是基于Windows操作系統,但是PAE和AWE機制并不是Windows特有的,32位linux也有類似的API。完整使用AWE機制的例子,大家可以參考MySQL的源碼。1 w??K* v- K% E6 w
? ? 最后想說的是,對于開發人員來說,一個好消息是64位CPU和操作系統正越來越普及。在64位環境下,一個進程的用戶模式的地址空間可達8TB(也就是說目前很多的64位系統只使用了40幾位的內存地址,遠沒有充分使用64位的內存地址),在可以預見的未來很長一段時間,估計我們都不會再為地址空間不足而發愁了,讓我們一起為64位時代的到來而歡呼吧!
總結
以上是生活随笔為你收集整理的如何在32位程序中突破地址空间限制使用超过4G的内存的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: QML中MouseArea元素的介绍
- 下一篇: PyQt5学习笔记13----pyqt线