摘要:本文主要為你講解linux中的分段和分頁機制的實現原理,相關的宏定義和函數功能。
本文來源:記憶體定址(二):linux中的分段與分頁機制 1.linux中的分段
除了用來類比80286的模式以外,段式基地址總是0(也就是說linux並沒有真實地實現分段機制),所以線性地址和虛擬位址總是一樣的。運行在所有使用者態的linux進程都使用一對相同的段進行資料和指令的定址,它們就是使用者資料區段和程式碼片段;同理,存在核心資料區段和程式碼片段。這四個重要的段描述符的值是:
Segment |
Base |
G |
Limit |
S |
Type |
DPL |
D/B |
P |
user code |
0x00000000 |
1 |
0xfffff |
1 |
10 |
3 |
1 |
1 |
user data |
0x00000000 |
1 |
0xfffff |
1 |
2 |
3 |
1 |
1 |
kernel code |
0x00000000 |
1 |
0xfffff |
1 |
10 |
0 |
1 |
1 |
kernel data |
0x00000000 |
1 |
0xfffff |
1 |
2 |
0 |
1 |
1 |
這樣做的結果是,當對指令或者資料指標進行儲存的時候,核心不需要為它設定邏輯地址的段選擇符,因為cs寄存器含有當前的段選擇符號。上面四個段的段選擇符號,分別用宏定義表示:__USER_CS,__USER_DS,__KERNEL_CS,__KERNEL_DS。當發生核心切換或者相應的地址訪問時,只需要把對應宏定義的數值裝入cs中即可。
1.1 linux GDT
一個CPU對應一個GDT,多有的GDT都放在cpu_gdt_table數組之中,所有GDT的地址和它們的大小都存放在cpu_gdt_descr數組之中。
GDT的布局如下,其中每個GDT包含18個段描述符和14個空的(為了利用cache局部性原理)。
18個段描述符大致如下:
* 使用者態和和心態代碼和資料區段共4個
* 任務狀態段(TSS),每個處理器1個
* 等
1.2 linux LDT
大多數linux使用者太程式不使用局部描述符表,核心定義了一個預設的LDT,放在default_ldt數組中。
某些進行在有時候需要建立自己的局部描述符表,它通過系統調用modify_ldt系統調用來實現。當處理器開始執行擁有自訂ldt表的進程時,該cpuGDT副本中的LDT表項相應的就被修改了。
2.linux中的分頁機制
最新linux分頁模式(2.6kernel以後)
linux採用了一種同時適用於32位和64位的普通分頁模型。2.6.10之前,linux一直採用三級分頁模型,2.6.11以後,linux採用四級分頁模型,如上圖,其中每個部分的大小和具體的電腦體繫結構有關。
對於沒有啟用PAE的系統,linux通過使得PUD和PMD的位都是0,雖然他們在地址中並不佔有一定的位置,但是核心還是保留了它們的指標和表項。這樣它們含有的項的數量為一,而且都映射到頁全域目錄的一個適當的目錄項目。
PAE禁用時,offset是12b,table是10b,pgd是10b;PAE被啟用,table是9b,PMD是9b,PUD是0b,PGD是2b.
2.1線性地址欄位
與頁表處理相關的宏:
PAGE_SHIFT:
OFFSET 欄位的位元,4k頁對應12位,PAGE_MASK是oxfffff000,用於屏蔽相應欄位
PMD_SHIFT:
offset+table的位元,PMD_SIZE用於計算頁中間目錄的一個單獨表項所映射地區的大小,也就是一個頁表的大小,PMD_MASK用於屏蔽PMD_SHIFT對應欄位。注意在i386架構中,PAE啟用與否對這個宏的值有影響,啟用以後它的值變為21.大型頁往往不使用最後一級頁表,所以大型頁的LARGE_PAGE_SIZE=PMD_SIZE, LARGE_PAGE_MASK=PMD_MASK.
PUD_SHIFT:
頁上級目錄所能映射地區的大小的對數。PUD_SIZE用於計算頁全域目錄中一個單獨表項所映射地區的大小,PUD_MASK用於屏蔽offset欄位、table欄位、middle欄位和upper dir欄位。因為upper dir恒為0位,所以在8086上,PUD_SHIFT總是等於PMD_SHIFT,而PUD_SIZE則等於4MB或者2MB。
PGDIR_SHIFT:
頁全域目錄中一個單獨表項所能映射地區的大小。具體的機理可以參考實體位址延伸(PAE)分頁機制,PAE的開啟與否對這個宏的結果有影響,PAE開啟的時候,它的值是12+9+9=30位,禁止的時候,它的值是12+10=22位。
PTRS_PER_PTE,PTRS_PER_PMD, PTRS_PER_PUD, PTRS_PER_PGD:
PAE禁止,分別為1024,1,1,1024;PAE啟用,分別為512,512,1,4。其中,PTRS_PER_PUD這一項的值恒是1.
2.2頁表處理
核心定義了很多函數和宏來:描述頁表項,修改頁表項,讀取頁表項;讀取頁標誌,設定頁標誌等;另外還有一些用來對頁進行分配的函數和宏定義。
2.3實體記憶體布局
在初始化階段,核心必須建立一個物理地址映射來指明哪些物理位址範圍對於核心可用而哪些不可用。
核心將以下也況設定位保留:
* 停用物理位址範圍內的頁框
* 含有核心代碼和已經初始化資料結構的頁框
由於要給bios和其他程式預留地址空間,核心無法安裝在記憶體的第一個MB(為了保持核心資料在記憶體在連續性),linux 2.6和前768個頁框(3M)布局如下:
---------------------------------------------------------------------------------------
| **** | |********| | | | |
-----------------------------------------------------------------------------------------
0 1 0x9f 0x100 0x2ff
_text _etext _edata _end
其中,從左分別是:
停用頁框
可用的頁框
停用頁框
核心代碼
已經初始化的核心資料
沒有初始化的核心資料
可用頁框
2.4進程頁表
注意:進程頁表分為使用者空間和系統空間,存取權限不同,使用0xc0000000作為分界線。
2.5核心頁表
核心如何初始化自己的頁表:建立有限的一共128K的地址空間+利用剩餘的空間來建立頁表。
1)臨時核心頁表
假設核心使用的段、臨時頁表和128KB的記憶體範圍能夠容納在前8M空間,這需要2個頁來映射。分頁的第一階段任務是在實模式和保護模式下能對這8M空間進行定址。
2)RAM小於896M時候的最終核心頁表
由核心頁表所提供的最終映射必須把從0xc0000000開始的線性地址映射到從0開始的物理地址。其中宏__pa和__va分別進行相應地區的物理地址和線性地址之間的轉換。
3)當RAM在896M和4096M之間的最終核心頁表
4)當RAM大於4096MB時候的最終核心頁表
此時,線性地址只有1G和RAM大於1G,此處的映射就可能涉及到PAE和高端記憶體,詳細可以參考高端記憶體。
2.6固定映射的線性地址
用於映射RAM的0~896M空間。
2.7cache和LTB
資料結構中最常使用的部分放在靠前的位置,同時盡量相鄰,從而利用cache line。
cr3的更新往往也意味著TLB的重新整理,下列情況除外:
1)兩個共用頁表集合的進程之間切換。
2)普通進程和核心進程之間切換。
另外,只要正在啟動並執行進程頁表集合發生變化,同樣需要更新TLB。對於多處理器系統,還涉及到懶惰TLB策略。