上一节我们已经掌握了进入保护模式的关键,这一节我们来具体实现他。
我们之前向屏幕输出都是通过设置中断实现,比较麻烦,这次我们换个方法,向显存输出信息,显存地址(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>
这回截个图:
进入保护模式之后,很多事情就方便做了。今天先这样。