处理器加电或者复位后,BIOS会执行硬件检测和初始化程序,如果没有错误,接下来就会进行操作系统引导。
BIOS会根据CMOS(一块可读写的RAM芯片,保存系统当前的硬件配置和用户的设定参数)里记录的启动顺序逐个地来尝试加载启动代码。
具体的过程是BIOS将磁盘的第一扇区(磁盘最开始的512字节,也就是主引导扇区)载入内存,放在0X0000:0X7C00处,然后检查这个扇区的最后两个字节是不是“0x55AA”,如果是则认为这是一个有效的启动扇区,如果不是就会尝试下一个启动介质;
如果主引导扇区有效,则以一个段间转移指令
jmp 0x0000:0x7c00
跳过去继续执行;
如果所有的启动介质都判断过后仍然没有找到可启动的程序,那么BIOS会给出错误提示。
所以,代码中的vstart=0x7c00不是空穴来风,而是根据代码被加载的实际位置决定的。
当这段程序刚被加载到内存后,
CS=0x0000, IP=0x7c00
如上图所示,假设不写vstart=0x7c00,那么标号“number”的偏移地址就从程序头(认为是0)开始算起,为0x012e;
但是实际上“number”的段内偏移地址是0x7d2e(0x012e+0x7c00=0x7d2e)!
为了修正这个偏移地址的差值,于是有vstart=0x7c00,也就是说段内所有指令的汇编地址都在原来的基础上加上0x7c00.
这里还要再补充一点,如果看这个源文件对应的列表文件,是看不出来偏移地址被加了0x7c00的。
列表文件的一个截图如下:
看到了吗?第一条指令的汇编地址,还是从0开始的!
而且
SECTION mbr align=16 vstart=0x7c00
这句话还是出现在了列表文件里。
我的理解是,列表文件仅仅是对源码的第一遍扫描吧。在后面的扫描中,0x7c00就起作用了。
举个例子吧,
上图有一行
16 00000007 2EA1[CA00]
mov ax,[cs:phy_base]
列表文件的末尾有
151 000000CA 00000100
phy_base dd 0x10000
也就是说 phy_base 这个标号的汇编地址就是00CA(这时候7C00还没有起作用)
我们再看一下编译后的二进制文件
在偏移为0x07的地方,对应的指令码是
2EA1CA7C
注意到其中的CA7C(低字节在前面)了吗? 这个就是00CA+7C00=7CCA的结果啊!
我们继续看代码,
;设置堆栈段和栈指针
mov ax,0
mov ss,ax
mov sp,ax
定义栈需要两个连续的步骤,即初始化SS和SP.
*——————-小贴士—————-
原书P158上方:处理器在设计的时候就规定,当遇到修改段寄存器SS的指令时,在这条指令和下一条指令执行完毕期间,禁止中断,以此来保护栈。也就是说,我们应该在修改SS的指令之后,紧接着一条修改SP的指令。
——————————————–*
因为已经设置了SP=SS=0,所以第一次执行PUSH指令时,先把SP减2,即0x0000-0x000=0xFFFE(借位被忽略);然后把内容送入SS:SP指向的内存单元处。如下图所示(文章中画的只是示意图,不是按照比例画的,凑合看)
代码的末尾部分有
phy_base dd 0x10000 ;用户程序被加载的物理起始地址
也就是说作者安排把用户程序加载到物理内存0x10000处,(我们完全可以修改成别的16字节对齐的地址,只要把用户程序加载到一个空闲的地方就可以。)
上面这几行的意思是根据物理地址计算出逻辑段地址,[DX:AX]是被除数,BX的内容是除数(16),计算结果在AX(对于本程序,结果就是0x1000)中。然后令DS和ES都指向这个段。
这段代码的最后调用了过程 read_hard_disk_0,我们看一下过程调用的代码,我在代码中加了一些注释:
read_hard_disk_0: ;从硬盘读取一个逻辑扇区 ;输入:DI:SI=起始逻辑扇区号 ; DS:BX=目标缓冲区地址 ;使用LBA28寻址方式 push ax push bx push cx push dx; 用到的寄存器压栈保存 mov dx,0x1f2 mov al,1 out dx,al ;读取的扇区数为1 inc dx ;0x1f3 mov ax,si out dx,al ;LBA地址7~0 inc dx ;0x1f4 mov al,ah out dx,al ;LBA地址15~8 inc dx ;0x1f5 mov ax,di out dx,al ;LBA地址23~16 inc dx ;0x1f6 mov al,0xe0 ;LBA28模式,主盘 or al,ah ;LBA地址27~24 out dx,al inc dx ;0x1f7 mov al,0x20 ;读命令 out dx,al .waits: in al,dx and al,0x88 cmp al,0x08 jnz .waits ;不忙,且硬盘已准备好数据传输 mov cx,256 ;总共要读取的字数 mov dx,0x1f0 .readw: in ax,dx mov [bx],ax add bx,2 loop .readw pop dx pop cx pop bx pop ax ret