從零開始寫一個操作系統內核 筆記(四) 番外篇 8086CPU 之分頁機制

一. 邏輯地址和線性地址

下面是一條普通的 eax賦值指令
> mov 0x80495b0, %eax
在這裏插入圖片描述
首先 這條指令 真正會被執行成 mov ds:0x80495b0, %eax
在保護模式下,ds 是個段選擇子,通過 GDT在 表 找到對應的 線性起始地址 再 加上 偏移地址 計算出 線性地址,在 保護模式未開啓分頁的情況下 線性地址就直接對應到物理地址,如果在 開啓了分頁機制 通過分頁映射到一個物理地址。

CPU 分頁機制

在這裏插入圖片描述
頁內部連續的線性地址映射到連續的物理地址中

32位 下 分頁大小爲 4Kb.CPU分頁 要藉助 頁表,頁表 存放在內存中

保護模式下,控制寄存器CR0的最高位PG位控制分頁的開啓 PG =1 此時通過頁表才能把線性地址轉換爲物理地址,

PG = 0 分頁機制無效,線性地址就直接作爲 物理地址。

每個任務可以有自己的頁目錄表和頁表

爲了節約內,8086 CPU 的設計者將線性地址通過頁目錄表和頁表兩級查找轉換成物理地址。
  1. 32模式下線性地址通過3個部分組成:

    • 最高10位 Directory 頁目錄表偏移量

      • 頁目錄表的大小爲4KB(32位模式下),記錄一個地址需要4K在32位模式下,如果 內存被拆成 4KB 爲最小單位,那麼 4G = 2 的 20次方 * 4KB = 220102442^{20} * 1024 * 4 如果頁表是一個大數組,每個頁數組記錄連續4KB的起始位置,那麼 每個地址32位地址棧4個字節 需要22042^{20} * 4 = 4M的大小存放,Intel覺得這樣太耗費空間了,將其拆分成 2個表 通過計算編程一個線性地址。

        下面來個直觀的例子:
        image-20200322163417627

    • 中間10位 Table 頁表偏移量

    • 最後12位Ofsset是屋裏也內側字節偏移量。
      在這裏插入圖片描述
      總結 : 1個一目錄佔 4個字節,每條記錄 棧4個字節 32 位 其中只有 前10位表示表示頁表的起始地址一個進程都會有自己獨立的頁目錄,一個頁目錄對應多個 頁表,通過線性地址的 前 10位找到頁目錄的起始索引 -> 通過線性地址的後 10位 取得頁表的索引,通過頁表的索引的20bit 索引到物理頁的 地址 -> 線性地址的最後12位爲 4K內的偏移
      在物理頁內找到的 20位 + 線性地址最後12位 這樣就能索引到2322^{32} 4G的內存。
      值得注意的是,頁表不需要一開始就分配,當CPU 執行程序 查找頁表時沒分配時觸發系統異常中斷,有操作系統處理再行分配,同時頁表可以放在內存不連續處。
      f(:)=f_{段映射}(選擇子:偏移)=線性地址
      f()=f_{頁映射}(線性地址)=物理地址

一、虛擬地址、物理地址、邏輯地址概述

物理地址(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

推薦文章 : https://www.cnblogs.com/alantu2018/p/9002441.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章