SQL Server 2000記憶體管理內幕

kitesky發表於2009-04-16
在這篇專欄裡,我們將從開發者的角度來探討SQL Server記憶體管理內幕。就是說,我們將討論SQL Server使用API和作業系統功能管理記憶體的方式及其工作原理。透過這種方式探討一個產品,將有助於我們理解產品開發者的思路,以及他們所設計的使用方法。理解一個產品的工作原理和它的設計用途,是掌握這個產品的關鍵。
本文來自: 程式設計入門網
[@more@]

我們將從一些基礎的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是達拉斯小牛隊的球迷,業餘時間喜歡看著他的孩子們玩鬧,喜歡體育運動、園藝。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/66009/viewspace-1020647/,如需轉載,請註明出處,否則將追究法律責任。

相關文章