原文地址:Inside SQL Server 2000's Memory Management Facilities
翻譯:RicCC
Ken Henderson
Microsoft Corporation
January 2004
本專欄摘選自Ken Henderson的《The Guru's Guide to SQL Server Architecture and Internals》(Addison-Wesley, 2003)。使用許可。著作權。
摘要:Ken Henderson從開發人員的角度探討了SQL Server記憶體管理內幕。
介紹
在這篇專欄裡,我們將從開發人員的角度來探討SQL Server記憶體管理內幕。就是說,我們將討論SQL Server使用API和作業系統功能管理記憶體的方式及其工作原理。通過這種方式探討一個產品,將有助於我們理解產品開發人員的思路,以及他們設計的使用方法。理解一個產品的工作原理和它的設計用途,是掌握這個產品的關鍵。
我們將從基礎的Windows記憶體管理基本原理介紹開始。和所有32位Windows應用程式一樣,SQL Server使用Windows記憶體管理功能分配、釋放、管理記憶體資源,它調用Win32記憶體管理API函數,與作業系統提供的記憶體資源進行互動。
由於SQL Server中幾乎所有的記憶體配置都使用虛擬記憶體(不是記憶體堆),因此絕大部分記憶體配置代碼最終都是通過調用Win32的VirtualAlloc或者是 VirtualFree函數完成。SQL Server調用VirtualAlloc預留、提交虛擬記憶體,調用VirtualFree釋放虛擬記憶體。
虛擬記憶體與實體記憶體
在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將進程的虛擬位址空間分成使用者模式(應用程式)和核心模式(作業系統)兩個部分
每個應用程式擁有自己的虛擬記憶體地址空間,但作業系統和裝置驅動程式共用同一個私人地址空間。每一個虛擬記憶體頁都和特定的處理器模式相關聯,為了存取某個虛擬記憶體頁,處理器必須工作在要求的模式下。這意味著應用程式不能直接存取核心模式的虛擬記憶體,系統必須切換到核心模式才能存取核心模式的記憶體空間。
應用程式記憶體調整
3GB啟動選項(Windows 2000的Advanced Server和DataCenter及後續Windows版本中可用)允許改變這兩個地址空間部分的預設大小。它允許將進程的使用者模式地址空間從2GB擴充到3GB,相應的代價是核心模式的地址空間從2GB減小到1GB。用Windows的說法,這個功能叫做應用程式記憶體調整,或者叫4GB調整(4GT)。你可以通過在BOOT.INI檔案的[Operating Systems]部分添加/3GB開關啟用應用應用程式記憶體調整。通常情況下,人們通過設定BOOT.INI檔案的[Operating Systems]部分,將系統配置為可以使用3GB或者不使用3GB啟動,以使在系統啟動時可以進行選擇。
警告:你也可以在Windows 2000 Professional和Windows 2000 Server上使用/3GB開關,這樣做的負面結果是,將核心模式的空間減小到了1GB,但並不會增加使用者模式的空間。換句話說,你減小了核心模式的空間但並沒有獲得任何好處。
注意:Windows XP和Windows Server 2003引入了一個新的啟動選項/USERVA,和/3GB一起使用,比單獨使用/3GB能夠更好的控制。你在BOOT.INI中添加/3GB的時候可以同時添加/USERVA,/USERVA比單獨使用/3GB的優點是它允許你指定一個準確的地址空間大小值供使用者模式存取。例如,/USERVA=2560為使用者模式配置2.5G的空間,剩餘的1.5G用於核心模式。上面的警告資訊在使用/USERVA選項時同樣適用。
聲明了大地址存取的可執行程式
在/3GB支援加入Windows之前,應用程式無法使用指標的最高位,使用者模式的應用程式只能夠對32位指標的前31位表示的地址空間進行存取。對於剩下的1位,一些聰明的開發人員不希望浪費進程空間裡的這1個位,把它用於了其它的目的,例如用於標識那些應用程式特定的地址配置類型的指標。這在引入/3GB後帶來一個難題,因為這種類型的應用程式無法區分引用2GB以上記憶體的指標,和那些引用2GB以下記憶體但是最高位用於其它目的的指標。基本上,使用/3GB啟動機器,會使這樣的應用程式崩潰。為瞭解決這個問題,微軟在Win32 PE檔案格式(定義Windows下可執行檔Exe和Dll結構的格式)的Characteristics欄位加入一個新標識位的支援,用於指示應用程式是否支援大的定址能力。設定可執行檔頭中Characteristics欄位的第32位啟用IMAGE_FILE_LARGE_ADDRESS_AWARE標識位。通過設定應用程式頭的這個標識位,表明應用程式能夠處理那些最高位被設定的指標,不會由於這個位帶來任何多意性。當設定了這個標識位,在正確的Windows版本上使用/3GB選項啟動,系統將為進程提供一個私人的擴充使用者模式地址空間。你可以使用DumpBin、ImageCfg等可以分析可執行檔頭的工具,查看應用程式是否啟用了這個標識位。Visual C++通過/LARGEADDRESSAWARE串連開關提供對IMAGE_FILE_LARGE_ADDRESS_AWARE的支援。SQL Server啟用了這個標識位,因此當你在正確的Windows版本上使用/3GB開關啟動,系統將擴充SQL Server的使用者模式地址空間。
注意:Windows在進程啟動時檢查可執行檔的IMAGE_FILE_LARGE_ADDRESS_AWARE標識,忽略Dll的標識。對那些最高位被設定的指標,dll代碼必須能夠正確處理。
實體位址延伸
從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模式。
地址視窗擴充
Widnows中的地址視窗擴充(Address Windowing Extensiongs)功能允許應用程式存取超過4GB的實體記憶體。32位的指標是一個整型,只能夠儲存小於等於0xFFFFFFFF的值,因此只能夠引用一個4GB的線性記憶體位址空間。AWE使應用程式可以突破這個限制,存取所有作業系統支援的記憶體。
在概念上,AWE並不是一個新的東西,實際上,從電腦誕生開始,作業系統和應用程式就圍繞指標限制開始使用類似的機制來處理。例如回到DOS時代,32 位擴充(象Phar Lap、Plinks及其它的一些)就普遍運用於16位應用程式,以存取正常地址空間之外的記憶體。用於擴充記憶體的特殊管理器、API非常普遍。也許你還記得象Quarterdeck QEMM-386這樣的產品,在那個時代普遍的用於這類用途中。在這些允許指標存取超過本身表達範圍的記憶體的機制中,具有代表性的方式,是在指標可直接存取的地址空間中提供一個視窗或者是地區,用於和指標無法直接存取的記憶體地區的轉換。這正是AWE的工作原理:在進程地址空間中提供一個地區,或者說一個視窗,用於使用者模式的代碼無法直接存取的記憶體地區進行記憶體存取交換的中轉站。
為了使用AWE,應用程式必須:
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與AWE
在Windows的記憶體管理功能中,應用程式調整(/3GB)可以給私人進程增加50%的地址空間,使用方便,因此成為一種常用方法,但AWE功能更具有彈性和擴充性。前面提到,當你為私人進程地址空間增加1GB,這1GB來自核心模式的地址空間,核心模式地址空間也由2GB被壓縮到1GB。對於核心模式的代碼,完整2GB的工作空間已經顯得狹窄,壓縮這部分空間意味著某些內部核心結構也必須要壓縮。這些結構中主要有機器上用於管理記憶體的表視窗(table Windows)。當你將核心模式部分壓縮到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更合適。
記憶體地區
SQL Server將分配的記憶體組織成兩個獨立的地區:BPool和MemToLeave。實際上如果你使用AWE模式,還有另外一個地區:在Windows AWE支援下可以存取的3GB以上的擴充實體記憶體地區。
BPool在這三個地區中是比較突出的一個,它是SQL Server主要的分配池,主要用於資料和索引頁的緩衝,也用於小於8K的記憶體配置。MemToLeave包含使用者模式地址空間中BPool沒有使用的那部分虛擬記憶體空間。3GB之上的AWE記憶體作為BPool的擴充,為資料和索引頁緩衝提供額外的空間。
當你啟動SQL Server的時候,SQL Server基於機器的實體記憶體和使用者模式地址空間的大小計算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的預留值。我曾經看到人們因為這個數字經常很高而驚慌,畢竟,它通常是機器總的實體記憶體或者是最大 使用者模式地址空間,減去MemToLeave地區的大小。這沒什麼擔心的,因為它僅僅是預留但沒有被分配的空間。之前提到過,預留的空間僅僅是一個地址空間,直到被分配時才會真正的和實體儲存體單元關聯。在這之後,被分配到BPool中的記憶體將會增加,直到達到SQL Server啟動時計算出的BPool上限值。
監控SQL Server虛擬記憶體使用
你可以通過SQL Server:Buffer Manager\Target Pages Perfmon計數器跟蹤計算BPool最大值。SQL Server不同部分需要記憶體時,BPool從啟動時被預留的地區中分配8KB大小的頁,直到達到上限值,你可以通過SQL Server:Buffer Manager\Total 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中獲得足夠的空閑空間,或者其它背景工作處理序釋放足夠的資源之前,串連可能會逾時。
分配
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地區。
記憶體管理器
伺服器運行時,記憶體管理器進行檢查,以確保為伺服器預留了一定數量的可用實體記憶體,使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 Manager\Free 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是達拉斯小牛隊的球迷,業餘時間喜歡看著他的孩子們玩鬧,喜歡體育運動、園藝。