开机后最开始的两行代码
本节问题
- 开机后执行的第一行操作系统代码是什么?
- 在这行代码之前发生了什么?
开机后初始化指向BIOS
PC寄存器(CPU中)存储着将要执行的指令在内存中的地址 按下开机键 ——> 初始化PC寄存器(CPU) ——> CPU按照PC寄存器的数值 ——> 寻找并执行内存中对应地址处的指令
sequenceDiagram
participant PC_Register
participant CPU
participant Memory
PC_Register->>CPU: 初始化PC寄存器
CPU->>PC_Register: 存储着将要执行的指令在内存中的地址
Note right of CPU: 按下开机键
CPU->>Memory: 按照PC寄存器的数值寻找并执行内存中对应地址处的指令
问题一:初始化的值是多少?
- 答:(Inter手册规定)开机后PC寄存器初始化为0xFFFF0(从这个内存地址开始执行CPU第一条指令)
问题二:CPU根据这个地址值去内存中找指令,为什么会找到BIOS?
- 答:CPU是把0xFFFF0作为CPU的地址线型号传输出去,去这个地址线对应的位置处找。
问题三:CPU的地址线连接的设备还不仅仅是内存?
- 答:CPU的地址线连接的设备有:RAM(内存)、ROM(BIOS)、Memory-Mapped IO(一些外设的IO端口)
- 答(二):0xFFFF0指向了BIOS程序所在地ROM区域
读取硬盘启动区(第一扇区)
启动区定义:硬盘中的0盘0道1扇区(第一扇区)的512个字节的最后两个字节分别是0x55和0xaa,BIOS就会认为它是个启动区
!!!注意:启动区一定在第一扇区,但第一扇区并不一定是启动区
对于操作系统而言:此时BIOS的工作是把512字节的二进制数据从硬盘搬运到了内存
对于操作系统的开发人员:我们仅需要把操作系统最开始的那段代码编译出来,并存储在硬盘的0盘0道1扇区即可
eg:在Linux0.11中,最开始的代码是汇编语言写的bootsect.s,位于boot文件夹下,通过编译,bootsect.s会被编译成二进制文件,存放在启动区的第一扇区
加载到内存0x7c00位置并跳转
将操作系统代码编译好存放在硬盘的启动区 ——> 开机后,BIOS程序将代码搬运到内存的0x7c00位置 ——> CPU会从这个位置开始,一条指令一条指令不断地往后执行下去
问题一:为什么要将操作系统代码搬运到0x7c00?
- 答:这是一开始的开发者规定的,不管是谁开发的操作系统,只要是用BIOS这种启动方式,都要假定自己将会被搬运到内存0x7c00这个位置,否则运行就会出错
问题二:BIOS是如何完成从硬盘加载到内存这个目标?
- 答:BIOS程序除了这个工作之外,还有很多计算机自检程序,不过这不是研究重点(小声BB:把硬盘中的数据加载到内存也不是啥难事)
- 答:BIOS只帮我们把启动区的这512字节加载到内存,仍在硬盘其他扇区的操作系统代码得我们自己来处理。 bootsect.s这个文件的前两行代码会被编译并存储在启动区,然后搬运到内存0x7c00,之后也会成为CPU执行的第一个指令,bootsect.s前两行代码如下
mov ax,0x07c0
mov ds,ax
代码含义:把0x07c0这个值复制到ax寄存器里,再将ax寄存器里的值复制到ds寄存器里 代码执行结果:让ds这个寄存器里的值变成了0x07c0 ds寄存器:一个16位的段寄存器,具体表示数据段寄存器,在内存寻址时充当段基址的作用 段基址:我们用汇编写一个内存地址时,实际上仅仅是写了偏移地址 eg:
mov ax, [0x0001]
相当于
mov ax, [ds:0x0001]
ds是默认加上的,表示在ds这个段基址处,往后在偏移0x0001单位,将这个位置的内存数据复制到ax寄存器中。
问题三:为什么ds寄存器的数值要赋值为0x07c0?
- 答:x86为了让自己在16位的实模式下,能访问到20位的地址线,所以要把段基址先左移四位。0x07c0左移四位就是0x7c00,这刚好就和这段代码被BIOS加载到的内存地址0x7c00一样了
- 答:也就是说,在以后写的代码里,访问的数据的内存地址都先默认加上0x7c00,然后再去内存中寻址
问题四:为什么统一加上0x7c00这个数?
- 答:BIOS规定死了把操作系统代码加载到内存0x7c00,里面的数据自然就全都被偏移了这么多,所以把数据段寄存器ds设置为这个值,方便了以后通过这种基址的方式访问内存里的数据
总结
本节的代码主要完成了两步操作:
- 第一步:BIOS将操作系统代码加载到内存0x7c00;
- 第二步:通过mov指令将默认的数据段寄存器ds的值改为0x07c0,方便以后的基址寻址方式