如果這個世界上只有risc架構的處理器的話, 記憶體定址就非常簡單了, 無非是虛擬位址轉實體地址什麼的. 但是由於有x86的存在, 記憶體管理複雜了許多. 由於歷史影響, x86不得不一直保留著傳統的段式定址方式.
記憶體位址
在x86下有邏輯位址(段+偏移量), 線性位址(虛擬位址), 和實體地址.
對於x86架構來說, 分段處理單元始終都是在工作的, 所有對於x86架構, c語言操作指標的時候, 指標的位址不能稱之為虛擬位址, 而應該是邏輯位址. 邏輯位址加上段描述符裡面的偏移才是虛擬位址(線性位址).
邏輯位址經過分段單元(segmentation unit)的硬體電路, 轉換成為線性位址, 然後分頁單元(paging unit)的硬體電路把線性位址轉換成乙個實體地址. x86下通過cr0暫存器可以設定開啟或關閉分頁單元, 只有在保護模式下才能開啟分頁單元.(當pe位清0時(實模式), 設定pg位將導致處理器產生乙個異常中斷)
在risc架構的處理器中, 基本都取消了段式管理模式, 只使用頁式記憶體管理. 這樣就沒有了邏輯位址的概念, 只保留虛擬位址和實體地址, 從而精簡了指令集和設計.
記憶體的訪問
在smp系統中, 或者在支援dma的單cpu系統中, 由於記憶體有可能被併發訪問, 所以便需要一種所謂的內在仲裁器(memory arbiter). 該設計完全由硬體來實現, 因此對軟體來說是隱藏的.
x86的段式定址
x86提供了6個段暫存器, cs dsss es fs gs. 每個段暫存器有16個bit, 裡面存放的是段描述符, 簡單來說, x86是通過段暫存器找到段描述符, 通過段描述符再找到段的基址來完成邏輯位址到線性位址的轉換的.
段暫存器是有隱藏屬性的, 當段選擇符被設定到段暫存器中的時候, 相應的段描述符就由內在裝入到對應的非編譯cpu暫存器. 這樣當邏輯位址轉換到虛擬位址的時候, 就不需要再去記憶體中讀取段描述符了.
rpl位說明的是程序對段訪問的請求許可權(request privilege level),是對於段選擇子而言的,每個段選擇子有自己的rpl,它說明的是程序對段訪問的請求許可權。而且rpl對每個段來說不是固定的,兩次訪問同一段時的rpl可以不同。當程序訪問乙個段時,需要程序特權級檢查,一般要求dpl >= max , 因此rpl有可能會削弱cpl的作用.
ti位標識了是要去gdt還是ldt中查詢索引.
cs暫存器沒有rpl段, 只有cpl段, 長度和rpl段一樣. 它表示cpu當前的特權級(ring0~ring3),windows和linux都只選用了0級和3級.
注:cpl: current privilege level, 存放在cs暫存器中.
rpl: request privilege level, 存放在其它幾個段暫存器中,表示請求的許可權.
dpl: descriptor privilege level, 儲存在段描述符中, 表明該段需要的訪問許可權.
段描述符
每個段由乙個8位元組的段描述符(segment descriptor)表示, 段描述符放在全域性描述符表(global descriptor table, gdt)或區域性描述符表(localdescriptor table, ldt)中.
gdt在記憶體中的基位址和大小存放在gdtr控制暫存器中, 當前正在被使用的ldt位址和大小放在ldtr控制暫存器中. 在linux下, 單處理器系統中只有乙個gdt,而在多處理器系統中每個cpu對應乙個gdt, 所有的gdt都存放在cpu_gdt_table陣列中.
ldt不是全域性可見的,它們只對引用它們的任務可見,每個任務最多可以擁有乙個ldt。另外,每乙個
ldt自身作為乙個段存在,它們的段描述符被放在
gdt中。
ia-32為ldt的入口位址也提供了乙個暫存器ldtr,因為在任何時刻只能有乙個任務在執行,所以ldt暫存器全域性也只需要有乙個。如果乙個任務擁有自身的ldt,那麼當它需要引用自身的ldt時,它需要通過lldt將其ldt的段描述符裝入此暫存器。lldt指令與lgdt指令不同的時,lgdt指令的運算元是乙個32-bit的記憶體位址,這個記憶體位址處存放的是乙個32-bit gdt的入口位址,以及16-bit的gdt limit。而lldt指令的運算元是乙個16-bit的選擇子,這個選擇子主要內容是:被裝入的ldt的段描述符在gdt中的索引值.
乙個段描述符是8位元組長, 因此它在gdt或ldt內的相對位址是由段選擇符的最高13位的值乘以8得到的. 例如: 如果gdt在0x00020000(gdtr暫存器的值), 段選擇符是2, 則段描述符的位址是0x00020000 + (2 * 8) = 0x00020010
gdt的第一項總是為0, 這就確保空段選擇符的邏輯位址會被認為是無效的, 因此引起乙個處理器異常. 能夠儲存在gdt中的段描述符的最大數目是8191, 即213 -1.
從邏輯位址轉化為虛擬位址的過程如下圖:
分頁記憶體管理機制
與分段記憶體管理機制不同, 支援mmu的處理器基本都實現了記憶體分頁管理機制, 以支援虛擬位址操作. 分頁管理機制也可以像分段機制一樣, 保護記憶體, 控制訪問許可權等. 分頁管理機制在大部分處理器上的設計都是類似的.
頁(page)
核心把物理頁作為記憶體管理的基本單位. mmu以頁大小為單位來管理系統中的頁表.
32位系統的頁大小一般為4kb, 64位系統的頁大小一般為8kb.32位linux系統中一般只需要兩級頁表, 而64位系統下則用到了4級.
核心用strcut page結構來表示系統中的每個物理頁. 每個物理頁有自己的flag(狀態標識), count(引用計數), virtual(虛擬位址)域.
區(zone)
由於硬體的限制, 只有某些頁能做特定的任務, 所以核心把頁劃分為不同的區.
linux主要使用的4種區:
zone_dma: 可以用來執行dma操作的頁.
zone_dma32: 可以被32位裝置訪問的用於執行dma的頁.
zone_nornal: 能正常對映的頁.
zone_highmem: 高階記憶體, 不能永久對映到核心位址空間.
tlb
tlb的作用是快取虛擬位址到實體地址的對映關係.
tlb命中成功的時候, mmu不需要讀取記憶體中的頁表, 減少了對記憶體的讀取.
x86下線性位址到實體地址的對映
x86架構下, cr3指向了pmd的基位址.
具體轉換演算法如下:
1. cr3 + page directory (10msb) = 指向 page_table_base
2. page_table_base + pagetable (10 中間位) = 指向 page_frame_base
3. page_frame_base + offset =實體地址 (獲得頁框)
總結:本來只是想講一講記憶體的定址方式的, 結果一不小心變成了x86專題了, 這裡其實也從側面說明了x86的複雜(窩心的設計, 看了好久才看懂). 其實拋開分段記憶體管理只討論分頁的話, x86和其它risc架構的處理器並沒有太大的不同.
linux不像windows那樣利用了很多x86的分段特性, 而是把幾個重要的段設定成統一的基位址(0x00000000)的方式, 來有限的使用分段機制. 這樣核心**在不同處理器架構上的相容性就比較簡單了.
深入理解linux核心(第三版)
linux核心設計與實現(第三版)
linux 的 numa 技術
Linux核心設計與實現(十二) 記憶體管理
一 基本概念 頁 核心把物理頁作為記憶體管理的基本單元。mmu 記憶體管理單元 是管理記憶體,並把虛擬位址轉換為實體地址的硬體。mmu已頁大小為單位管理系統中的也不。從虛擬記憶體的角度來看,頁就是最小單位。當時必須理解一點,page結構與物理頁相關,並非與虛擬頁相關。系統中的每個物理頁都要分配乙個這...
linux核心設計與實現讀書筆記 記憶體管理
一 頁 記憶體管理的基本單位 頁。核心中用struct page表示物理頁,位於,屬性包括flag頁狀態 count頁的引用計數,virtual頁虛擬位址。目的在於描述物理記憶體本身而非其中的資料。1 獲得頁 核心 alloc pages gft t gft mask,order 連續分配2n個連續...
linux 核心設計與實現相關
有待繼續補充。第一章 linux核心簡介 需要注意 核心開發其實並不難。第二章 從核心出發 核心開發需要注意 1 沒有c庫,c庫太大了 2 沒有記憶體保護機制 3 不要輕易使用浮點數 4 可移植的重要性 5 同步和併發 疑問 編譯和安裝核心?必須在linux下麼?其他機器安裝了gcc編譯器呢?能否編...