代码清单
;代码清单12-1
;文件名:c12_mbr.asm
;文件说明:硬盘主引导扇区代码
;创建日期:2011-5-16 19:54;修改于2022-02-16 11:15
;设置堆栈段和栈指针
mov ax, cs
mov ss, ax
mov sp, 0x7c00
;计算GDT所在的逻辑段地址
12 mov ax, [cs: gdt_base + 0x7c00] ;低16位
13 mov dx, [cs: gdt_base + 0x7c00 + 0x02] ;高16位
14 mov bx, 16
15 div bx
mov ds, ax ;令DS指向该段以进行操作
mov bx, dx ;段内起始偏移地址
;创建0#描述符,它是空描述符,这是处理器的要求
mov dword [bx+0x00],0x00
mov dword [bx+0x04],0x00
;创建#1描述符,保护模式下的数据段描述符(文本模式下的显示缓冲区)
mov dword [bx+0x08],0x8000ffff
mov dword [bx+0x0c],0x0040920b
;初始化描述符表寄存器GDTR
mov word [cs: gdt_size+0x7c00],15 ;描述符表的界限(总字节数减一)
lgdt [cs: gdt_size+0x7c00]
in al,0x92 ;南桥芯片内的端口
or al,0000_0010B
out 0x92,al ;打开A20
cli ;保护模式下中断机制尚未建立,应
;禁止中断
mov eax,cr0
or eax,1
mov cr0,eax ;设置PE位
;以下进入保护模式... ...
mov cx,00000000000_01_000B ;加载数据段选择子(0x08)
mov ds,cx
;以下在屏幕上显示"Protect mode OK."
mov byte [0x00],'P'
mov byte [0x02],'r'
mov byte [0x04],'o'
mov byte [0x06],'t'
mov byte [0x08],'e'
mov byte [0x0a],'c'
mov byte [0x0c],'t'
mov byte [0x0e],' '
mov byte [0x10],'m'
mov byte [0x12],'o'
mov byte [0x14],'d'
mov byte [0x16],'e'
mov byte [0x18],' '
mov byte [0x1a],'O'
mov byte [0x1c],'K'
mov byte [0x1e],'.'
hlt ;已经禁止中断,将不会被唤醒
;-------------------------------------------------------------------------------
gdt_size dw 0
gdt_base dd 0x00007e00 ;GDT的物理地址
times 510-($-$$) db 0
db 0x55,0xaa
现在还处在实模式下,因此GDT中安装描述符,必须将GDT的线性地址,这里线性地址就是物理地址以下线性地址都称为物理地址,转换成逻辑逻辑段地址和偏移地址
gdt_base处开辟了一个双字(因为是实模式,所以这里是4个字节为一个双字)来保存GDT的线性地址
看到第12行,将GDT线性地址的低16位传送到寄存器AX中
和之前一样,使用了段前缀“cs:”,表明是访问代码段中的数据
此时,代码段中的逻辑段地为0,主引导程序就位于这个段中且偏移地址是0x7c00
标号gdt_base代表的数值是它相对于主引导程序起始处的位移量,所以这个数据有效地址是0x7c00+gdt_base
在第13行将GDT线性基地址的高16位传送寄存器DX
14~15行将取得的线性基地址转换成逻辑地址,也就是将GDT的内存位置当成一个段来操作
DA:AX除以16,商在AX中段地址,余数在DX中偏移地址
接着17~18行将寄存器AX中的逻辑段地址传送到数据段寄存器DS中,将偏移地址传送到寄存器BX中
处理器规定,GDT中第一个描述符必须是空描述符
描述符选择子(简称选择子)用来选择一个段描述符,并加载到段寄存器
描述符选择子包含了描述符在描述表中的序号(索引号)
如果选择的GDT中的第一个描述符(0号)
则选择子为0
一个未初始化的选择子也是0,使用这样的描述符默认选择GDT中的0号描述符,但并不是我们的本意
所以,处理器要求将第一个描述符定义成空描述
看到21和22行,将两个全0的双字分别写入BX和BX+4的地方
在进入保护模式后
我们希望在屏幕上显示“到此一游”,所以要访问显存,要访问显存就必须定义成一个段,并创建一个描述符
25和26行
描述符各个字节在内存中的映像,在INTEL中是低端字节序,低位在低地址,高位在高地址
可以看到
线性基地址为0x000B8000
段界限为0x0FFFF,粒度为字节(G=0),即,该段的长度为64KB
属于存储器的段(S=1)
这是一个32位的段(D=1)
该段目前位于内存中(P=1)
段的特权级为0(DPL=00)
这是一个可读可写、向上扩展的数据段(TYPE=0010)
这些完成后,接下来就是加载描述符表的线性基地址和界限到寄存器GDTR,使用lgdt指令
该指令的格式为
lgdt m 在有效地址m处,包含GDT的32位线性地址和16位界限值,共6字节
lgdt指令从指定的内存地址处加载6字节的数据到寄存器GDTR,其中包括32位的GDT线性地址及16位的界限值
该指令在实模式和保护模式下都可以执行,但是在实模式下使用16位的有效地址m访问内存;在32位保护模式下使用32位的有效地址m访问内存
在6字节的内存区域中,要求前(低)16位是GDT的界限值,后(高)32位是GDT的基地址
计算机启动后,在初始状态下,寄存器GDTR的基地址被初始化为0x00000000;界限值0xFFFF
第29行,GDT的界限值15写入标号gdt size 所在的内存单元
与访问gdt base 处的数据一样,标号gdt size 处的这个字节,有效地址为gdt size+0x7c00
这里一共写了2个描述符,每个占用8字节,两个就是16字节
GDT的界限值总字节数减去1,所以是15
接着第31行,gdt size开始的6字节加载到GDTR
因为gdt size 和gdt base是连续声明的,所以从gdt size 处读取6字节,包括gdt base
目前还没有进入保护模式,所以以上都是在实模式上进行的