一. 邏輯地址和線性地址
下面是一條普通的 eax賦值指令
> mov 0x80495b0, %eax
首先 這條指令 真正會被執行成 mov ds:0x80495b0, %eax
在保護模式下,ds 是個段選擇子,通過 GDT在 表 找到對應的 線性起始地址 再 加上 偏移地址 計算出 線性地址,在 保護模式未開啓分頁的情況下 線性地址就直接對應到物理地址,如果在 開啓了分頁機制 通過分頁映射到一個物理地址。
CPU 分頁機制
頁內部連續的線性地址映射到連續的物理地址中
32位 下 分頁大小爲 4Kb.CPU分頁 要藉助 頁表,頁表 存放在內存中
保護模式下,控制寄存器CR0的最高位PG位控制分頁的開啓 PG =1 此時通過頁表才能把線性地址轉換爲物理地址,
PG = 0 分頁機制無效,線性地址就直接作爲 物理地址。
每個任務可以有自己的頁目錄表和頁表
爲了節約內,8086 CPU 的設計者將線性地址通過頁目錄表和頁表兩級查找轉換成物理地址。
-
32模式下線性地址通過3個部分組成:
-
最高10位 Directory 頁目錄表偏移量
-
頁目錄表的大小爲4KB(32位模式下),記錄一個地址需要4K在32位模式下,如果 內存被拆成 4KB 爲最小單位,那麼 4G = 2 的 20次方 * 4KB = 如果頁表是一個大數組,每個頁數組記錄連續4KB的起始位置,那麼 每個地址32位地址棧4個字節 需要 = 4M的大小存放,Intel覺得這樣太耗費空間了,將其拆分成 2個表 通過計算編程一個線性地址。
下面來個直觀的例子:
-
-
中間10位 Table 頁表偏移量
-
最後12位Ofsset是屋裏也內側字節偏移量。
總結 : 1個一目錄佔 4個字節,每條記錄 棧4個字節 32 位 其中只有 前10位表示表示頁表的起始地址一個進程都會有自己獨立的頁目錄,一個頁目錄對應多個 頁表,通過線性地址的 前 10位找到頁目錄的起始索引 -> 通過線性地址的後 10位 取得頁表的索引,通過頁表的索引的20bit 索引到物理頁的 地址 -> 線性地址的最後12位爲 4K內的偏移
在物理頁內找到的 20位 + 線性地址最後12位 這樣就能索引到 4G的內存。
值得注意的是,頁表不需要一開始就分配,當CPU 執行程序 查找頁表時沒分配時觸發系統異常中斷,有操作系統處理再行分配,同時頁表可以放在內存不連續處。
-
一、虛擬地址、物理地址、邏輯地址概述
物理地址(physical address)
用於內存芯片級的單元尋址,與處理器和CPU連接的地址總線相對應。
——這個概念應該是這幾個概念中最好理解的一個,但是值得一提的是,雖然可以直接把物理地址理解成插在機器上那根內存本身,把內存看成一個從0字節一直到最大空量逐字節的編號的大數組,然後把這個數組叫做物理地址,但是事實上,這只是一個硬件提供給軟件的抽像,內存的尋址方式並不是這樣。所以,說它是“與地址總線相對應”,是更貼切一些,不過拋開對物理內存尋址方式的考慮,直接把物理地址與物理的內存一一對應,也是可以接受的。也許錯誤的理解更利於形而上的抽像。
虛擬內存(virtual memory)
這是對整個內存(不要與機器上插那條對上號)的抽像描述。它是相對於物理內存來講的,可以直接理解成“不直實的”,“假的”內存,例如,一個0x08000000內存地址,它並不對就物理地址上那個大數組中0x08000000 - 1那個地址元素;
之所以是這樣,是因爲現代操作系統都提供了一種內存管理的抽像,即虛擬內存(virtual memory)。進程使用虛擬內存中的地址,由操作系統協助相關硬件,把它“轉換”成真正的物理地址。這個“轉換”,是所有問題討論的關鍵。
有了這樣的抽像,一個程序,就可以使用比真實物理地址大得多的地址空間。(拆東牆,補西牆,銀行也是這樣子做的),甚至多個進程可以使用相同的地址。不奇怪,因爲轉換後的物理地址並非相同的。
——可以把連接後的程序反編譯看一下,發現連接器已經爲程序分配了一個地址,例如,要調用某個函數A,代碼不是call A,而是call 0x0811111111 ,也就是說,函數A的地址已經被定下來了。沒有這樣的“轉換”,沒有虛擬地址的概念,這樣做是根本行不通的。
打住了,這個問題再說下去,就收不住了。
邏輯地址(logical address)
Intel爲了兼容,將遠古時代的段式內存管理方式保留了下來。邏輯地址指的是機器語言指令中,用來指定一個操作數或者是一條指令的地址。以上例,我們說的連接器爲A分配的0x08111111這個地址就是邏輯地址。
——不過不好意思,這樣說,好像又違背了Intel中段式管理中,對邏輯地址要求,“一個邏輯地址,是由一個段標識符加上一個指定段內相對地址的偏移量,表示爲 [段標識符:段內偏移量],也就是說,上例中那個0x08111111,應該表示爲[A的代碼段標識符: 0x08111111],這樣,才完整一些”
32位保護模式下 開啓分頁:
;========================================;
; 開啓分頁機制
; 加載頁到1M以上空間,頁目錄地址:0x100000 1M處
; 頁表開始位置:0x101000 1M + 4KB(一個頁目錄)處
;
;========================================;
SwitchPage:
xor edx,edx ;除法初始化edx寄存器
mov eax,[ddMemSize] ;獲取內存大小
mov ebx,0x400000 ; 一個頁表 有1024 項 每一項 表示4KB 所以 是 1024 * 4KB = 4MB 一個頁表能表示4MB大小
div ebx ;計算 你當前擁有內存需要幾個頁表 才能表示 內存大小/每頁頁表所能表示的大小
mov ecx,eax ;ecx 表示 所需頁表PDE的格式
test edx,edx ;如果 不能整除 就要再添加一個頁表
jz .no_remainder;如果 能整除
inc ecx ;多加一個 表
.no_remainder:
push ecx
;首先初始化頁目錄
mov ax,SelectorData
mov es,ax
mov edi,PDE_ORIGIN_ADDRESS ;頁目錄起始位置 頁目錄佔4K
xor eax,eax
;分頁的 地址 和屬性 佔 32字節 前20字節指向物理頁的前10位頁表起始位置 前 20位 頁存在屬性 | U/S 屬性位值,用戶級 | R/W 屬性位值,讀/寫/執行
mov eax,PTE_ORIGIN_ADDRESS | PG_P | PG_US_U | PG_RW_W
.SetupPDE:
stosd
add eax,0x1000 ;每個頁表地址 相差4K 4096個字節
loop .SetupPDE ;循環 直到ecx爲0
pop eax ;取出頁表個數
mov ebx,0x400;每個頁表可以存放 1024 個PTE
mul ebx ;頁表個數 * 1024 得到需要多少個PTD
mov ecx,ebx ;eax = PTE個數,用於循環
mov edi,PTE_ORIGIN_ADDRESS ;頁表首地址
xor eax,eax
;頁表從0 開始
mov eax,PG_P | PG_US_U | PG_RW_W
.SetupPTE:
stosd ;將ds:eax -> ds:edi
add eax,0x1000 ;頁指向 物理內存 從 0 ,4K ,8K ....;
loop .SetupPTE
;設置CR3 寄存器 和CR0 開啓分頁機制
mov eax,PDE_ORIGIN_ADDRESS
mov cr3,eax
mov eax,cr0;將CR0 PF位置位
or eax,0x80000000
mov cr0,eax
jmp short .SetupWating
;給CPU 一點延遲 讓分頁機制 生效
.SetupWating:
nop
nop
ret