1. 程式人生 > >【自制作業系統05】開啟記憶體分頁機制

【自制作業系統05】開啟記憶體分頁機制

通過前四章的努力,我們成功將控制權轉交給了 loader.asm 這個程式,並且從真實模式跨越到了保護模式。第四章講保護模式的時候我說過,這是我們作業系統的第一個精彩之處。但其實這只是針對之前我們進行的只是無意義的輸出,以及硬碟的載入等工作。但到了這一章,之前一步步的努力進入到了保護模式,也只能說是做了很多苦力,其實很多程式碼都是固定的,給我們發揮的空間也不大。

但是到了本章,可以說終於有能體現出我們設計能力的地方了。

一、實現分頁要做哪些事

還是先直接簡單說要做的事,再說為什麼,實現分頁要做以下三件事:

  1. 在記憶體某位置寫好頁表
  2. 頁目錄地址賦值給 cr3 暫存器
  3. 將 cr0 暫存器的 pg 位置 1

我們對比下進入保護模式中實現段描述符機制需要做的三件事:

  1. 在記憶體某位置寫好段描述符表
  2. 段描述符表地址賦值給 gdtr 暫存器
  3. 將 cr0 暫存器的 pe 位置 1(這個其實是開啟保護模式)

你看,是否是非常相似呢?都是記憶體某位置準備xxx,把起始地址賦值給一個特定的暫存器,然後將另一個特殊暫存器的某位置 1 表示開啟。所以上一章我說過,cpu 與操作系體打配合,這種模式運用得非常多。我們寫作業系統的人不用管 cpu 的具體實現,只需要按照指定步驟操作即可,之後硬體會幫我們完成所需要的功能。

二、為什麼要分頁

說實話我也想不明白為什麼要分頁,主要是我說不上來為什麼不是其他方式,所以這塊我也只能跟著官方說的去理解了。

如果只用段式管理的話,段大小不一致,且同一個程式邏輯地址和實體地址都是連續的。段大小不一致導致記憶體有大段有小段,也會留下一些記憶體碎片,過大的段查不進來,過小的段插進去又會產生更小的碎片。同一個段內所有的程式地址都是連續的,這也導致不靈活,我們希望能有一套機制使得程式所用的邏輯地址連續,但實際對映到的實體地址並不連續,增加這麼一個層來解決這個問題。

我們本講只是準備一些必要的頁表,然後開啟頁表機制。等到後面多工的時候才能真正體會到頁表的用處以及好處,所以我們姑且先簡單理解下,至於具體的好處,其實有好多細節的,等以後用到的時候慢慢體會。

三、頁表長什麼樣以及虛擬地址到實體地址的轉換

我們可以類比段的轉化,我們最初給的地址是 段選擇子:段內偏移值,在保護模式下,用段選擇子去記憶體中的段描述符表中,找到段描述符,取出段基址,再+段內偏移地址,得到最終的實體地址。

頁的轉化也是類似的,上一步通過段描述符得到的“實體地址”,再開啟分頁後叫做邏輯地址。這個邏輯地址也是分成 前半部分:後半部分 這種形式,用前半部分的值在頁表中尋找並換出一個頁地址(也可以理解成基址這個概念),然後再拼接上後半部分的值,得到最終的實體地址。

只不過,現在的頁表方案一般是二級頁表,第一級叫頁目錄表(PDE),第二級叫頁表(PTE)。然後這個邏輯地址就是被看成 高10位:中間10位:後12位。高10位負責再頁目錄表中找到一個頁目錄項,這個頁目錄項的值加上中間10位拼接後的地址去頁表中去尋找一個頁表項,這個頁表項的值,再加上後12位,拼接後的地址就是最終的實體地址。

12位可以表示 4K,所以也就是一個頁可表示的記憶體大小為 4KB。10位可以表示 1K,所以頁目錄表中最多有 1024 個頁目錄項,一個頁表中最多有 1024 個頁表項,那最大可表示的記憶體範圍就是 1024 * 1024 * 4KB = 4G。其實這也是廢話,你可以仔細想想看,不論你分成幾級頁表,只要是通過這種方式定址的,只要是一個 32 位的地址,總是可以表示 4G 大小的。只不過通過你的不同分法,可能導致頁大小,頁目錄項數目,頁表數目,以及假如你定了 n 級頁表後的 n 級頁表的頁表項數目不同而已。

頁目錄表和頁表的資料結構

虛擬地址到實體地址的轉換

四、頁表設計

