一, 系统启动之前
我们知道, 计算机中有各种内存设备, 比如内存条RAM, 主板上的ROM, 显存等, 这些设备有的时候都是独立的, 但在CPU看来, 这些内存是连续的, 所有的内存设备都可以组成一个"大内存", 大概可以用下面的图来描述:
其中RAM在最底层, 主板的ROM在最顶层, 而显存等其它外围设备的内存位于中间. 主板的ROM中装有BIOS(基本输入输出系统), 大小一般都是64KB. 当然, 不同的计算机, 内存大小不一样, 所以BIOS所在的区域是从哪个地方开始, 都是不确定的. 但是对于Bochs虚拟机, 默认状态下BIOS位于0xFFFF0000 到 0xFFFFFFFF 这段区域.
对于16位的8086计算机而言, 当我们按下电源, 主板通电, CPU此时是处于实模式的, CS寄存器的值位0xFFFF, 其他所有寄存器的值都是0x0000. 所以CS:IP 的值为0xFFF0.
可以在Bochs中调试BIOS程序来验证, 如图所示:
进入汇编级调试:
$ ./dbg-asm # 进入汇编级调试
调试方法: 输入s, 代表 step, 即单步执行
我们可以通电之后CS:IP指向的却是是0xfffffff0地址, 而BIOS的第一条语句的是jmp far f0000:e95b 这个意思是跳转到0x000fe05b地址处, 所以接下来就执行0x000fe05b地址处的指令了.
这里有个问题, 就是刚开始就直接跳转了? 猜想可能是0xfffffff0到0xfffffff之间只有16个字节, 太少了, 存储不了几条指令, 如果不跳转, 那执行了几条指令后就没有指令执行了, 所以就必须要跳转到内存地址较低的地方. 但是为何不直接就从内存位置较低的地方开始执行呢? 我也不懂为何现在的BIOS都是这样设计的.
然后一直单步执行, 就可以看到BIOS的所有指令了.
直接启动Bochs虚拟机, 可以看到如下信息:
从这里可以看到BIOS程序在屏幕上显示了一些硬件信息, 最后看到BIOS尝试让计算机从软盘(Floppy)开始启动, 却发现could not read the boot disk (不能读取到启动磁片), 然后提示No bootable device (没有可启动的设备)
二, bootsect程序执行
实际上, BIOS最后的功能是将启动设备的第一个扇区的程序载入到0x7c00处. 这里的启动设备可以是软盘, 可以是光盘, 也可以是硬盘.
首先执行./dbg-asm, 开始汇编级调试, 然后跳转到0x7c00处, 然后单步执行:
调试方法: 输入 b 0x7c00 就表示把0x7c00处设置为断点, 然后输入c, 表示 continue
用vim打开linux/boot/bootsect.s, 发现这里面的汇编程序和调试中的代码是一样的. 所以可以判断, linux中的bootsect.s就是操作系统启动的第一个模块了. 当
三, 编译bootsect.s并执行
as86是汇编器, ld86是链接器, -0表示生成16位的目标程序, -a表示生成与GNU as和ld部分兼容的代码, -s告诉链接器ld86去除最后生成的可执行文件中的符号信息.
bootsect模块应该位于启动设备的第1个扇区, 而一个扇区的大小只有512个字节, 但是编译成功之后却发现bootsect有544个字节, 原因是ld86链接器产生了minix可执行文件格式, 这就类似于在windows系统中, 每个可执行文件的头部都会有可执行文件格式, 没有这些信息程序就不能成功执行!
要去掉这个头部的32个字节. 只需要用linux下的dd命令, 很方便就可以完成:
执行结果就是将bootsect中的前32个字节忽略, 并将创建的新文件命令为Image
将Image文件复制到linux-0.11目录下, 然后在bochs中启动:
启动之后的结果如图所示:
从这里可以发现bootsect在屏幕上显示了一条信息: Loading system ...