記憶體定址之分頁機制
寫在前面:
分頁機制完成線性地址到實體地址的轉換 80x86 規定分頁機制是可選的。分段和分頁沒有什麼必然聯絡,分段可以說是 Intel 的 CPU 一直保持著的一種機制,而分頁只是保護模式下的一種記憶體管理策略。想開啟分頁機制,CPU必須工作在保護模式,而工作在保護模式可以不開啟分頁。
分頁機制由控制暫存器 CR0 中的 PG 位啟用,如PG=1則啟用分頁機制,把線性地址轉換為實體地址;如果PG=0則直接把段機制產生的線性地址當作實體地址使用。
為什麼要分頁?
問題的本質是在目前只分段的情況, CPU 認為線性地址等於實體地址,而線性地址是由編譯器編譯出來的,它本身是連續的,所以實體地址也必須要連續才行,但我們可用的實體地址不連續。換句話說,如果線性地址連續,而實體地址可以不連續,不就解決了嗎。所以要解除線性地址和實體地址一一對應的關係,然後將他們的關係重新建立,通過某種對映關係,可以將線性地址對映到任意實體地址。
頁與頁表
為了效率起見,將線性地址空間分成若干大小相等的片,稱為頁( Page )。相應的地,邏輯上把記憶體劃分為與頁大小相等的若干儲存塊,稱為( 物理 )塊或頁面( Page Frame )。常見的頁面大小為 4KB,每一頁都有 4K 位元組長,每一頁的起始地址都能被 4K 整除。
分頁機制通過把線性地址空間中的頁,重新定位到實體地址空間來進行管理,因為每個頁面的整個 4K 位元組作為一個單位來對映,並且每個頁面都對齊 4K 位元組的邊界,因此,線性地址的低 12 位經過分頁機制直接地作為實體地址的低 12 位使用。所以,線性地址的高 20位可用來定位一個物理頁,低 12 位可用來在該物理頁內定址。
頁表項結構
頁表就是個 N 行 l 列的表格,頁表中的每一行(只有一個單元格)稱為頁表項( Page Table Entry,PTE ),其大小是 4 位元組,頁表項的作用是儲存記憶體實體地址。當訪問一個線性地址時,實際上就是在訪問頁表項中所記錄的實體記憶體地址。
不管是頁目錄還是頁表,每個表項佔4個位元組,其表項結構基本相同,如上圖。
頁面地址對頁目錄而言,指的是頁表所在的物理頁面在記憶體的起始地址;對頁表而言,指的是頁所對應的物理頁面在記憶體的起始實體地址。頁面的起始地址是 4K 的整數倍,所以頁面項的低 12 位另做它用,核心用這 12 位來存放頁的屬性。
屬性包括以下內容:
- 第0位是存在位,如果P=1,表示該頁存在於實體記憶體中,如果P=0,表示不在實體記憶體中。
- 第1位是讀/寫位,若為1表示可讀可寫,若為0表示可讀不可寫。
- 第2位是普通使用者/超級使用者位,這兩位為頁目錄項提供硬體保護。當特權級為3的程序要想訪問頁面時,需要通過頁保護檢查,而特權級為0的程序就可以繞過頁保護。
- 第3位是PWT(Page Write-Through)位,表示是否採用寫透方式,寫透方式就是既寫記憶體(RAM)也寫快取記憶體,該位為1表示採用寫透方式
- 第4位是PCD(Page Cache Disable)位,表示是否啟用快取記憶體,該位為1表示啟用快取記憶體。
- 第5位是訪問位,若為 1 表示該頁被 CPU 訪問過了。
- 第6位是髒頁位,當 CPU 對一個頁面執行寫操作時,就會設定對應頁表項的D位為1。僅針對頁表項有效,並不會修改目錄項中的 D 位。
- 第7位是Page Size標誌,只適用於頁目錄項。如果置為1,頁目錄項指的是4MB的頁面,請看後面的擴充套件分頁。
- 第8位為全域性位,用來指定該頁是否為全域性頁,為 1 表示是全域性頁,為 0 表示不是全域性頁。若為全域性頁,該頁在快取記憶體TLB中一直儲存,給出虛擬地址直接給實體地址
- 第9~11位由作業系統專用,Linux也沒有做特殊之用。
怎樣用線性地址找到頁表中對應的頁表項?
首先要明確:分頁機制開啟前,要將頁表地址載入到控制暫存器 CR3 中,這個地址是頁表的實體地址。雖然分頁機制的作用是將線性地址轉換成實體地址,但其轉換過程相當於在關閉分頁機制下進行,過程中所涉及到的頁表及頁表項的定址,它們的地址都被 CPU 當作最終的實體地址(本來也是實體地址)直接送上地址匯流排,不會被分頁機制再次轉換(否則會無限遞迴下去)。
一個頁表項對應一個頁,所以,用線性地址的高 20 位作為頁表項的索引,每個頁表項要佔用 4 位元組大小,所以這高 20 位的索引乘以 4 後才是該頁表項相對於頁表實體地址的位元組偏移量。用 CR3 暫存器中的頁表實體地址加上此偏移量便是該頁表項的實體地址,從該頁表項中得到對映的實體地址,然後用線性地址的低 12 位與該物理頁地址相加,所得的地址就是最終要訪問的實體地址。
為什麼使用兩級頁表?
32 位環境下,4GB 線性空間分成 4KB 一個頁,那就是 1M 個頁,每個頁表項需要 4 個位元組來儲存,那麼整個 4GB 空間的對映就需要 4MB 的記憶體來儲存管理資訊。這只是一個線性地址空間的對映,每個程序都有自己的對映,假設有100個程序在執行,就需要 400MB 的記憶體來儲存管理資訊,太浪費了 。
一級頁表中所有頁表項必須提前建好,且要求是連續的。為什麼需要連續的呢? 如果不連續,就沒有辦法通過頁表暫存器+偏移量找到對應的頁表項了。
所以要解決的是:不要一次性地將頁表項全建好,需要時動態建立頁表項。二級頁表很好的解決了這個問題。
兩級頁表結構
所謂兩級頁表就是對頁表再進行分頁,一個頁表內的所有頁表項是連續存放的,頁表本質上是一堆資料,也是以頁為單位存放在主存中。
第一級稱為頁目錄表。每個頁表的實體地址在頁目錄表中都以頁目錄項( Page Directory Entey,PDE ) 的形式來儲存,4MB 的頁表再次分頁可以分為 1K ( 4MB / 4KB )個頁,對每個頁的描述需要 4 個位元組,所以頁目錄表佔用 4K 大小,正好是一個標準頁的大小,其指向第二級表。線性地址的高 10 位產生第一級的索引,由索引得到的表項中,指定並選擇了 1K 個二級表中的一個頁表。
第二級稱為頁表,存放在一個 4K 大小的頁面中,包含 1K 個表項,每個表項包含一個頁的物理基地址。線性地址的中間 10 位產生第二級索引,可以獲得包含頁的實體地址的頁表項。這個實體地址的高 20 位與線性地址的低 12 位形成了最終的實體地址。
有人覺著這樣做的話對映 4GB 地址空間需要 4MB+4KB 的記憶體,不是更大了嗎? 當然不是,在一個程序中,實際使用到的記憶體遠沒有4GB這麼大,一級頁表需要一次分配所有頁表空間,且要求是連續的,兩級頁表則可以在需要的時候再分配頁表空間,這樣節省記憶體。
線性地址到實體地址的轉換
- CR3包含著頁目錄的起始地址,用32位線性地址的最高10位A31~A22作為頁目錄的頁目錄項的索引,將它乘以4,與CR3中的頁目錄的起始地址相加,形成相應頁表的地址。
- 從指定的地址中取出32位頁目錄項,它的低12位為0,這32位是頁表的起始地址。用32位線性地址中的A21~A12位作為頁表中的頁面的索引,將它乘以4,與頁表的起始地址相加,形成32位頁面地址。
- 將A11~A0作為相對於頁面地址的偏移量,與32位頁面地址相加,形成32位實體地址。
整個過程是比較機械的,每次轉換先獲取物理頁基地址,再從線性地址中獲取索引,合成物理地址後再訪問記憶體。不管是頁表還是要訪問的資料都是以頁為單位存放在主存中的,因此每次訪問記憶體時都要先獲得基址,再通過索引(或偏移)在頁內訪問資料,因此可以將線性地址看作是若干個索引的集合。
快表 TLB( Translation Lookaside Buffer )簡介
分頁機制雖然很靈活,但是從線性地址轉換為實體地址的過程還是比較麻煩,過程不再贅述。每一個線性地址到實體地址的轉換都要重複轉換的過程,涉及到多級頁表就更顯得麻煩。而且處理器的速度和記憶體的速度完全是兩個數量級,頁表在記憶體中,轉換過程中頻繁的訪問記憶體,使得地址轉換的速度慢上加慢。
那能不能通過線性地址直接得到對應的實體地址,免去查表的過程? 答案是可以的,就是應用到快取,根據程式區域性性原理,可以將近來常訪問的地址和指令載入到速度更快的裝置中。處理器準備了一個快取記憶體,專門用來存放虛擬地址頁框與實體地址頁框的對映關係,這個快取就是 TLB,俗稱快表。
有了 TLB,處理器在定址之前會用虛擬地址的高 20 位作為索引來查詢 TLB 中的相關條目,如果命中則返回虛擬地址所對映的物理頁框地址,否則會查詢記憶體中的頁表,獲得實體地址後再更新 TLB。
Linux中的分頁機制
- 頁全域性目錄
- 頁頂級目錄
- 頁中間目錄
- 頁表
頁全域性目錄包含若干頁上級目錄的地址,頁上級目錄又依次包含若干頁中間目錄的地址,而頁中間目錄又包含若干頁表的地址。每一個頁表項指向一個頁框。線性地址因此被分成五個部分。圖中沒有顯示位數,因為每一部分的大小與具體的計算機體系結構有關。
對於沒有啟用實體地址擴充套件的32位系統,兩級頁表已經足夠了。從本質上說Linux通過使“頁上級目錄”位和“頁中間目錄”位全為0,徹底取消了頁上級目錄和頁中間目錄欄位。不過,頁上級目錄和頁中間目錄在指標序列中的位置被保留,以便同樣的程式碼在32位系統和64位系統下都能使用。核心為頁上級目錄和頁中間目錄保留了一個位置,這是通過把它們的頁目錄項數設定為1,並把這兩個目錄項對映到頁全域性目錄的一個合適的目錄項而實現的。
啟用了實體地址擴充套件的32 位系統使用了三級頁表。Linux的頁全域性目錄對應80×86 的頁目錄指標表(PDPT),取消了頁上級目錄,頁中間目錄對應80×86的頁目錄,Linux的頁表對應80×86的頁表。
最後,64位系統使用三級還是四級分頁取決於硬體對線性地址的位的劃分。
我們雖然討論的是Linux的分頁機制,實際上我們用了大部分篇幅來討論Intel CPU的分頁機制實現。因為Linux的分頁機制是建立在硬體基礎之上的,不同的平臺需要有不同的實現。Linux在軟體層面構造的虛擬地址,最終還是要通過MMU轉換為實體地址,也就是說,不管Linux的分頁機制是怎樣實現的,CPU只按照它的分頁實現來解讀線性地址,所以Linux傳給CPU的線性地址必然是滿足硬體實現的。例如說:Linux在32位CPU上,它的四級頁表結構就會相容到硬體的兩級頁表結構。可見,Linux在軟體層面上做了一層抽象,用四級頁表的方式相容32位和64位CPU記憶體定址的不同硬體實現。
相關推薦
記憶體定址之分頁機制
寫在前面: 分頁機制完成線性地址到實體地址的轉換 80x86 規定分頁機制是可選的。分段和分頁沒有什麼必然聯絡,分段可以說是 Intel 的 CPU 一直保持著的一種機制,而分頁只是保護模式下的一種記憶體管理策略。想開啟分頁機制,CPU必須工作在保護模式,而工
Linux內存尋址之分頁機制
緩存 itl ans linux 存儲器 apt target tar linux中 http://blog.xiaohansong.com/2015/10/05/Linux內存尋址之分頁機制/ 在上一篇文章Linux內存尋址之分段機制中,我們了解邏輯地址通過分段機制轉換為
Linux分頁機制之分頁機制的演變--Linux記憶體管理(七)
1 頁式管理 1.1 分段機制存在的問題 分段,是指將程式所需要的記憶體空間大小的虛擬空間,通過對映機制對映到某個實體地址空間(對映的操作由硬體完成)。分段對映機制解決了之前作業系統存在的兩個問題: 地址空間沒有隔離 程式執行的地址不確定 不過分段方法存在一個嚴重的問題:記憶體的使用效率
Linux 記憶體定址之分段機制
前言 最近在學習Linux核心,讀到《深入理解Linux核心》的記憶體定址一章。原本以為自己對分段分頁機制已經理解了,結果發現其實是一知半解。於是,查找了很多資料,最終理順了記憶體定址的知識。現在把我的理解記錄下來,希望對核心學習者有一定幫助,也希望大家指出錯誤之處。分段到
Linux分頁機制之分頁機制的實現詳解--Linux記憶體管理(八)
[注意] 如果您當前使用的系統並不是linux,或者您的系統中只有一份linux原始碼,而您又期待能夠檢視或者檢索不同版本的linux原始碼 LXR (Linux Cross Reference)是比較流行的linux
記憶體的管理和分頁機制
一、問題提出: 我們經常會使用malloc()以及free()函式進行堆區記憶體申請與釋放。那麼你是否會這樣做: int * p = malloc(0);/*malloc分配了0個位元組嗎,如果是那麼p指向誰呢,是NULL嗎*/ free(p);/*假如malloc分配了0個位元組,p指向了NU
【淺談】x86記憶體管理的分段分頁機制
最近一直在接著作業系統的課程,著手跟著os.dev上的大神的文件寫一個小型的核心,然後前期的東西自己一直在看也沒時間寫Blog,最近做到了記憶體管理這裡,看了Jamesmolloy的文件還有一些os.dev.org上的關於memorymanage的東西,總覺的還是寫點東西吧
Linux的記憶體定址——淺談分段和分頁機制
本文會以80x86架構,linux2.6為例,簡單介紹記憶體的分段和分頁機制。1. 三種記憶體地址關於記憶體地址,首先要了解它有三種,分別是邏輯地址、線性地址和實體地址。把邏輯地址轉換為線性地址是由一個叫做分段單元的硬體電路完成的。同樣地,還有一個叫做分頁單元的硬體電路負責把
Linux分頁機制之概述--Linux記憶體管理(六)
1 分頁機制 在虛擬記憶體中,頁表是個對映表的概念, 即從程序能理解的線性地址(linear address)對映到儲存器上的實體地址(phisical address). 很顯然,這個頁表是需要常駐記憶體的東西, 以應對頻繁的查詢對映需要(實際上,現代支援VM的處理器都有一個叫TLB的硬體級頁表快取部件
程式設計機制探析之執行棧與記憶體定址
《程式設計機制探析》第四章 執行棧與記憶體定址 計算機啟動之後,作業系統程式首先從硬碟進入記憶體條,成為最先執行起來的一批程序。這一批作業系統程序可了不得,它們規定了CPU工作的總流程。CPU工作的時候,必須嚴格遵守作業系統程序定義的工作流程。 為了滿足人類使用者的需求
作業系統實現之記憶體分頁機制.虛擬空間
這裡我們將把頁目錄表放在0x100000處.頁表也挨著頁目錄表放在0x101000處(第二個頁表.當然在此之前應該把實體記憶體給算出來.這裡可以使用bios中斷來獲取實體記憶體%include "boot.inc" section loader vstart=loader_base_addr ;-------
深入理解計算機系統-之-記憶體定址(四)--linux中分段機制的實現方式
linux中的分段機制 前面說了那麼多關於分段機制的實現,其實,Linux以非常有限的方式使用分段。因為,Linux基本不使用分段的機制(注:並不是不使用,使用分段方式還是必須的,會簡化程式的編寫和執行方式),或者說,Linux中的分段機制只是為了相容IA
xv6學習筆記 分頁機制 和記憶體管理
XV6分頁機制、記憶體管理 報告內容 0. mmu.h的閱讀 mmu.h原始碼中給出了XV6虛擬地址的構成,及所代表的含義 mmu.h中還有頁表的相關資訊,每個頁目錄都與1024條記錄,每一個頁表中也有1024條記錄,每一頁的大小4096位元組,也就是4kb。 // Pag
《程式設計機制探析》第四章 執行棧與記憶體定址
《程式設計機制探析》第四章 執行棧與記憶體定址計算機啟動之後,作業系統程式首先從硬碟進入記憶體條,成為最先執行起來的一批程序。這一批作業系統程序可了不得,它們規定了CPU工作的總流程。CPU工作的時候,必須嚴格遵守作業系統程序定義的工作流程。為了滿足人類使用者的需求,現代的作
Windows記憶體分頁機制
大多數現代的作業系統都支援虛存, 這使得系統上的每個程式都擁有自己的地址空間. 每當程式讀取記憶體時, 都必須指定一個地址. 對於每個程序, 該地址必須轉換為實際的實體記憶體地址. 例如, 若我們的MzfHips.exe 程式需要的資料在虛擬記憶體地址的0x0041FF10處, 則實際的實體地址可能被對映為0
虛擬記憶體的好處及多級分頁機制的原因
眾所周知,在現代計算機系統中都使用了虛擬地址。在一個程式的執行那個過程中,由CPU產生虛擬地址,該虛擬地址經過MMU轉換成實體地址,然後使用該實體地址去訪問記憶體。那麼虛擬地址存在的原因是什麼呢?為何不能由CPU直接產生實體地址呢? 第一,使用虛擬地址可以更加高效的使用實體
Linux核心原始碼分析--記憶體管理(一、分頁機制)
Linux系統中分為幾大模組:程序排程、記憶體管理、程序通訊、檔案系統、網路模組;各個模組之間都有一定的聯絡,就像蜘蛛網一樣,所以這也是為什麼Linux核心那麼難理解,因為不知道從哪裡開始著手去學習。很多人會跟著系統上電啟動 BIOS-->bootse
80386的記憶體定址機制複習
題外話: “不試試怎麼知道不行。”金句啊金句。 80x86的記憶體定址機制複習2011.06.03 80386處理器的工作模式: 80386處理器有三種工作模式:真實模式、保護模式 和 虛擬86模式。 模式的切換:真實模式和保護模式之間可以相互轉換,保護模式和虛擬86模式之間可以相互轉換,而真實模式和虛擬8
Linux記憶體地址的分段、分頁機制(上)
在深入學習Linux核心原始碼之前,需要先對Linux執行的硬體基礎有個大概的認識,主要包括CPU中的暫存器和磁碟。 1.i386暫存器和系統指令 在Linux系統中使用的主要包括i386暫存器中的16位標誌暫存器,4個記憶體管理暫存器和4個控制暫存器及
計算機原理學習(6)-- x86-32 CPU和記憶體管理之分頁管理
前言 上一篇我們瞭解了x86-16 CPU計算機的記憶體訪問方式,定址方式,以及基於MS-DOS的應用程式的記憶體佈局。這一篇會主要介紹32位處理器的記憶體訪問,記憶體管理以及應用程式的記憶體佈局。雖然目前64位CPU已經非常普及了,不過相對於32位的記憶體管理方式並沒