MIT6.828——Lab 1. Part 2 启动qemu
先给上Lab1的地址:https://pdos.csail.mit.edu/6.828/2018/labs/lab1/
参考资料是这位大神的博客:https://www.cnblogs.com/fatsheep9146/p/5078179.html
这里要先开两个terminal,然后全都进入lab(cd lab),其中一个terminal中输入指令make qemu-gdb,然后出现

在另一个terminal中先输入make,再输入make gdb,然后出现

看到第一条指令的地址了嘛?是0xffff0,也就是在Part 1中看到的内存空间中,低1MB中最顶端的64KB,是ROM BIOS的地方,左边方括号中代表CS:IP,这里推荐《X86从实模式到保护模式》这本书,里面将汇编语言讲得很详细,CS是代码段寄存器,IP是指令指针,它只和CS一起使用,当一段代码开始执行时,CS指向代码段的起始地址,IP则指向段内偏移。
为什么要这样设计?为了保证每次系统重启后BIOS总能第一时间获得机器的控制权,此时没有任何软件在内存中运行。
这是运行的第一条指令,是一条跳转指令,跳转到0xfe05b地址处。至于要知道这个地址是怎么通过指令中的值计算出来的,我们需要先知道,当PC机启动时,CPU运行在实模式(real mode)下,而当进入操作系统内核后,将会运行在保护模式下(protected mode)。实模式是早期CPU,比如8088处理器的工作模式,这类处理器由于只有20根地址线,所以它们只能访问1MB的内存空间。但是CPU也在不断的发展,之后的80286/80386已经具备32位地址总线,能够访问4GB内存空间,为了能够很好的管理这么大的内存空间,保护模式被研发出来。所以现代处理器都是工作在保护模式下的。但是为了实现向后兼容性,即原来运行在8088处理器上的软件仍旧能在现代处理器上运行,所以现代的CPU都是在启动时运行于实模式,启动完成后运行于保护模式。BIOS就是PC刚启动时运行的软件,所以它必然工作在实模式。
真正的物理地址转换公式是:CS左移1位再加上IP

第二条指令是将立即数0x0与cs:0x61c8这个地址内的值进行比较,cs:0x61c8是指CS的值加上偏移量位0x61c8所得到地址中的值

第三条指令是根据比较结果,也就是当结果不相等时,跳转到地址0xfd2e1。
jne指令:如果ZF标志位为0的时候跳转,即上一条指令cmpl的结果不是0时跳转,也就是$cs:0x6ac8地址处的值不是0x0时跳转。

第四条指令是异或,显然是将dx寄存器清零,现在的地址是0xfe066,说明结果是0,cs:0x61c8中的值是0。


第五,六条指令是将dx寄存器的值移动到ss中,将立即数0x7000移到esp中,这里的ss指的是栈段寄存器,esp指的是栈指针。(注意,sp是16位的栈指针,esp是32位的栈指针)

第七条指令将立即数0xf34c2移动至edx寄存器中

第八条指令是直接跳转到0xfd15c地址中

第九条指令是将寄存器eax的值复制到寄存器ecx中

第十条指令是CLI(Clear Interrupt) 中断标志置0指令 使 IF = 0,也就是屏蔽所有外部中断

第十一条指令cld是将DF标志清零,以指示传送是正方向。这里解释一下,要实现段之间的批量数据传送,比如movsb或者movsw,需要指定是正向传送还是反向传送,正向传送是指传送操作的方向是从内存区域的低地址端到高地址端

第十二条指令将立即数0x8f移动到eax寄存器