我們這樣設計頁表:

  • 頁目錄表的第 0 項和第 768 項,都對應緊接著的第一個頁表,映射了低端 1M 的實體記憶體(0x00000-0x100000),也就是說邏輯地址的開端 1M 和 3G 以上的第一個 1M 地址,都對應這實體記憶體的地段 1M。
  • 頁目錄表的第 769~1022 項,分別往後對應 254 個頁表,不過這些頁表還沒有寫,先空著
  • 頁目錄表的第 1023 項,其地址指向該頁目錄表本身(也就是把頁目錄表當作頁表去理解了),通過這種方式可以訪問頁目錄表本身。(這塊其實我也沒理解為啥要這麼搞,無非就是想用虛擬地址訪問到這個頁表本身嘛。

為什麼這樣設計呢?

因為我們分頁之前的程式碼(loader)都在低端 1MB 範圍內,所以開啟分頁之後的邏輯地址開始的 1M 也要一一對應上實體地址的開始 1M,所以有了第 0 個頁目錄項。第 768 個頁目錄項對應著邏輯地址 3G 以上的 4M( 0xc0000000~0xc03fffff 不過我們頁表只寫了 256 項也就是規劃了 1M),這是因為我們決定將作業系統核心寫在 3G 以上的 1M 空間裡。

我們規劃,虛擬地址的 0~3G 是使用者空間,3~4G 是核心空間,所以我們提前把頁目錄表的第 769~1022 項建好,至於為什麼以後再說。

五、上程式碼

loader.asm

...
;建立頁表並初始化(頁目錄和頁表)
PAGE_DIR_TABLE_POS equ 0x100000
call setup_page

;重新載入 gdt,因為已經變成了虛擬地址方式
sgdt [lgdt_value]
mov ebx,[lgdt_value+2]
or dword [ebx+0x18+4],0xc0000000
add dword [lgdt_value+2],0xc0000000
add esp,0xc0000000

;頁目錄表起始地址存入 cr3 暫存器
mov eax,PAGE_DIR_TABLE_POS
mov cr3,eax

;開啟分頁
mov eax,cr0
or eax,0x80000000
mov cr0,eax

;重新載入 gdt
lgdt [lgdt_value]

mov byte [gs:0x1e0],'p'
mov byte [gs:0x1e2],'a'
mov byte [gs:0x1e4],'g'
mov byte [gs:0x1e6],'e'
mov byte [gs:0x1ea],'o'
mov byte [gs:0x1ec],'n'

jmp $

setup_page:
;先把頁目錄佔用的空間逐字清零
    mov ecx,4096
    mov esi,0
.clear_page_dir:
    mov byte [PAGE_DIR_TABLE_POS+esi],0
    inc esi
    loop .clear_page_dir
    
;開始建立頁目錄項(PDE)
.create_pde:
    mov eax,PAGE_DIR_TABLE_POS
    add eax,0x1000; 此時eax為第一個頁表的位置及屬性
    mov ebx,eax
    or eax,111b
    mov [PAGE_DIR_TABLE_POS],eax
    mov [PAGE_DIR_TABLE_POS+0xc00],eax
    sub eax,0x1000
    mov [PAGE_DIR_TABLE_POS+4*1023],eax

;開始建立頁表項(PTE)
    mov ecx,256
    mov esi,0
    mov edx,111b
.create_pte:
    mov [ebx+esi*4],edx
    add edx,4096
    inc esi
    loop .create_pte
    
;建立核心其他頁表的頁目錄項(PDE)
    mov eax,PAGE_DIR_TABLE_POS
    add eax,0x2000
    or eax,111b
    mov ebx,PAGE_DIR_TABLE_POS
    mov ecx,254
    mov esi,769
.create_kernel_pde:
    mov [ebx+esi*4],eax
    inc esi
    add eax,0x1000
    loop .create_kernel_pde
    ret
...

六、執行

Makefile 仍然和上一章一樣,所以直接執行 make brun

可以看到分頁開啟後,成功在螢幕輸出了 pageon 字串

七、學到這的一些感悟

我之前寫過一篇文章 究竟什麼是技術,還被好多人罵了。我文章裡說的就是感覺現在做的事情(Java),以及好多好多所謂的技術,都只是應用而已,甚至覺得只有基礎科學,只有研究質子中子電子,這些東西才算是真正的技術,其他的只是應用而已。

不過現在我知道自己的問題所在了,因為我研究作業系統就是想做點真正的技術。但現在看來,如果還延續當時的想法,像開啟分頁,進入保護模式,往顯示卡對映的記憶體寫資料,這些都應該只叫做應用。因為這些的底層原理是 cpu 硬體電路的佈線方式,我們的作業系統只是應用了它們,把一些操作封裝起來再暴露給使用者而已。

但如果真這樣深入下去,其實是沒完沒了的,你的求知慾又會深入到物理層面,這其實跟計算機技術已經相差甚遠了。所以我現在覺得,把底層細節當作已知,在這上面建立一套完善的體系,這本身就是這一層的技術了,每一層有每一層技術的複雜性,不能說越底層的才越接近技術,越接近真理。

所以,你可以不斷深入探索底層的技術,但大可不必對自己所研究層次的知識妄自菲薄。

寫在最後:開源專案和課程規劃

如果你對自制一個作業系統感興趣,不妨跟隨這個系列課程看下去,甚至加入我們,一起來開發。

參考書籍

《作業系統真相還原》這本書真的贊!強烈推薦

專案開源

專案開源地址:https://gitee.com/sunym1993/flashos

當你看到該文章時,程式碼可能已經比文章中的又多寫了一些部分了。你可以通過提交記錄歷史來檢視歷史的程式碼,我會慢慢梳理提交歷史以及專案說明文件,爭取給每一課都準備一個可執行的程式碼。當然文章中的程式碼也是全的,採用複製貼上的方式也是完全可以的。

如果你有興趣加入這個自制作業系統的大軍,也可以在留言區留下您的聯絡方式,或者在 gitee 私信我您的聯絡方式。

課程規劃

本課程打算出系列課程,我寫到哪覺得可以寫成一篇文章了就寫出來分享給大家,最終會完成一個功能全面的作業系統,我覺得這是最好的學習作業系統的方式了。所以中間遇到的各種坎也會寫進去,如果你能持續跟進,跟著我一塊寫,必然會有很好的收貨。即使沒有,交個朋友也是好的哈哈。

目前的系列包括

  • 【自制作業系統01】硬核講解計算機的啟動過程
  • 【自制作業系統02】環境準備與啟動區實現
  • 【自制作業系統03】讀取硬碟中的資料
  • 【自制作業系統04】從真實模式到保護模式