本專欄摘選自《The Guru's Guide to SQL Server Architecture and Internals》
Introduction
在這篇專欄里,我們將從開發者的角度來探討SQL Server內存管理內幕。就是說,我們將討論SQL Server使用API和操作系統功能管理內存的方式及其工作原理。通過這種方式探討一個產品,將有助于我們理解產品開發者的思路,以及他們所設計的使用方法。理解一個產品的工作原理和它的設計用途,是掌握這個產品的關鍵。
我們將從一些基礎的Windows內存管理基本原理介紹開始。和所有的32位Windows應用程序一樣,SQL Server使用Windows內存管理功能分配、釋放、管理內存資源。正如所有其它的Windows應用程序,SQL Server調用Win32內存管理API函數,與操作系統提供的內存資源進行交互。
由于SQL Server中幾乎所有的內存分配都使用虛擬內存(不是內存堆),因此絕大部分內存分配代碼最終都是通過調用Win32的VirtualAlloc或者是VirtualFree函數完成。SQL Server調用VirtualAlloc保留、提交分配的虛擬內存,調用VirtualFree釋放虛擬內存。
Virtual Memory vs Physical Memory
在x86系列處理器上,Windows為所有進程提供一個4GB虛擬內存工作空間。用"虛擬"這個詞,意思是這個內存并不是通常意義上的內存,它只是一個地址范圍,并沒有和物理存儲單元關聯在一起。當進程請求內存分配時,這些地址空間才被使用,和具體的物理存儲單元關聯起來。然而這些物理存儲單元并不一定是物理內存,它通常可能會是磁盤空間,確切的說,是操作系統的分頁文件(System Paging Files)。這就是為什么多個應用程序可以同時運行在一個128M內存的系統上,每個應用程序都有一個4GB的虛擬內存地址空間--它不是真正的內存,但對應用程序來說可以理解為內存。Windows透明的處理paging files的數據拷貝,以使應用程序能夠使用的內存可以超過機器的實際物理內存,并使應用程序能夠公平的存取機器的物理內存。
這個4GB的地址空間被分成兩部分:user mode部分和kernal mode部分。默認情況下,每個部分的大小為2GB,在Windows NT系列的操作系統上,可以通過BOOT.INI中的開關來改變這個默認設置(Windows NT, Windows 2000, Windows XP和Windows Server 2003屬于Windows NT系列,Windows 9x和Windows ME不屬于)。
圖1:Windows將進程的虛擬地址空間分成user mode(應用程序)和kernal mode(操作系統)兩個部分
每個應用程序擁有自己的虛擬內存地址空間,但操作系統和設備驅動程序共享同一個私有地址空間。每一個虛擬內存頁(memory page)都和特定的處理器模式(processor mode)相關聯,為了存取某個虛擬內存頁,處理器必須工作在要求的模式下。這意味著應用程序不能直接存取kernal mode的虛擬內存,系統必須切換到kernal mode才能存取kernal mode的內存空間。
Application Memory Tuning
3GB啟動選項(Windows 2000的Advanced Server和DataCenter及后續Windows版本中可用)允許改變這兩個地址空間部分的默認大小。它允許將進程的user mode地址空間從2GB擴展到3GB,相應的代價是kernal mode的地址空間從2GB減小到1GB。用Windows的說法,這個功能叫做Application Memory Tuning或者是4GB Tuning(4GT)。你可以通過在BOOT.INI文件的[Operating Systems]部分添加/3GB開關啟用應用Application Memory Tuning。通常情況下,人們通過設置BOOT.INI文件的[Operating Systems]部分,將系統配置為可以使用3GB或者不使用3GB啟動,以使在系統啟動時可以進行選擇。
警告:你也可以在Windows 2000 Professional和Windows 2000 Server上使用/3GB開關,這樣做的負面結果是,將kernal mode的空間減小到了1GB,但并不會增加user mode的空間。換句話說,你減小了kernal mode的空間但并沒有獲得任何好處。
注意:Windows XP和Windows Server 2003引入了一個新的啟動選項/USERVA,和/3GB一起使用,比單獨使用/3GB能夠更好的控制。你在BOOT.INI中添加/3GB的時候可以同時添加/USERVA,/USERVA比單獨使用/3GB的優點是它允許你指定一個準確的地址空間大小值供user mode存取。例如,/USERVA=2560為user mdoe配置2.5G的空間,剩余的1.5G用于kernal mode。上面的警告信息在使用/USERVA選項時同樣適用。
Large-Address-Aware Executables
在/3GB支持加入Windows之前,應用程序無法使用指針的最高位,User mode的應用程序只能夠對32位指針的前31位表示的地址空間進行存取。對于剩下的1位,一些聰明的開發者不希望浪費進程空間里的這1個位,把它用于了其它的目的,例如用于標識那些應用程序特定的地址分配類型的指針。這在引入/3GB后帶來一個難題,因為這種類型的應用程序無法區分引用2GB以上內存的指針,和那些引用2GB以下內存但是最高位由于其它原因而被設置的指針。基本上,使用/3GB啟動機器,會使這樣的應用程序崩潰。為了解決這個問題,微軟在Win32 PE文件格式(定義Windows下可執行文件Exe和Dll結構的格式)的Characteristics字段加入一個新標識位的支持,用于指示應用程序是否支持大的尋址能力。設置可執行文件頭中Characteristics字段的第32位啟用IMAGE_FILE_LARGE_ADDRESS_AWARE標識位。通過設置應用程序頭的這個標識位,表明應用程序能夠處理那些最高位被設置的指針,不會由于這個位帶來任何多意性。當設置了這個標識位,在正確的Windows版本上使用/3GB選項啟動,系統將為進程提供一個私有的擴展user mode地址空間。你可以使用DumpBin、ImageCfg等可以分析可執行文件頭的工具,查看應用程序是否啟用了這個標識位。Visual C++通過/LARGEADDRESSAWARE連接開關提供對IMAGE_FILE_LARGE_ADDRESS_AWARE的支持。SQL Server啟用了這個標識位,因此當你在正確的Windows版本上使用/3GB開關啟動,系統將擴展SQL Server的user mode地址空間。
注意:Windows在進程啟動時檢查IMAGE_FILE_LARGE_ADDRESS_AWARE標識,忽略Dll的標識。對那些最高位被設置的指針,dll代碼必須能夠正確處理。
Physical Address Extension
從Pentium Pro開始,Intel處理器提供一種叫做Physical Address Extension(PAE)的內存映射模式。PAE支持高達64GB的物理內存存取。PAE模式下,內存管理單元(Memory Management Unit - MMU)仍然實現了頁目錄條目(Page Directory Entries - PDEs)和頁表條目(Page Table Entries - PTEs),但是在這個之上有一個新的層級:頁目錄指針表(Page Directory Pointer Table)。PAE模式下系統能夠尋址更大的內存,因為PDEs和PTEs為64位寬,是之前標準寬度的兩倍,而并不是通過PAE模式下的頁目錄指針表實現。頁目錄指針表把這些高存儲容量的表和索引管理起來。使用PAE模式需要一個特殊版本的Windows內核,在Windows 2000及后續版本中均有提供,單處理器機器上位于Ntkrnlpa.exe中,多處理器機器上位于Ntkrnlpamp中。和/3GB、/USERVA一樣,在BOOT.INI文件中添加/PAE啟用PAE模式。
Address Windowing Extensions
Widnows中的Address Windowing Extensiongs功能允許應用程序存取超過4GB的物理內存。32位的指針是一個整型,只能夠存儲小于等于0xFFFFFFFF的值,因此只能夠引用一個4GB的線性內存地址空間。AWE使應用程序可以突破這個限制,存取所有操作系統支持的內存。
在概念上,AWE并不是一個新的東西,實際上,從計算機誕生開始,操作系統和應用程序就圍繞指針限制開始使用類似的機制來處理。例如回到DOS時代,32位擴展(象Phar Lap、Plinks及其它的一些)就普遍運用于16位應用程序,以存取正常地址空間之外的內存。用于擴展內存特殊用途的管理器、API非常普遍。也許你還記得象Quarterdeck QEMM-386這樣的產品,在那個時代普遍的用于這類用途中。在這些允許指針存取超過本身表達范圍的內存的機制中,具有代表性的方式,是在指針可直接存取的地址空間中提供一個窗口或者是區域,用于和指針無法直接存取的內存區域的轉換。這正是AWE的工作原理:在進程地址空間中提供一個區域,或者說一個窗口,用作和user mode的代碼無法直接存取的內存區域進行內存存取交換的中專站。
為了使用AWE,應用程序必須:(譯者注:下面講的"需要存取的物理內存"指那些user mode進程在自己的地址空間中無法直接訪問到的內存)
1. 使用Win32的AllocateUserPhisycalPages API函數分配要存取的物理內存。該函數需要調用者具有將內存頁鎖定的權限。
2. 使用VirtualAlloc API函數在進程的地址空間中創建一個區域,作為與需要存取的物理內存進行映射的一個窗口。
3. 使用MapUserPhysicalPages或者MapUserPhysicalPagesScatter API函數,將需要存取的物理內存映射到這個虛擬內存窗口中。
Windows 2000及后續版本支持AWE,盡管可以在低于2G物理內存的機器上使用AWE,但一般只是在2G或者超過2G內存的機器上使用,因為AWE是32位進程存取超過3GB內存的唯一方法。如果你在低于3GB物理內存的系統上,在SQL Server中啟用AWE支持,系統會忽略這個選項并使用正常的虛擬內存管理方法。AWE內存一個比較有意思的特性是它不會使用磁盤,你將注意到AWE相關的API函數只對物理內存進行存取,這就是說AWE內存就是物理內存,不會與系統分頁文件發生交換。
用于AWE提供的物理內存緩存的虛擬內存窗口,需要具有讀、寫存取權限,因此當你設置這個虛擬窗口時,傳給VirtualAlloc的保護屬性只能是PAGE_READWRITE。這也意味著你無法使用VirtualProtect保護這個區域中的內存頁,來防止被修改或存取。
注意:你常用的一些檢測應用程序內存使用的工具,例如任務管理器、Perfmon/Sysmon等,都無法顯示各個進程AWE內存的使用量。并沒有什么可以指示各個進程AWE內存的使用量,也就沒有什么可以報告給定進程工作區中AWE內存的大小。
/3GB vs AWE
在Windows的內存管理功能中,Application Memory Tuning(/3GB)可以給私有進程增加50%的地址空間,使用方便,因此成為一種常用方法,但AWE功能更具有彈性和擴展性。前面提到,當你為私有進程地址空間增加1GB,這1GB來自kernal mode的地址空間,kernal mode地址空間也由2GB被壓縮到1GB。對于kernal mode代碼,完整2GB的工作空間已經顯得狹窄,壓縮這部分空間意味著某些內部核心結構也必須要壓縮。這些結構中主要有機器上用于管理內存的表窗口(table Windows)。當你將kernal mode部分壓縮到1GB后,這個表最大就只能管理16GB的物理內存了。例如你在一臺具有64GB物理內存的機器上運行Windows 2000 DataCenter,啟動時使用了/3GB選項,你就只能夠存取這臺機器25%的內存,剩余的48BG將無法被操作系統和應用程序使用。AWE允許你訪問超過3GB的內存,而通過/3GB,你僅僅為私有進程空間獲得額外的1GB。Large Address Aware自動透明的使得這個額外空間對應用程序可用,但它被限制在1GB之內。理論上,AWE通過Win32 AWE API函數,使得所有對操作系統可用的物理內存對應用程序可用。盡管AWE更難于使用和存取,但它更具彈性和擴展。
并不是說任何情況下AWE都比/3GB好,只是通常狀況下是這樣。比如說當你需要很多空間以分配內存,而又不能放在AWE內存中(例如象線程棧Thread Stacks、鎖內存Lock Memory、存儲過程計劃Procedure Plans等),你也許會發現/3GB更合適。
Memory Regions
SQL Server將分配的內存組織成兩個獨立的區域:BPool和MemToLeave。實際上如果你使用AWE模式,還有另外一個區域:在Windows AWE支持下可以存取的3GB以上的物理內存。
BPool在這三個區域中是比較突出的一個,它是SQL Server主要的分配池,主要用于數據和索引頁的緩存,也用于小于8K的內存分配。MemToLeave包含user mode地址空間中BPool沒有使用的那部分虛擬內存空間。3GB之上的AWE內存作為BPool的擴展,為數據和索引頁緩存提供額外的空間。
當你啟動SQL Server的時候,SQL Server基于機器的物理內存和user mode地址空間的大小計算BPool的上限。在計算出這個值后,MemToLeave區域被保留,這有利于防止BPool隨后的預留造成內存碎片。接下來,BPool被保留,它可以分成多達32個獨立預留塊,用于滿足在BPool保留時SQL Server進程中那些正在請求虛擬地址空間的dll及其它分配請求。在保留BPool區域之后,MemToLeave區域被釋放。MemToLeave用于SQL Server內部超過8KB的連續空間分配請求,以及象OLEDB Provider、進程內COM對象等外部客戶(指SQL Server主要引擎之外,駐留在SQL Server進程中的那些內存請求者)分配請求。
因此,一旦SQL Server啟動,BPool就被保留,但未被提交,MemToLeave基本就是進程的虛擬內存地址空間中的空閑部分。如果你在SQL Server啟動之后查看SQL Server進程的Virtual Bytes Perfmon計數器,你將發現它反映的是BPool的預留。我曾經看到人們因為這個數字經常很高而驚慌,畢竟,它通常是機器總的物理內存或者是最大user mode地址空間,減去MemToLeave區域大小。這沒什么擔心的,因為它僅僅是保留但沒有提交的空間。之前提到過,保留的空間僅僅是一個地址空間,直到被提交時才會真正的和物理存儲單元關聯。在這些之后,被提交到BPool中的內存將會增加,直到達到SQL Server啟動時計算出的BPool上限值。
Monitoring SQL Server Virtual Memory Use
你可以通過SQL Server:Buffer ManagerTarget Pages Perfmon計數器跟蹤計算出的BPool最大值。SQL Server不同部分需要內存時,BPool提交一開始就被保留的8KB大小的頁直到達到計算的上限值,你可以通過SQL Server:Buffer ManagerTotal Pages Perfmon計數器跟蹤BPool中被提交的虛擬內存的使用狀況。另外你可以通過Private Bytes計數器跟蹤SQL Server進程中所有被提交的虛擬內存的使用狀況。
因為SQL Server中絕大部分虛擬內存的使用都來自BPool,因此通常情況下,這兩個計數器將一前一后的增加或平穩下來(記住,當啟用AWE支持后,Private Bytes計數器不會反映SQL Server全部的內存使用)。如果Total Pages計數器平穩下來,而Private Bytes持續增加,這通常表明MemToLeave區域中連續的內存分配。這種內存分配可能比較常見,例如可能是SQL Server創建額外的工作線程時和線程棧相關的內存分配,或者是進程內COM對象、擴展存儲過程等外部請求者的內存泄漏等。如果由于內存泄漏或者內存使用過大,導致MemToLeave區域耗盡,使SQL Server進程用完了虛擬內存地址空間的內存(或者是MemToLeave區域中的最大空閑塊低于0.5M的默認進程棧大小),就算是并沒有達到使用sp_configure配置的最大工作進程數,SQL Server將無法創建新的工作進程。這種情況下,如果SQL Server需要創建一個新的工作進程來執行一個工作請求,例如處理SQL Server新的連接請求等,那么這個工作請求將被延遲,知道服務器有足夠的資源創建工作進程,或者是其它工作進程被釋放出來。這可能會導致用戶無法連接到服務器,因為在從MemToLeave中獲得足夠的空閑空間或者是其它工作進程被釋放能夠處理當前工作請求之前,連接可能會超時。
Allocations
SQL Server中的內存請求者在初始化內存請求時,先創建一個內存對象管理當前的請求,當內存對象執行請求時,它調用SQL Server中相應的內存管理器從BPool或者是MemToLeave區域獲取內存。請求小于8KB時,通常從BPool中獲取內存;當請求8KB或者更大的連續空間時,通常從MemToLeave區域中獲取。因為一個內存對象可能會產生多個分配請求,因此有可能會從MemToLeave區域中分配小于8KB的分配請求。向SQL Server進程空間請求內存一般情況下都是內部請求者,就是說SQL Server的內部對象需要內存以執行某個任務,當然不是絕對的,象上面提到過的也有可能是外部請求者。通常,這些外部請求者使用Win32內存API函數分配和管理內存,因此是從MemToLeave區域中分配,因為(對于操作系統而言,譯者注)SQL Server進程中只有MemToLeave區域可用(BPool區域被SQL Server保留,譯者注)。但對于擴展存儲過程是個特殊情況,擴展存儲過程調用ODS的srv_alloc API函數實現,這使得它同SQL Server內部請求者被同等的處理,通常srv_alloc請求小于8KB的內存時從BPool中分配,大的內存分配則來自MemToLeave區域。
The Memory Manager
服務器運行時,內存管理器進行檢查,以確保為服務器預留了一定數量的可用物理內存,使Windows和服務器上其它應用程序能夠繼續平穩的運行。這個數量從4M到10M左右(Windows Server 2003上接近10M),基于系統負載和BPool中內存頁生命期得出。如果服務器上可用物理內存開始低于這個極限值,服務器釋放BPool中的部分內存頁,以收縮BPool的內存使用量(假設SQL Server的動態內存配置被啟用)。內存管理器也確保任何時候保留了一定數量的空閑內存頁,以使新的分配請求到達時,不必等待內存分配。這里的空閑,意思是指這些內存頁被提交了,但是未使用。被提交但未被使用的BPool內存頁通過一個空閑列表跟蹤,當列表中的頁被使用時,內存管理器從BPool的預留中分配更多的內存頁,直到整個BPool預留被提交。你將看到Process:Private Bytes Perfmon計數器由于這個行為而逐漸的增長(通常是線性增長)。
系統中對應每一個CPU都有一個單獨的空閑列表,當需要使用空閑頁用于滿足一個分配請求時,先檢查和當前分配請求CPU相關的空閑列表,然后再檢查系統中其它CPU相關的列表。這在多處理器系統上,有利于各個處理器更好的使用本地緩存,提高擴展性。你可以使用SQL Server:Buffer Partition Perfmon計數器監控特定的BPool分區,通過SQL Server:Buffer ManagerFree Pages Perfmon計數器監控所有分區的空閑列表。
整個運行過程中,SQL Server內存管理器進程(可能運行在內存管理器線程或其它服務器線程中)監控系統內存狀態,為系統其它應用程序保留合理數量的空閑物理內存,為新的內存分配請求預留一個安全數量的內存頁。當在服務器上使用AWE時,其中的某些方面必須改變。在使用AWE時,BPool一開始就獲取并鎖定機器的物理內存,鎖定的內存數量根據是否設置了maximum server memory確定。如果設置了,BPool嘗試鎖定由maximum server memory確定的數量;如果沒有設置,BPool只留出大致128M,供其它進程使用,鎖定機器上其余的全部物理內存。然后,BPool使用3GB之上的內存(AWE內存)作為數據和索引的分頁文件(paging files),它將這些區域(3GB之上)的物理內存頁映射到適當的虛擬內存地址空間中,使32位指針能夠引用到。
作為一個丈夫和父親的Ken Henderson,居住在德克薩斯州的達拉斯郊區。他是8本不同技術主題書籍的作者,包括最近發行的《The Guru's Guide to SQL Server Architecture and Internals》。Ken Henderson是達拉斯小牛隊的球迷,業余時間喜歡看著他的孩子們玩鬧,喜歡體育運動、園藝。
原文:http://msdn.microsoft.com/library/e....asp?frame=true
(T114)