三種地址
今天我們來學習一下 Linux 下的內存尋址,通常我們在談內存地址的時候于颖,我們在談什么呢古今?所以首先我們得明確三種地址(以80x86微處理器為例):
- 邏輯地址(logical address):機器語言指令中用來指定一個操作數或者一條指令的地址货邓,每一個邏輯地址由一個段和偏移量組成馏艾,偏移量指明了從段開始的地方到實際地址之間的距離
- 線性地址(linear address 也叫做虛擬地址 virtual address):是一個 32 位無符號整數,可以用來表達 4GB 的地址捣辆,通常用十六進制數表示
- 物理地址(physical address):用于內存芯片內的內存單元尋址蔬螟,它們從微處理器的地址引腳發(fā)送到內存總線上的電信號對應。
以上內容來自 《Understanding The Linux Kernel》
內存管理單元(Memory Management Unit, MMU)通過分段單元的把一個邏輯地址轉換成線性地址汽畴,通過分頁單元把線性地址轉換成物理地址旧巾。
從 80286 開始耸序,Intel 處理器以兩種不同的方式執(zhí)行地址轉換,分別為實模式(real mode)和保護模式(protected mode)鲁猩。下面我們就展開描述坎怪,在保護模式下,硬件的分段機制和分頁機制
分段機制
段選擇符和段寄存器
邏輯地址有兩部分組成:一個段標識符和一個偏移量廓握。短標識符是一個 16 位的字段搅窿,成為段選擇符;偏移量是一個 32 位長的字段隙券。
為了快速找到段選擇符男应,處理器提供了段寄存器用來存放段選擇符,分別為 cs娱仔,ss沐飘,ds,es牲迫,fs耐朴,gs。
其中有三個有專門的用途:
- cs:代碼段寄存器恩溅,指向包含程序指令的段
- ss:棧段寄存器,指向包含當前程序棧的段
- ds:數據段寄存器谓娃,指向包含靜態(tài)數據或者全局數據段
其中脚乡,cs 含有一個兩位的字段,用來指明當前的 CPU 特權等級(CPL)滨达,0 代表最高等級奶稠、3 代表最低等級。 Linux 只用到了 0 和 3捡遍,分別稱為 內核態(tài) 和 用戶態(tài)
段描述符
每個段由一個 8 字節(jié)的段描述符表示锌订,描述了段的基本信息。段描述符放在全局描述符表(GDT)或者局部描述符表(LDT)中画株。
通常只會定義一個 GDT辆飘,每個進程除了放在 GDT 中的段以外,如還需要創(chuàng)建附加的段谓传,就可以有自己的 LDT蜈项。GDT 在主存中的地址和大小存放在 gdtr 控制寄存器中,LDT 的地址和大小則存放在 ldtr 中续挟。
段描述符包涵以下關鍵字段:
- Base:包含段的首字節(jié)的線性地址
- Type:描述了段的類型特征和它的存取權限
- DPL:限制對這個段的存取權限紧卒,表示訪問這個段的要求的最小 CPU 特權等級
- P:Segment-Present 標志,表明當前段是否在內存中诗祸。Linux 總是把這個標志設為 1跑芳,從來不會把整個段交換到磁盤上去
分段單元
介紹完上述的概念轴总,那么邏輯地址是如何轉換到線性地址的呢?我們通過下面的步驟來簡單說明:
- 先檢查段選擇符的 TI 字段博个,以決定段描述符保存在 GDT 中還是在 LDT 中怀樟。如果在 GDT 中,分段單元從 gdtr 中得到 GDT 的線性基地址坡倔,如果在 LDT 中漂佩,分段單元從 ldtr 中得到 LDT 的線性基地址
- 從段選擇符的 index 字段計算段描述符的地址,計算方法為 index * 8 (一個段描述符為 8 字節(jié))罪塔,將這個結果與 gdtr 或者 ldtr 中的基地址相加
- 把邏輯地址中的偏移量與段描述符中的 Base 值相加投蝉,就得到了線性地址
快速訪問分段機制
如果每次都執(zhí)行上述的過程,可能會比較耗時征堪,因為 GDT 是存儲在主存中的瘩缆,每次都訪問主存,可能會比較慢佃蚜,所以為了提高邏輯地址到線性地址的轉換速度庸娱,80x86 處理器提供了一組6個不可編程寄存器。每一個不可編程寄存器含有 8 個字節(jié)的段描述符谐算,具體的值由相對應的段寄存器中的段描述符確定熟尉。每當一個段選擇符被裝入段寄存器,相對應的段描述符就由主存裝入到對應的不可編程寄存器洲脂,這樣就可以不需要上面三個過程中的前兩個斤儿,就可以得到線性地址了。
分頁機制
頁恐锦、頁框和頁表
分頁單元把線性地址轉換成物理地址往果,其中的關鍵任務是把所請求的訪問類型與線性地址的訪問權限做對比。
- 頁:為了更高效和更經濟的管理內存一铅,線性地址被分為以固定長度為單位的組陕贮,成為頁。頁內部連續(xù)的線性地址空間被映射到連續(xù)的物理地址中潘飘。這樣肮之,內核可以指定一個頁的物理地址和對應的存取權限,而不用指定全部線性地址的存取權限卜录。這里說頁局骤,同時指一組線性地址以及這組地址包含的數據
- 頁框:分頁單元把所有的 RAM 分成固定長度的頁框,每一個頁框包含一個頁暴凑。頁框是主存的一部分峦甩,因此也是一個存儲區(qū)域。頁和頁框相比,前者只是一個數據塊凯傲,可以存放在頁框或者磁盤中犬辰。
- 頁表:把線性地址映射到物理地址的數據結構成為頁表,頁表存放在主存中冰单,并在啟用分頁單元之前必須由內核對頁表進行適當的初始化
常規(guī)的分頁
從 80386 開始幌缝,Intel 處理器的頁大小為 4KB。
32 位的線性地址被分為 3 個域:
- Directory(目錄):最高 10 位
- Table(頁表):中間 10 位
- Offset(偏移量):最低 12 位
線性地址的轉換分兩步完成诫欠,每一步都基于一種轉換表涵卵,第一種轉換表成為頁目錄表,第二種轉換表成為頁表荒叼。
為什么需要兩級呢轿偎?目的在于減少每個進程頁表所需的 RAM 的數量。如果使用簡單的一級頁表被廓,將需要高達 2^20 個表項來表示每個進程的頁表坏晦,即時一個進程并不使用所有的地址,二級模式通過職位進程實際使用的那些虛擬內存區(qū)請求頁表來減少內存容量嫁乘。每個活動的進程必須有一個頁目錄昆婿,但是卻沒有必要馬上為所有進程的所有頁表都分配 RAM,只有在實際需要一個頁表時候才給該頁表分配 RAM蜓斧。
頁目錄項和頁表項的結構如下:
- Present 標志:為 1 則表示頁在主存中仓蛆;如果為 0 則表示不在內存中,如果執(zhí)行一個地址轉換的時候挎春,所需的頁表項或者頁目錄項中的該標志為 0看疙,那么分頁單元就把該線性地址存在在控制寄存器 cr2 中,并產生 14 號異常:缺頁異常搂蜓。
- 包含頁框物理地址最高 20 位的字段
- Dirty:當對頁框進行寫操作時就設置這個標志
- Read/Write 標志:含有頁或者頁表的存取權限
- User/Supervisor:含有訪問頁或者頁表所需的特權等級
了解了以上結構之后狼荞,我們看看如何從線性地址轉換到物理地址的:
- 線性地址中的 Directory 字段決定頁目錄中的目錄項辽装,目錄項指向適當的頁表
- 線性地址中的 Table 字段又決定頁表的頁表項帮碰,頁表項含有頁所在頁框的物理地址
- 線性地址中的 Offset 地段決定了頁框內的相對位置,由于 offset 為 12 為拾积,所以一頁含有 4096 字節(jié)的數據
以上描述的為 80x86 微處理器硬件分頁機制殉挽,不同架構的 64 位處理器分頁機制,大體的思路就是將二級模式拓展為三級(ia64)或者四級(x86_64)拓巧,以達到對更大范圍尋址空間的支持斯碌。具體到 Linux 中如何使用操作系統(tǒng)的分段分頁機制以及進程的地址空間管理,后續(xù)再談