引用:Live and Learn html
以Intel的中央處理器爲例,Linux 32位的系統中,物理內存的基本單位是字節(Byte),1個字節有8個二進制位。每一個內存地址指向一個字節,內存地址加1後獲得下一個字節的地址。這裏用以表示物理內存實際位置的地址,就是一般所說的物理地址(Physical Address)。CPU正在執行的進程代碼、進程數據和棧區數據等,都臨時保存在物理內存中。程序員
線性地址(Linear Address,亦即虛擬地址 Virtual Address)是出於如下考慮算法
(1) 隔離不一樣進程使用的內存地址空間;(4) 擴展內存,即運行所需內層大於物理內存的程序編程
而在物理地址和程序之間增長的中間層。虛擬地址範圍對應CPU的尋址能力,32位的CPU的虛擬地址範圍爲 0x00000000 ~ 0xFFFFFFFF,即最大虛擬內存爲2^32 Bytes = 4GB;相應的64位CPU最大虛擬內存爲 2^64 Bytes,然而實際上目前大部分操做系統和應用程序都不須要這樣大的虛擬地址空間,而且64位長的地址會增長系統的複雜性和地址轉換成本,所以目前的 x86-64架構只使用虛擬地址低位48位(0 ~ 47)做爲虛擬地址,並用第47位的值填充48 ~ 63高位,所以64位CPU的最大虛擬內存爲2^48 = 256TB。通常地,物理地址空間只是虛擬地址空間的一個子集。架構
爲了提升內存管理效率,發揮虛擬空間的做用,可設定CPU的 CR0 寄存器的最高位(PG,分頁標誌位),啓用分頁機制將虛擬空間等分紅若干頁,而後按頁幀管理和使用虛擬空間。物理內存規定的頁大小有4096 Bytes,8192 Bytes,2MB, 4MB等,由於虛擬空間頁中存儲的內容其實是要放到物理內存上的,因此虛擬空間也採用上述大小進行分頁。普通的分頁大小採用4KB的標準。操作系統
現代計算機系統中,通常不須要程序員直接操做物理地址,而是由操做系統按頁幀爲進程分配執行用的虛擬地址。每一個頁幀能夠被映射到任何可用的物理內存 頁。CPU 在執行程序進程時,CPU發出對相應的虛擬地址進行讀或寫操做,硬件設備(MMU,內存管理單元,通常都集成在CPU芯片上)分析虛擬地址後查詢頁表並計 算,將該虛擬地址映射爲物理地址,而後經過北橋芯片(北橋芯片主要功能就是負責CPU和物理內存之間的通訊)鏈接內存總線,從而CPU可以訪問到物理內存 中進程代碼和數據。指針
邏輯地址(Linear Address)指的是程序內部的地址偏移量。該地址以操做系統爲程序分配的程序入口地址爲基準,指定程序中操做數或指令的地址。邏輯地址是程序員直接操縱的地址,例如在 C 語言編程中,定義一個 int 變量,而後使用取地址運算符(&var)獲得的地址就是邏輯地址。htm
邏輯地址由兩部分構成,分別是段選擇符(Segment Selector)和段內偏移量(Offset),段選擇符是一個16-bit (2字節)無符號數,段偏移量則是一個 32-bit 的無符號數。段選擇符的內容以下圖所示。blog
Figure 1 Segment Selector Fields<喎�"http://www.2cto.com/kf/ware/vc/" target="_blank" class="keylink">vcD4KCjxoMT4yLiDE2rTmudzA7daut9a2zrv61sY8L2gxPgoKPHA+ts6jqFNlZ21lbnRhdGlvbqOpysfU2rbOyr3E2rTmudzA7bXEuMXE7s/C0M6zybXEyvXT76Gjts7KvbncwO21xLv5sb7LvM/rysew0bPM0PLE2sjdu/K5/bPMudjPtbfWs8m2zqOswP3I57T6wuu2zqGiyv2+3bbOtcijrLLZ1/fPtc2zsLS2zs6qvfizzLfWxeTQ6cTitdjWt7/VvOSjrNXi0fmyu82svfizzLbOv9W85LK7zayjrMq1z9bBy734s8y49MDrus3E2rTmsaO7pKGjw7+49rbOtrzT0NfUvLq1xMPoyva3+6OoU2VnbWVudCBEZXNjcmlwdG9yLCA4LWJ5dGUgbG9uZ6Opo6zV4tCpw+jK9rf7sbuxo7Tm1NrIq77Ww+jK9rf7se2jqEdsb2JhbCBEZXNjcmlwdG9yIFRhYmxlLCBHRFSjqbvyvtayv8Poyva3+7Hto6hMb2NhbCBEZXNjcmlwdG9yIFRhYmxlLCBMRFSjqdbQoaO2zsPoyva3+7XExNrI3cjnzbwyy/nKvqGjPC9wPgogPGltZyBzcmM9"http://www.2cto.com/uploadfile/Collfiles/20141113/2014111309173162.png" alt="\"> 索引
Figure 2 Segment Descriptor
Base Address一共 32 bits,它指向當前段第一個字節的線性地址。Limit部分一共 20 bits,它指明本段虛擬空間最後一個字節相對第一個字節的偏移量,所以它也能表示段的長度。與頁不一樣(長度固定爲4KB等),段的長度根據程序相應內容 變化。另外,若是標誌位 G設定爲0,那麼偏移量每增長 1,地址值增長 1 byte,那麼這時段的最大長度爲 1 byte * 2^20 = 1MB;若是標誌位 G設定爲1,那麼偏移量加1,地址值增長 4 KB,相應的這時段的最大長度爲 4KB * 2^20 = 4GB。
在進程的執行過程當中,當遇到須要訪問內存的指令時,首先根據邏輯地址獲得相應的線性地址,而後再根據線性地址獲得物理地址。根據邏輯地址獲得線性地址的過程如圖3所示。
Figure 3 Translating a Logical Address
CPU提供CS 寄存器臨時保存正在執行的進程代碼段的段選擇符,DS 寄存器臨時保存進程數據段的段選擇符,以及SS寄存器臨時保存棧區分段的段選擇符。這樣,在轉換邏輯地址的時候,CPU根據當前保存在CS中的段選擇器 (參見圖1),其中 TI 標誌肯定段描述符位於GDT仍是LDT,Index部分肯定段描述符在表(GDT或LDT)中的位置,從而能夠找到邏輯地址對應的段描述符;根據段描述符 中的Base Address 找到段的起始線性地址,使用起始地址加上指令邏輯地址中的偏移量,就能獲得指令所指向的實際線性地址。
因爲分段機制和Intel處理器相關聯,在其它的硬件系統上,可能並不支持分段式內存管理,所以在 Linux 中,操做系統傾向與使用分頁的方式管理內存。在用戶模式(User Mode)下,全部的進程共用用戶代碼段和用戶數據段。用戶模式下,全部進程使用代碼段的段描述符的Base Address部分都指向線性地址0x00000000,同時數據段的段描述符的 Base Address部分也指向線性地址0x00000000;在內核模式(Kernel Mode)下,全部的進程共用內核代碼段和內核數據段。內核全部進程使用代碼段的段描述符的Base Address部分都指向線性地址0x00000000,同時數據段的段描述符的 Base Address部分也指向線性地址0x00000000。上述的段描述符的G位都設定爲1,段對應的虛擬空間從0到2^32,對應整個32位CPU的最大 虛擬空間。
上述辦法解決了其它硬件平臺不支持段式管理的狀況,大大簡化了地址轉換操做,可是因爲理論上每一個進程的可用線性空間範圍都是4G,即進程共用段表, 使用段界限隔離進程內存的目的就不能實現了。所以,在Linux中,爲每一個進程分配獨立的頁表,純粹依靠分頁機制提供內存保護和進程隔離。接下來,針對分 頁機制進行詳細的說明。
分頁機制將整個線性地址空間及整個物理內存當作由許多大小相同的存儲塊組成的,並把這些塊做爲頁(虛擬空間分頁後每一個單位稱爲頁)或頁幀(物理內存 分頁後每一個單位稱爲頁幀)進行管理。不考慮內存訪問權限時,線性地址空間的任何一頁,理論上能夠映射爲物理地址空間中的任何一個頁幀。最多見的分頁方式是 以 4KB 單位劃分頁,而且保證頁地址邊界對齊,即每一頁的起始地址都應被4K整除。在4KB的頁單位下,32位機的整個虛擬空間就被劃分紅了 2^20 個頁。由於虛擬地址是按頁所有被映射到相同大小的頁幀,而且頁面邊界對齊,所以虛擬地址的後12位能夠直接做爲物理地址的低12位使用。
爲了節省儲存頁表所需的內存空間(2^20 * 4B = 4M),32位操做系統常使用兩級頁表結構記載虛擬地址空間分頁現狀。所以每一個虛擬地址就由三部分組成,高10位是頁目錄(Page Directory)中內容的索引,中間10位是頁表索引,低12位則做爲對應物理地址在頁幀中的偏移量。
Figure 4 Paging Mechanism
頁目錄保存在CR3寄存器中,能夠直接訪問。訪問時以線性地址高10位做爲索引,直接檢索並獲得對應索引的 32 位頁目錄項。32位頁目錄項的結構如圖4中Page Directory部分所示,目錄項的高20位用以給出該目錄項對應的頁表在內存中的物理地址的高 20 位,1024個目錄項恰好能給出1024個頁表的入口地址。目錄項的低12位是一些標誌位,其中P標誌指明當前目錄項對應的頁表是否在內存中;U標誌指明 當前目錄項對應的頁的訪問權限;S標誌指明頁的大小是4KB或4MB,等等。另外,因爲每一個頁目錄項的長度爲32位,即4個字節,頁目錄中共有1024個 頁目錄項,因此頁目錄的總大小爲 4KB。
頁表保存在內存中。頁表項的長度是32位,每一個頁表中有1024個頁表項,可得出每一個頁表的大小是 4 KB。頁表在內存中存放時,與物理分頁的大小(4KB)對齊,因此每一個頁表所在的物理內存的起始物理地址的後12位都是0。而該物理地址的高20位又由頁 表對應的頁目錄項中的高20位指定,這樣就能夠獲得找到物理內存中的頁表了。找到頁表後,以線性地址的中間10位爲索引,檢索到該索引對應的32位頁表 項。和頁目錄項相似,頁表項的高20用以給出其對應頁幀的起始物理地址的高20位。頁表項的低12位是關於頁的標誌位。
頁幀對應物理內存。根據前面的兩步找獲得頁幀的起始物理地址的高20位後,因爲物理內存按4KB大小劃分紅頁幀,因此頁幀的起始物理地址的低12位 都是0。這樣高20位加低12位,獲得頁幀的起始物理地址。找到頁幀後,使用線性地址的低10位做爲偏移量,加上頁幀的起始物理地址後能找到線性地址對應 的物理地址了。須要注意的是,頁幀和頁表項的對應關係並非肯定的,頁表項指向的頁首先是虛擬頁,而後該虛擬頁的內容被儲存在任何合適的頁幀中。
操做系統按頁爲每一個進程分配虛擬地址範圍,理論上根據程序須要最大可以使用4G的虛擬內存。但因爲操做系統須要保護內核進程內存,因此將內核進程虛擬 內存和用戶進程虛擬內存分離,前者可用空間爲1G虛擬內存,後者爲3G虛擬內存。進程執行時,操做系統爲其分配的頁的頁目錄會被加載到CR3寄存器,頁表 會被加載到物理內存。分頁單元將線性地址轉換爲物理地址的過程當中,會檢查當前進程是否有訪問該分頁的權限,以及線性地址對應的頁數據是否在物理內存中,如 果上述檢查條件未被經過,分頁單元將會生成頁錯誤異常,進而停止進程或將相應分頁數據加載到物理內存。
物理地址擴展(Physical Address Extension)是Intel 32位CPU上獨有的一種虛擬地址分頁方式。理論上,32位CPU有32條內存尋址線,最多能訪問4G的物理內存;實際上在Linux系統中,用戶模式程 序須要線性地址空間,所以內核最多隻能直接訪問的物理內存爲1G。可是,隨着計算機軟件的發展,一臺32位計算機上可能同時運行許多進程,而這些同時運行 的進程所需內存量會大於4G,所以Intel爲其32位CPU增長了4條內存尋址線,共36條,這樣CPU支持的物理內存增大到2^36,即64GB。擴 展物理內存的同時,保持虛擬地址空間範圍爲4G不變。從而使32位的應用程序繼續使用32位的地址,每一個進程可以使用的最大虛擬內存還是4GB。
64GB的物理內存在4KB分頁下,被分紅2^24個頁幀,每一個頁幀的起始物理地址後12位仍然爲0,但前24位則須要頁表提供。而咱們知道,常規 分頁中,頁表項爲32位,其中只能提供20位做爲其指向頁幀的高20位物理地址,不能知足36位系統的尋址須要。能夠同經過增長頁表項的總長度來解決這個 問題,爲了保證以4KB的邊界對齊,咱們將頁表項的長度增長爲64位,8字節(而不能是恰好知足須要的36位),頁表大小保持4KB,那麼一個頁表中只有 512個頁表項(2^12 / 8)。
相應地,頁目錄也要適應36位的物理內存尋址能力,每條頁目錄項長度也變成64位,頁目錄大小保持4KB,一個頁目錄中只有512個頁目錄項。這樣一個頁目錄總共可檢索 512 × 512=2^18個頁,而虛擬地址空間共有 2^20個頁,因此總共須要4個頁目錄。
一個新的分層被加入到CR3控制器和頁目錄之間,這個新的分層是頁目錄指針表(Page Directory Pointer Table)。頁目錄指針表中有四個長度爲64位的指針,分別指向前述的4個頁目錄。頁目錄指針表被加載到64GB內存的第一個4GB上(物理地址 0x00000000 ~ 0xFFFFFFFF),CR3中則保存的是該頁目錄指針表的起始物理地址。
開啓物理擴展尋址方式後,將線性地址轉換爲物理地址的方式和以前有較大不一樣,具體過程如圖5所示。Figure 5 Linear Address Translation with PAE
首先由CR3獲得頁面指針表的物理地址,而後以線性地址的30 ~ 31位做爲索引獲得頁目錄。接下來的21 ~39位(共9 bits,剛好提供所有512個頁目錄項的索引)能夠幫助找到線性地址對應的頁表,12 ~ 20(共9 bits,剛好提供所有512個頁表項的物理地址)能夠幫助找到線性地址對應的頁幀的物理地址。
64位機的尋址能力爲 2^64 Bytes,但實際中用不到這麼多的虛擬內存,使用64位尋址方式還會形成尋址時間增長、內存空間浪費等不利因素,所以在實際應用中,對64位機使用48 位的尋址方式(最大支持256TB物理內存)。一樣的,將物理內存分爲4KB大小的頁幀,那麼就須要 48-12=36位物理地址高位來肯定頁幀位置。爲了減少儲存頁表所需的物理內存,實現內存權限訪問,能夠經過增長兩個頁目錄層來分散頁表。在Linux 中,採用4層分頁的方式來實現該目的。
(1) 爲進程建立一個獨立的虛擬地址空間(範圍)
例如在32位系統常規分頁狀態下,操做系統發現待執行程序的指令和數據總和爲32KB,那麼操做系統會爲進程分配8個頁的虛擬內存空間,並分配頁目錄和頁表,把頁目錄裝入CR3,把進程用到的頁表加載到內存。但並不把指令和數據加載到內存。
(2) 讀取程序可執行文件文件頭,而且創建虛擬空間與可執行文件中的代碼段、數據段的邏輯地址的映射關係這一步將程序指令和數據映射到虛擬內存空間中。
(3) 將 CPU 的指令寄存器設置成可執行文件的入口地址,啓動運行
執行程序過程時,若是當前指令或數據之在虛擬地址空間中,而實際上並不在物理內存中(前兩步都沒有將指令或數據加載到物理內存),將發生頁錯誤,這 時操做系統再從物理內存分配一個空閒的物理頁幀,並將虛擬地址頁對應的數據從磁盤拷貝加載到物理頁幀中,並創建頁表項和頁幀的映射關係。隨着進程的執行, 頁錯誤也會不斷產生,操做系統也會響應每一個頁錯誤併爲進程分配物理內存頁幀。但物理內存是有限的,爲一個進程可分配的物理內存也有限。所有可用物理內存都 分配給進程後,若是進程繼續拋出頁錯誤請求更多物理內存,這時候操做系統根據自身的頁置換操做算法,在保證進程正常運行的前提下,將先前爲進程分配的物理 內存頁幀收回,從新分給該進程。