处理器是通过端口来和外围设备打交道的。本质上,端口就是一些寄存器,端口是处理器和外围设备通过I/O接口交流的窗口,每一个I/O接口都可能拥有好几个端口,所有端口都是统一编号的,比如I/O接口A有3个端口,端口号分别为0x0021~0x0023。
这里引入in,out操作:
out %al, PortAddress 向端口地址为PortAddress的端口写入值,值为al寄存器中的值
in PortAddres,%al 把端口地址为PortAddress的端口中的值读入寄存器al中
0x70端口和0x71端口是用于控制系统中一个叫做CMOS的设备
操作CMOS存储器中的内容需要两个端口,一个是0x70另一个就是0x71。其中0x70可以叫做索引寄存器,这个8位寄存器的最高位是不可屏蔽中断(NMI)使能位。如果你把这个位置1,则NMI不会被响应。低7位用于指定CMOS存储器中的存储单元地址,所以如果你想访问第1号存储单元,并且在访问时,我要使能NMI,那么你就应该向端口0x70里面送入0b10000001 = 0x81。
out al,0x70是将al寄存器里的值写到0x70端口中,寄存器al其实就是寄存器eax的低8位,即0x8f(10001111)
in 0x71,al,用来读出数据端口里的数据,0x71为数据端口。
这三条指令可以看出,它首先关闭了NMI中断,并且要访问存储单元0xF的值,并且把值读到al中,但是在后面我们发现这个值并没有被利用,所以可以认为这三条指令是用来关闭NMI中断的。

我们可以查看到,它控制的是 PS/2系统控制端口A,而第16,17步的操作明显是在把这个端口的1号bit置为1。这个端口的bit1的功能是
bit 1= 1 indicates A20 active
即A20位,即第21个地址线被使能,了解实模式和保护模式的同学肯定清楚,如果A20地址线被激活,那么系统工作在保护模式下。但是在之后的boot loader程序中,计算机首先要工作在实模式下啊。所以这里的这个操作,根据网上http://kernelx.weebly.com/a20-address-line.html所说应该是去测试可用内存空间。在boot loader之前,它肯定还会转换回实模式。

lidt指令:加载中断向量表寄存器(IDTR)。这个指令会把从地址0xf6ab8起始的后面6个字节的数据读入到中断向量表寄存器(IDTR)中。中断是操作系统中非常重要的一部分,有了中断操作系统才能真正实现进程。每一种中断都有自己对应的中断处理程序,那么这个中断的处理程序的首地址就叫做这个中断的中断向量。中断向量表自然是存放所有中断向量的表了。

把从0xf6a74为起始地址处的6个字节的值加载到全局描述符表格寄存器中GDTR中。这个表实现保护模式非常重要的一部分。

CR0是处理器内部的控制寄存器,它是32位寄存器,包含了一系列用于控制处理器操作模式和运行状态的标志位。它的第1位是保护模式允许位,是开启保护模式大门的门把手。如果把该位置"1",则处理器进入保护模式。
但是这里出现了问题,我们刚刚说过BIOS是工作在实模式之下,后面的boot loader开始的时候也是工作在实模式下,所以这里把它切换为保护模式,显然是自相矛盾。所以只能推测它在检测是否机器能工作在保护模式下。

之后就是一系列的初始化。这里的23~29步之所以这么做是按照规定来的,https://en.wikibooks.org/wiki/X86_Assembly/Global_Descriptor_Table链接中指出,如果刚刚加载完GDTR寄存器我们必须要重新加载所有的段寄存器的值,而其中CS段寄存器必须通过长跳转指令,即23号指令来进行加载。所以这些步骤是在第19步完成后必须要做的。这样才能是GDTR的值生效。
综上,我们可以看到BIOS的操作就是在控制,初始化,检测各种底层的设备,比如时钟,GDTR寄存器。以及设置中断向量表。但是作为PC启动后运行的第一段程序,它最重要的功能是把操作系统从磁盘中导入内存,然后再把控制权转交给操作系统。所以BIOS在运行的最后会去检测可以从当前系统的哪个设备中找到操作系统,通常来说是我们的磁盘。也有可能是U盘等等。当BIOS确定了,操作系统位于磁盘中,那么它就会把这个磁盘的第一个扇区,通常把它叫做启动区(boot sector)先加载到内存中,这个启动区中包括一个非常重要的程序--boot loader,它会负责完成整个操作系统从磁盘导入内存的工作,以及一些其他的非常重要的配置工作。最后操作系统才会开始运行。
可见PC启动后的运行顺序为 BIOS --> boot loader --> 操作系统内核