標籤:
目錄
- 1 記憶體位址類型
- 2 硬體上的“記憶體段式管理”
- 2.1 段選擇符和段寄存器
- 2.2 段描述符 (Segment Descritor)
- 2.3 段描述符的快速存取
- 2.4 邏輯地址到線性地址的轉換
- 3 Linux 裡記憶體段式管理
- 4 硬體上的頁式管理
- 5 Linux 中的記憶體頁式管理
1 記憶體位址類型
程式員通過記憶體位址 (memory address) 來訪問記憶體單元中儲存的內容。在 X86 架構上,需要區分下面三種地址類型:
- 邏輯地址 (Logical address)
邏輯地址是機器語言中指令操作符的運算元的地址。邏輯地址由段和位移兩個部分構成。
- 線性地址 (Linear address)
線性地址也被成為虛擬位址,一個 32 位的無符號數最多可以定址 4 GB 的空間。線性地址通常用 16 進位來表示, 例如 32 位無符號數的定址空間: 0x00000000 ~ 0xffffffff 。
- 物理地址 (Physical address)
物理地址用來在記憶體晶片中定址,其地址和處理器向記憶體匯流排發出的電訊號的引腳向對應,一般用 32 或者 36 位的無符號數來表示。
換句話說,物理地址是 CPU 和記憶體之間使用的地址。
三種地址之間的轉換關係如:
+--------------+ +--------+
Logical Addr | SEGMENTATION | Linear Addr | PAGING | Physical addr
--------------> | UNIT |------------------>| UNIT |--------------->
+--------------+ +--------+
其中的 SEGMENTATION UNIT 負責將邏輯地址轉換成線性地址, PAGING UNIT 負責將線性地址轉換成為物理地址。這兩個 UNIT 會在後面的小節中介紹。
多 CPU 會共用記憶體,而記憶體的讀寫必須是串列的,為此一個名為 memory arbitor (仲裁器) 的硬體。其實在單 CPU 的機器上,也使用這個仲裁器,因為單 CPU 上在使用 DMA 的時候也要處理記憶體的並發訪問問題。但從編程的角度上來看,我們不需要關心這個仲裁器。 2 硬體上的“記憶體段式管理” 2.1 段選擇符和段寄存器
前面提到,邏輯地址由兩個部份組成:段和和段內位移。其中,段可以用一個 16 位的“ 段選擇符”來表示,而位移,則可以用一個 32 位的變數來表示。
X86 處理器提供了段寄存器以便快速擷取段描述符,這些寄存器包括:
- cs
Code Segment Register , 用於存放程式碼片段,也就是程式的指令。
- ss
Stack Segment Register ,用於存放當前程式的棧。
- ds
Data Segment Register ,用於存放全域和靜態資料。
- es, fs, gs
通用寄存器。
其中, cs 寄存器還有一個重要功能: cs 寄存器中包含了一個 2-bit 的地區: CPL,也就是 Current Privilege Level, 代表了 CPU 的當前的特權等級 0為最高, 3為最低。以便確認當前的 CPU 是否可以執行那段代碼。此外,Linux中僅用了層級 0 和 3, 分別對應了核心態和使用者態。 2.2 段描述符 (Segment Descritor)
每一個”段“ 都由段選取器進行選擇,並由段描述符來表示該段的各種屬性,包括段的起始位置,段的長度等等。段描述符為 8 Byte 大小,存放於 GDT (Global Descritor Table) 或者 LDT (Local Descritor Table) 中。 GDT 和 LDT 都有相應的寄存器存放,分別名為 gdtr 和 ldtr 。至於該 Descritor 究竟存放於哪個寄存中,取決於段選擇符的 TI 這個 bit.
下面這個表格說明了段描述符的組成:
Segment Descritor
Byte/ 3 | 2 | 1 | 0 |
/ | | | |
|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|
+-------------------------------+-------------------------------+
7 | BASE(24-31) |G|D|.|.|LIMIT()|P|DPL|S| TYPE | BASE(16-23) | 4
+-------------------------------+-------------------------------+
3 | BASE(0-15) | LIMIT(0-15) | 0
+-------------------------------+-------------------------------+
其中, 第6個Byte的第4個bit 是 AVL
其中:
- Base
該段的第一個 Byte 的線性地址。
- G
Granularity,粒度。如果該位為 0 ,表明段的大小以 byte 為單位,如果為 1 ,表明大小以 4096 bytes (一個記憶體頁的大小)為單位 .
- D
- 1: address 為 32 位
- 0: address 16 bit.
- AVL
Linux 中不使用該位。
- Limit
Limit 中儲存了該段的最後一個記憶體單元的位移,換句話說,它指明了該段的大小。根據 G 的不同,該段的大小也不同:
- G = 1: 段大小範圍為 1 Byte ~ 1 MB (2^20 * 1 B)
- G = 0: 4K ~ 4GB (2^20 * 4096 B)
- S
Sytem Flag ,如果該 Flag 被設定,則表明該段中儲存了關鍵的資料結構,如 LDT 等等;否則,則說明該段為普通的資料區段或者程式碼片段。
- TYPE
表明了該段的類型,以及存取權限。
- DPL
Descriptor Privilege Level: 用於限制對該段的訪問,表明了想要對該段進行訪問所需的最低許可權。例如, DPL=0的段只有CPL=0時候才能訪問;而 DPL=3 的段,則任意 PL 的 CPU 都可以訪問。
- P
Segment-Present flag: 該值為1則表明段存在於記憶體當中,否則該段則存在於 SWAP 中。
2.3 段描述符的快速存取
前面提到, 段選擇符長度為 16 bit, 其組成如下:
Segment Selector
Byte/ 1 | 0 |
/ | |
|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|
+-------------------------------+
| Index |.| . |
+-------------------------------+
^ ^
| |
TI ---------------------+ | (0 -- GDTR, 1 -- LDTR)
RPL ------------------------+ (Required Privilege Level)
其中:
- index
Index 指出了段選擇符所指向的段描述符在 GDT 或者 LDT 中的位置。
- TI
Table Indicator, 指出了段選擇符是在 GDT 還是 LDT。
- TI = 0: GDT 中。
- TI = 1: LDT 中。
- RPL
Requestor Privilege Level, 表明了載入該段選擇符時候的 CPU 的 CPL。
通過段選擇符中的 TI 和 Index ,可以找到相應的段描述符。此外, 80X86 處理器為每一個段選擇符寄存器都提供了一個不可程式化的寄存器,用於存放該選擇符對應的段描述符。每次段選擇符寄存器中裝載了段選擇符,相應的段描述符就會自動裝載到對應的那個不可程式化的寄存器中,以便於訪問。
GDT/LDT Segment
+-------------+ +---------------+
| NULL | +----------->|.#.%#%---.+ +. |<----------+
+-------------+ | |+.++*#m#-m*-+m | |
+----> | 段描述符 1 |-------+ |**#+#%--m#%++-.| |
| +-------------+ |-..#*+%#m%m-%+ | |
| | 2 | |..+-- .+..-++ | |
| +-------------+ +---------------+ |
| | 3 | |
| +-------------+ |
| | . | |
| +-------------+ |
| | . | |
| +-------------+ |
| |
| |
| --------------- |
| 段寄存器 ----/ 不可程式化寄存器 \---- |
| +-------------------+ / +------------------+ \ |
+---| 段選擇符 | ( | 段描述符 | )----+
+-------------------+ \ +------------------+ /
----\ /----
---------------
2.4 邏輯地址到線性地址的轉換
邏輯地址到線性地址的轉換由 SEGMENTATION UNIT 完成, 需要下面幾個步驟:
用一段虛擬碼來表示:
addr_t logical_2_linear(seg_selector selctor)
{
descriptor_table dt = NULL;
seg_desc desc = NULL;
/* Step 1 */
switch (selctor->TI) {
case 0: {
dt = GDT;
break;
}
case 1: {
dt = LDT;
break;
}
default:
dt = GDT;
break;
}
/* Step 2 */
desc = dt + selector->index * 8;
/* Step 3 */
return desc->base + selector->offset;
}
80X86 上, 由於引入了不可程式化的寄存器用於儲存段選擇符相應的段描述符,這個轉換過程的前兩步可以省略掉。 3 Linux 裡記憶體段式管理
相對於記憶體的段式管理, Linux 更喜歡記憶體的頁式管理。80x86 上, 盡在需要的時候才會使用一下段式管理。對段式管理我沒有仔細看,只知道:
- Linux 的記憶體段式管理,針對核心空間和使用者空間,分別將記憶體分為了程式碼片段和資料區段,共四個段。
- 每個段的 base 都為 0 ,這樣,selector->offset 實際上就是記憶體的線性地址。
其他的東西,遇到的時候再看吧。 4 硬體上的頁式管理
PAGING UNIT 負責將線性地址轉換成為物理地址。線性地址按照固定長度細分成頁。
這裡有若干基本概念,如下:
4.1 常規分頁
前面提到, 線性地址的長度為 32 bit ,它可以分成三個部分,如所示:
Byte/ 3 | 2 | 1 | 0 |
/ | | | |
|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|
+-------------------------------+-------------------------------+
| Directory | Table | Offset |
+-------------------------------+-------------------------------+
線性地址向物理地址的轉換需要兩個步驟,其中需要兩個表: Page Directory 和 Page Table 。每一個活躍的進程都必有分配一個 Page Directory ,但 Page Table 中對應的 Page Frame 卻沒有必要立即分配, Pages Frame 在有需要的時候再進行分配即可。
進程的 Page Directory 的物理地址存放在控制寄存器 cr3 裡面,這樣,線性地址到物理地址的轉化可分為如下幾個步驟:
- 根據線性地址中的 Directory ,可以從 cr3 指向的 Page Directory 中找到 Page Table
- 根據線性地址中的 Table , 可以從前面找到的 Page Table 中找到相應的 Page Frame
- 根據線性地址中的 Offset , 從前面找到的 Page Frame 中找到該 PageFrame 中的相對位置。
整個過程如所示:
Linear Address
-------------+---------------+---------------+---------------+
Byte/ 3 | 2 | 1 | 0 |
/--------------+---------------+---------------+---------------+
/7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|
+-------------------------------+-------------------------------+
| Directory | Table | Offset |
+--------+----------------------+----------------+--------------+
| | | Page Frame
| | | +---------+
| | | | |
| | | +---------+
| | Page +----->| ###### |
| | Table +---------+
| | +---------+ | |
| Page | | | +---------+
| Directory | +---------+ | |
| +------------+ +--->| ###### |------> +---------+
| | | +---------+
| +------------+ | |
+----> | ########## |-----> +---------+
cr3 +------------+
+--------+ | |
| |--------------> +------------+
+--------+
當 Page 以 4K 為單位對齊的時候,記憶體中 Page Frame 大小為 4K ,此時,每一個 PageFrame 的地址的低 12 位均為0, Page Directory 和 Page Table 中的目錄項和頁表項都將低 12 位作為控制字,形如:
Byte/ 3 | 2 | 1 | 0 |
/ | | | |
|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|
+---------------+---------------+-------+-----+-+-+-+-+-+-+-+-+-+
| HIGH 20 bit | | |P| | |P|P| | | |
| Page Directory Entry Addr |AVAIL|G|S|D|A|C|W|U|W|P|
| Page Table Entry Addr | | | | | |D|T| | | |
+---------------------------------------+-----+++++++++++++++++++
| | | | | | | | |
| | | | | | | | +---> Present
| | | | | | | +-----> Writable
| | | | | | +-------> User/Supervisor Flag
| | | | | +---------> Write-through
| | | | +-----------> Cache disable
| | | +-------------> Accessed
| | +---------------> Dirty
| +-----------------> Page Size Flag
+-------------------> Global Flag
其中:
- Present flag
置位時候表示該項(目錄項或者頁表項)在主記憶體中;清零,則表示不在主記憶體中。如果在對某個目錄項或者頁表項進行地址類型轉換的時候發現該位為0, PAGING UNIT 會將線性地址存到 cr2 上,然後觸發 Page Fault 。
- Writable
表示該項是否可寫,為1時候可寫。
- User/Supervisor Flag
表明了訪問該 page 所需的許可權。
- PWT
緩衝區相關,表明是否 Write-through,後面會涉及到。
- PCT
設定為1 的時候,表示關閉 緩衝儲存空間。
- Accessed
表明該項已經被訪問過了。
- Dirty
僅用於頁面表項,表示該 Page Frame 被寫過。
- Page Size Flag
僅用於目錄項,當該值為 0 時,表示目錄項所指向的頁面表(Page Table) 中的頁面表項中的Frame 大小為 4K 。而當該值為1時,頁面表項的大小就變成了 4M 。
- Global Flag
僅用於頁面表項(Page Table Entry) ,為 1 時候,表明該頁面為全域頁面。
4.2 拓展分頁
從 Pentium 開始, Intel 引入了拓展分頁 (Extended Pageing) , 拓展分頁將頁大小改成了 4 M ,這種情況下,線性地址的前 10 位仍為目錄項,但是中間的 Page Table 被去掉了, OFFSET 從原來的 12 位變成了 22 位。如所示:
Linear Address
-------------+---------------+---------------+---------------+
Byte/ 3 | 2 | 1 | 0 |
/--------------+---------------+---------------+---------------+
/7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|
+-------------------------------+-------------------------------+
| Directory | OFFSET |
+--------+-------------------------+----------------------------+
| |
| | 4MB Page
| | +----------------+
| | | |
| | +----------------+
| | | |
| | +----------------+
| Page +------->| ############# |
| Directory +----------------+
| +------------+ | |
| | | +----------------+
| +------------+ | |
+----> | ########## |------------->+----------------+
cr3 +------------+
+--------+ | |
| |--------------> +------------+
+--------+
拓展分頁和前面的普通分頁的區別在於:
- 拓展分頁中,目錄項中的 PS 為 1 ,而普通分頁中,為 0 。
- 拓展分頁沒有頁面表,後面22位均為 offset 。
5 Linux 中的記憶體頁式管理
為了保持 Linux 的可移植性, Linux 裡面的頁式管理,被封了一層、一層,一層,一層,又一層。
Linux 核心中使用的虛擬位址(Virtual Address, Linear Address),出於可移植性的考慮,分成了四層,分別為:Page Global Directory, Page Upper Directory, Page Middle Directory, 和 Page Table 。這幾個層次再加上最後的一個頁內位移,共同構成了 Linux 的線性地址, 如所示:
|<------------------------------ BITS_PER_LONG --------------------------------->|+---------------+---------------+---------------+---------------+----------------+| PGD | PUD | PMD | PTE | Offset |+---------------+---------------+---------------+---------------+----------------+ | | | |<- PAGE_SHIFT ->| | | |<----------- PMD_SHIFT -------->| | |<---------------- PUD_SHIFT ------------------->| |<-------------------------- PGDIR_SHIFT ----------------------->|
The linear address space of a process is divided into two parts:
- 0x00000000 ~ 0xbfffffff:
核心空間和使用者空間的進程都可訪問。
- 0xc0000000 ~ 0xffffffff:
僅限於核心態進程訪問。
Author:yangyingchao, 2010-09-10
引用:http://blog.163.com/vic_kk/blog/static/4947052420108104224228/
Linux記憶體定址