通過前四章的努力,我們成功將控制權轉交給了 loader.asm 這個程序,並且從實模式跨越到了保護模式。第四章講保護模式的時候我說過,這是我們操作系統的第一個精彩之處。但其實這只是針對之前我們進行的只是無意義的輸出,以及硬盤的加載等工作。但到了這一章,之前一步步的努力進入到了保護模式,也只能說是做了很多苦力,其實很多代碼都是固定的,給我們發揮的空間也不大。
但是到了本章,可以說終於有能體現出我們設計能力的地方了。
一、實現分頁要做哪些事
還是先直接簡單說要做的事,再說為什么,實現分頁要做以下三件事:
- 在內存某位置寫好頁表
- 頁目錄地址賦值給 cr3 寄存器
- 將 cr0 寄存器的 pg 位置 1
我們對比下進入保護模式中實現段描述符機制需要做的三件事:
- 在內存某位置寫好段描述符表
- 段描述符表地址賦值給 gdtr 寄存器
- 將 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 硬件電路的布線方式,我們的操作系統只是應用了它們,把一些操作封裝起來再暴露給用戶而已。
但如果真這樣深入下去,其實是沒完沒了的,你的求知欲又會深入到物理層面,這其實跟計算機技術已經相差甚遠了。所以我現在覺得,把底層細節當作已知,在這上面建立一套完善的體系,這本身就是這一層的技術了,每一層有每一層技術的復雜性,不能說越底層的才越接近技術,越接近真理。
所以,你可以不斷深入探索底層的技術,但大可不必對自己所研究層次的知識妄自菲薄。
寫在最后:開源項目和課程規划
如果你對自制一個操作系統感興趣,不妨跟隨這個系列課程看下去,甚至加入我們(下方有公眾號和小助手微信),一起來開發。
參考書籍
《操作系統真相還原》這本書真的贊!強烈推薦
項目開源
當你看到該文章時,代碼可能已經比文章中的又多寫了一些部分了。你可以通過提交記錄歷史來查看歷史的代碼,我會慢慢梳理提交歷史以及項目說明文檔,爭取給每一課都准備一個可執行的代碼。當然文章中的代碼也是全的,采用復制粘貼的方式也是完全可以的。
如果你有興趣加入這個自制操作系統的大軍,也可以在留言區留下您的聯系方式,或者在 gitee 私信我您的聯系方式。
課程規划
本課程打算出系列課程,我寫到哪覺得可以寫成一篇文章了就寫出來分享給大家,最終會完成一個功能全面的操作系統,我覺得這是最好的學習操作系統的方式了。所以中間遇到的各種坎也會寫進去,如果你能持續跟進,跟着我一塊寫,必然會有很好的收貨。即使沒有,交個朋友也是好的哈哈。
目前的系列包括
