上一讲,讲了CPU执行操作系统的最开始的两行代码:
mov ax, 0x07c0
mov ds, ax
这两行代码将数据段寄存器 ds 的值变成了 0x07c0,方便之后访问内存时,利用这个段基址进行寻址。
接下来的代码:
mov ax,0x9000
mov es,ax
mov cx,#256
sub si,si
sub di,di
rep movw
此时,ds 寄存器的值已经是 0x07c0 了,然后又通过同样的方式将 es 寄存器的值变成 0x9000,接着又把 cx 寄存器的值变成 256(代码里确实是用十进制表示的,与其他地方有些不一致,不过不影响)。现在 ds、es 和 cx 寄存器的值就都被赋上了确定的值了,
其中sub指令,就是sub a,b意思就是a = a - b
因此sub si,si sub di,di就是表示这两个寄存器的值为0
这样一来上面的代码的结果就是
ds = 0x07c0
es = 0x9000
cx = 256
si = 0
di = 0
这上面的六条指令都是为了服务后面的rep movw这条指令,movw表示复制一个字也就是2个字节,
根据这行指令,自然就会引出以下三个问题:
- 重复执行多少次呢?答案是 cx 寄存器中的值,也就是 256 次。
- 从哪复制到哪呢?答案是从 ds:si 处复制到 es:di 处,也就是从 0x7c00 复制到 0x90000。
- 一次复制多少呢?刚刚说过了,复制一个字 16 位,也就是两个字节。那么。一共复制 256 次的两个字节,其实就是复制 512 个字节。
总结一下就是,将启动区的512字节,又把它移动到0x90000处开始的512字节处,也就是下面的样子:
我理解的就是:0x7c00这个地址位置比较靠前,为了操作系统的代码不被覆盖,把它先移动到比较靠后的位置。现在操作系统最开始的代码位于0x90000处了,再往后看代码会发现这是一条跳转指令了:
jmpi go, 0x9000
go: mov ax,cs
mov ds,ax
mov es,ax
jmpi 是一个段间跳转指令,表示跳转到 0x9000:go 处执行。
这里寻址方式依然是段基址先左移4位,再加上偏移地址。段基址左移4位就是0x90000, 因此最后的效果就是跳转到0x90000 + go这个内存地址处执行。
go在这里就是一个标签,学过汇编都知道,这里最终编译成机器码的时候会被翻译成一个值,这个值就是go这个标签在文件内的偏移地址。当然比较准确的说法就是,bootsect.s编译成二进制文件bootsect后,go这个标签在被编译成二进制文件里的内存地址偏移量。
这个偏移地址加上0x90000,就刚好是go标签处的那行代码,mov ax, cs此时所在的内存地址了。
其实,就是接着bootsect.s这个文件的代码继续执行而已,因为前面的移动代码已经执行过了。
假如 mov ax,cs 这行代码位于最终编译好的二进制文件的 0x08 处,那 go 就等于 0x08,而最终 CPU 跳转到的地址就是 0x90008 处。
总结
- 第一讲就是由BIOS把启动区的代码复制到内存地址0x7c00处,由于x86寻址方式的历史兼容原因,所以需要段基址左移4位,然后就是把0x7c00的512启动区的数据复制到0x90000地址处。然后接着运行。