經過前三章的努力,咱們成功將控制權轉交給了 loader.asm 這個程序。具體說就是 bios 經過加載並跳轉到 0x7c00(IMB大叔們定的) 把控制權轉交給了咱們操做系統的第一個彙編程序 mbr.asm,而後 mbr.asm 裏作的事就是經過加載 loader 程序並跳轉到 0x900(這個是咱們本身定的)把控制權轉交給了 loader.asm 程序,目前這個程序裏還只是向屏幕輸出一行字符串「loader」,今天咱們就將擴展它。而且今天咱們要作的事,是操做系統中的第一個精彩之處,就是從實模式跨越到保護模式。html
我這人喜歡直面問題,其實本章只須要搞明白三個主要問題就好了,什麼是實模式和保護模式,實模式與保護模式的區別是什麼,怎麼進入保護模式。我先來簡單闡述下這三個問題ios
Intel 8086 是一個由 Intel 於 1978 年所設計的 16 位微處理器芯片,是 x86 架構的鼻祖。緊接着 Intel 又推出了第一款 32 位的 cpu Intel 80286(很快被淘汰,80386更經典一些),這款 cpu 因爲和以前有不少不一樣的「保護」特性,因此稱爲保護模式,也是與此同時,以前的 8086 這個 16 位 cpu 纔有了實模式的叫法。git
因此什麼是實模式和保護模式,其實就是 Intel 給本身的處理器特性命的一個名字而已,具體有哪些特性那就是細節問題了,但最起碼有一點剛剛已經有所透露,那就是保護模式至少是 32 位的,而實模式是 16 位的(即便一個 32 位的 cpu 也有實模式)編程
進入保護模式有三步:數組
能夠看出進入保護模式的操做是很簡單的,但提早要作好準備工做,最重要的就是 gdt(Global Descriptor Table 全局描述表)的準備。數據結構
section loader vstart=0x900 jmp protect_mode gdt: ;0描述符 dd 0x00000000 dd 0x00000000 ;1描述符(4GB代碼段描述符) dd 0x0000ffff dd 0x00cf9800 ;2描述符(4GB數據段描述符) dd 0x0000ffff dd 0x00cf9200 ;3描述符(28Kb的視頻段描述符) dd 0x80000007 dd 0x00c0900b lgdt_value: dw $-gdt-1 ;高16位表示表的最後一個字節的偏移(表的大小-1) dd gdt ;低32位表示起始位置(GDT的物理地址) SELECTOR_CODE equ 0x0001<<3 SELECTOR_DATA equ 0x0002<<3 SELECTOR_VIDEO equ 0x0003<<3 protect_mode: ;進入32位 lgdt [lgdt_value] in al,0x92 or al,0000_0010b out 0x92,al cli mov eax,cr0 or eax,1 mov cr0,eax jmp dword SELECTOR_CODE:main [bits 32] ;正式進入32位 main: mov ax,SELECTOR_DATA mov ds,ax mov es,ax mov ss,ax mov esp,LOADER_STACK_TOP mov ax,SELECTOR_VIDEO mov gs,ax mov byte [gs:0xa0],'3' mov byte [gs:0xa2],'2' mov byte [gs:0xa4],'m' mov byte [gs:0xa6],'o' mov byte [gs:0xa8],'d' jmp $
這裏說說個人心得體會,如今看整段的代碼雖不能說每一行讓我本身寫能寫出來,但如今看起來極爲清晰。我如今其實已經想不起來當時爲何理解了很久很久就是理解不了,調試了好半天也總是有各類問題。不過這個代碼是我去掉了一些無關緊要影響理解的部分,只留下了最精華的部分,我不知道若是我一開始接觸的是這樣的代碼是否可以理解到位。架構
鳥瞰整段代碼,大概分爲三塊。學習
有件事如今說可能體會不大,寫到後面好多地方你會發現,像加載 gdt 這種操做模式好多地方都是通用的,咱先不用管 gdt 是什麼,總之 cpu 會有不少與操做系統相互打配合的地方,這個就是其中之一。配合怎麼打呢,那就是 cpu 定義好一個數據結構,再給你一個寄存器。操做系統通常負責作三件事情操作系統
而後就開啓了這個功能,段描述符表如此,頁表如此,TSS亦是如此,這個以後講到會深有體會。我如今已經有所體會了,但還沒整理出所有的這種打配合的地方,等我再深刻些再給你們整理一份。設計
直接上乾貨,還記不記得第一節課說的內容
在你開機的一瞬間,CPU 的 PC 寄存器被強制初始化爲 0xFFFF0。若是再說具體些,CPU 將段基址寄存器 cs 初始化爲 0xF000,將偏移地址寄存器 IP 初始化爲 0xFFF0,根據實模式下的最終地址計算規則,將段基址左移 4 位,加上偏移地址,獲得最終的物理地址也就是抽象出來的 PC 寄存器地址爲 0xFFFF0。
這種段基址左移 4 位,加上偏移地址,獲得物理地址的方式,就是實模式下的地址轉換方式。
然而保護模式下不同了
在保護模式下,段基址寄存器中存的數據,被理解爲段選擇子,根據這個值去咱們本身在內存中寫好的段描述符表中找,找到對應的段描述符,從中取出段基址。用這個段基址加上偏移地址,最終獲得物理地址(邏輯地址和頁表的事之後再說,不衝突)。
就這麼點區別
那天然就有兩個問題,一個是段描述符表長什麼樣子呀?決定了咱們往內存中寫的數據結構是什麼。另外一個就是去哪找段描述符表壓,這個就須要告訴 cpu 爲咱們提早預留好的寄存器,也就是 lgdt 指令。下面咱們就分別看着兩個問題
首先段描述符表是一張表,在內存中也就是個數組,是一個個的段描述符一個個緊挨着的結果。因此咱們要了解段描述符長什麼樣就行了
這裏我順便把選擇子和 GDTR 寄存器的結構也列出來了,這些就是所有的須要咱們本身寫數據的地方了,也是 cpu 和操做系統配合中須要約定的所有事情
;0描述符 dd 0x00000000 dd 0x00000000 ;1描述符(4GB代碼段描述符) dd 0x0000ffff dd 0x00cf9800 ;2描述符(4GB數據段描述符) dd 0x0000ffff dd 0x00cf9200 ;3描述符(28Kb的視頻段描述符) dd 0x80000007 dd 0x00c0900b
咱們看看這些直接在內存中寫死的常量,就是按照段描述符的數據結構寫的
代碼段描述符轉化爲二進制是 00000000_00000000_11111111_11111111_00000000_11001111_10011000_00000000
數據段描述符轉爲爲二進制是 00000000_00000000_11111111_11111111_00000000_11001111_10010010_00000000
視頻段描述符轉化爲二進制是 10000000_00000000_00000000_00000111_00000000_11000000_10010000_00000000
這裏咱們拿視頻段描述符來分析,提取(拼湊)出段基址的數據,00000000_00000000_10000000_00000000,轉換爲十六進制是 0x80000。怎麼樣熟不熟悉,這剛好是顯卡黑白模式在內存中的映射的起始地址。能夠看下第一章的內容,不過我這裏仍是把圖貼出來。
接下來的幾個常量定義,很容易明白它們的意思
lgdt_value: dw $-gdt-1 ;高16位表示表的最後一個字節的偏移(表的大小-1) dd gdt ;低32位表示起始位置(GDT的物理地址) SELECTOR_CODE equ 0x0001<<3 SELECTOR_DATA equ 0x0002<<3 SELECTOR_VIDEO equ 0x0003<<3
lgdt_value 就是按照 lgdt 寄存器規定的數據結構拼湊出來的,下面的三個常量其實就是對應上面定義的三個段描述符的偏移量,因爲每一個描述符佔 64 位,也就是佔 8 個地址單元,因此索引下標的計算就是第幾個描述符 * 8就行了,相信這個不難理解。
代碼直接對應上面的三步
加載 gdt
lgdt [lgdt_value]
打開 A20
in al,0x92 or al,0000_0010b out 0x92,al cli ;禁止中斷,先不用管
將 cr0 的 pe 位置 1
mov eax,cr0 or eax,1 mov cr0,eax
此時已經進入保護模式了,段基址寄存器的意義已經變了,因此跳轉指令變成了
jmp dword SELECTOR_CODE:main
前面就是將數據段寄存器賦值給一些段基址寄存器用於訪問數據段,而後將棧基址賦值位本次加載到的內存位置,重點是下面幾句
mov ax,SELECTOR_VIDEO mov gs,ax mov byte [gs:0xa0],'3' ...
這段將咱們剛剛寫好的常量 SELECTOR_VIDEO 寫入了段基址寄存器 gs,並在其後用了這個基址寄存器去進行 mov 操做。經過這個段選擇子,在段描述符表裏尋找出來的段基址是咱們寫好的顯卡的內存映射的起始地址,因此同前幾章在實模式下的輸出就同樣了。
咱們並無增長新文件,因此Makefile和上一篇同樣,不用變,直接運行看效果,make brun
能夠看到,咱們的段基址寄存器沒有直接寫顯卡的起始地址,而是經過段選擇子索引的,但依然正常輸出了 "32mod" 字符串,說明成功了
若是你對自制一個操做系統感興趣,不妨跟隨這個系列課程看下去,甚至加入咱們,一塊兒來開發。
《操做系統真相還原》這本書真的贊!強烈推薦
當你看到該文章時,代碼可能已經比文章中的又多寫了一些部分了。你能夠經過提交記錄歷史來查看歷史的代碼,我會慢慢梳理提交歷史以及項目說明文檔,爭取給每一課都準備一個可執行的代碼。固然文章中的代碼也是全的,採用複製粘貼的方式也是徹底能夠的。
若是你有興趣加入這個自制操做系統的大軍,也能夠在留言區留下您的聯繫方式,或者在 gitee 私信我您的聯繫方式。
本課程打算出系列課程,我寫到哪以爲能夠寫成一篇文章了就寫出來分享給你們,最終會完成一個功能全面的操做系統,我以爲這是最好的學習操做系統的方式了。因此中間遇到的各類坎也會寫進去,若是你能持續跟進,跟着我一塊寫,必然會有很好的收貨。即便沒有,交個朋友也是好的哈哈。
目前的系列包括