上一節我們已經掌握了進入保護模式的關鍵,這一節我們來具體實現他。
我們之前向屏幕輸出都是通過設置中斷實現,比較麻煩,這次我們換個方法,向顯存輸出信息,顯存地址(CGA)是0xB8000~0xBFFFF,我們只需要用其中一段就行,顯示功能不會用太多。使用方法很簡單,兩個顯存地址分別存儲字符和字符的前景,寫入後就可以看到效果了:
<code> 1[bits 16] 2;後面保護模式的代碼就是32位了,先寫著 3org 0x70000 4 5START: 6 ;這次一定要初始化,設置堆棧段和棧指針 7 mov ax,cs 8 mov ds,ax 9 mov es,ax10 mov ss,ax11 mov sp,01213point:14 ;清屏是個好習慣15 mov ax, 0x216 int 0x101718 mov ax, 0xb80019 mov es, ax20 mov di, 021 mov byte [es: di + 0], 'L'22 mov byte [es: di + 1], 0xcf23 mov byte [es: di + 2], 'o'24 mov byte [es: di + 3], 0xcf25 mov byte [es: di + 4], 'a'26 mov byte [es: di + 5], 0xcf27 mov byte [es: di + 6], 'd'28 mov byte [es: di + 7], 0xcf29 mov byte [es: di + 8], 'i'30 mov byte [es: di + 9], 0xcf31 mov byte [es: di + 10], 'n'32 mov byte [es: di + 11], 0xcf33 mov byte [es: di + 12], 'g'34 mov byte [es: di + 13], 0xcf/<code>
截圖太麻煩了,我拿我的人格擔保,一定可以顯示出Loading,效果和之前是一樣的。只是有人會發現,這樣打印字符串太不舒服了。是的,有更好的辦法,但是現在要先解決保護模式,保護模式進入也需要通過字符輸出來確認是否成功,但是進入保護模式後,我們就不能使用BIOS中斷來輸出字符了,所以這裡先簡單介紹操作顯存。
我們現在來初始化GDT並加載他,根據上一節初探保護模式,我們得知,我們需要填寫表相應位的值來初始化,填寫完後還要用GDTR加載:
<code> 1load_GDTR: 2 ;保存GDT地址到GDTR 3 lgdt[gdt] 4 5gdt_head: 6 dd 0x0000000 7 dd 0x0000000 8 9 dw 0x000ffff ;段限制:0-15位10 dw 0x0000000 ;段基地址:0-15位11 db 0x0000000 ;段基地址:16-23位12 db 10011010b ;段描述符的第6字節屬性(代碼段可讀寫)13 db 11001111b ;段描述符的第7字節屬性:16-19位14 db 0x0000000 ;段描述符的最後一個字節是段基地址的第二部分:24-31位1516 dw 0x000ffff ;段限制:0-15位17 dw 0x0000000 ;段基地址:0-15位18 db 0x0000000 ;段基地址:16-23位19 db 10010010b ;段描述符的第6字節屬性(數據段可讀寫)20 db 11001111b ;段描述符的第7字節屬性:limit(位16-19)21 db 0x0000000 ;段描述符的最後一個字節是段基地址的第二部分:24-31位2223gdt:24 dw gdt - gdt_head - 125 dd gdt_head/<code>
數字後綴b表示二進制。關於GDT的填寫,註釋已經寫得很詳細了,可以參考上一篇文章Intel的全局描述符示意圖來理解。此時已經完成了GDT的初始化,接下來打開A20地址線。
讀端口用in指令,寫端口用out指令。有個規定就是在92h端口,如果al的二進制第二位是1則打開,反之關閉:
<code> 1... 2 lgdt[gdt] 3 4enable_A20: 5 in al,0x92 6 or al,10b ;一般直接寫2 7 out 0x92,al 8 9 cli1011gdt_head:12.../<code>
然後就是設置PE位,跳轉到保護模式:
<code> 1... 2 out 0x92,al 3 4 cli 5 6set_PE: 7 mov eax,cr0 8 or eax,1 9 mov cr0,eax1011entry_to_protection_mode:12 jmp dword 0x8:flush13...14 dd gdt_head1516[bits 32]17;這裡已經是32位模式了,nasm就會編譯成32位的代碼18flush:/<code>
此時我們要驗證保護模式是否真正開啟,需要向顯存寫入信息,但是這次我們不需要再偏移地址了,直接0xb8000就行:
<code>1flush:2 mov dword [0xb8000+160+0],'P'3 mov dword [0xb8000+160+1],0x2f45 jmp $ ;在這停止,cpu進入睡眠模式6 hlt78times 4096-($-$$) db 0 ;對齊內存/<code>
這回截個圖:
進入保護模式之後,很多事情就方便做了。今天先這樣。