这个实验是基于MIT的2017的6.826课程,搭建环境的时候踩了几个坑,但是当时没有记录下来,可惜~
Part 1: PC Bootstrap
介绍了如何安装qemu以及如同通过qemu来模拟操作系统的启动。
The PC’s Physical Address Space
- 8088处理器的内存地址
0 - 640K 内存区域
0xF0000 - 0xFFFFF BIOS区域
0xB8000 - 0xBFFF 显卡区域
The ROM BIOS
[f000:fff0] 0xffff0: ljmp $0xf000,$0xe05b
mit 6.828 lab1 Exercise2
这部分的代码是嵌入在ROM BIOS中的,因此在磁盘上并没有对应的代码。
使用si命令得到的前22条汇编指令如下。虽然能看懂每条指令的字面意思,但看不懂具体实现的功能,后来参考myk的6.828 Lab1大致理解了基本功能:设置ss和esp寄存器的值,打开A20门(为了后向兼容老芯片)、进入保护模式(需要设置cr0寄存器的PE标志)。
1 [ f000:fff0] 0xffff0: ljmp $0xf000,$0xe05b
2 [f000:e05b] 0xfe05b: cmpl $0x0,%cs:0x6ac8
3 [f000:e062] 0xfe062: jne 0xfd2e1
4 [f000:e066] 0xfe066: xor %dx,%dx
5 [f000:e068] 0xfe068: mov %dx,%ss
6 [f000:e06a] 0xfe06a: mov $0x7000,%esp
7 [f000:e070] 0xfe070: mov $0xf34c2,%edx
8 [f000:e076] 0xfe076: jmp 0xfd15c
9 [f000:d15c] 0xfd15c: mov %eax,%ecx
10 [f000:d15f] 0xfd15f: cli
11 [f000:d160] 0xfd160: cld
12 [f000:d161] 0xfd161: mov $0x8f,%eax
13 [f000:d167] 0xfd167: out %al,$0x70
14 [f000:d169] 0xfd169: in $0x71,%al
15 [f000:d16b] 0xfd16b: in $0x92,%al
16 [f000:d16d] 0xfd16d: or $0x2,%al
17 [f000:d16f] 0xfd16f: out %al,$0x92
18 [f000:d171] 0xfd171: lidtw %cs:0x6ab8
19 [f000:d177] 0xfd177: lgdtw %cs:0x6a74
20 [f000:d17d] 0xfd17d: mov %cr0,%eax
21 [f000:d180] 0xfd180: or $0x1,%eax
22 [f000:d184] 0xfd184: mov %eax,%cr0
23 [f000:d187] 0xfd187: ljmpl $0x8,$0xfd18f
参考MIT6.828——Lab 1. Part 2 启动qemu
代码笔记
-
第一条指令:
[f000:fff0] 0xffff0: ljmp $0xf000,$0xe05b
因为8086加载或者复位的时候,CS = 0xFFFF, IP = 0x0000
,所以它取的第一条指令位于0xFFFF0
处。
通过ljmp
指令来跳转到地址0xfe05b
,并改变CS
和IP
的地址,CS = 0xF000
,IP = 0xe05b
。 -
第二、三条指令:
[f000:e05b] 0xfe05b: cmpl $0x0,%cs:0x6ac8
判断0xf6ac8是否等于0,具体意思不清楚。如果不为0,则跳转到0xfd2e1。 -
第四、五、六、七条指令
置ss
为0,esp
为0x7000
,edx
为0xf34e2
,之后再跳转到0xfd15c
-
CLI:Clear Interupt,禁止中断发生。STI:Set Interupt,允许中断发生。CLI和STI是用来屏蔽中断和恢复中断用的,如设置栈基址SS和偏移地址SP时,需要CLI,因为如果这两条指令被分开了,那么很有可能SS被修改了,但由于中断,而代码跳去其它地方执行了,SP还没来得及修改,就有可能出错。
-
CLD: Clear Director。STD:Set Director。在字行块传送时使用的,它们决定了块传送的方向。CLD使得传送方向从低地址到高地址,而STD则相反。这里要解释一下,要实现段之间的批量数据传送,比如movsb或者movsw,需要指定是正向传送还是反向传送,正向传送是指传送操作的方向是从内存区域的低地址端到高地址端。
-
第十二到十四条指令:
处理器是通过端口来和外围设备打交道的。本质上,端口就是一些寄存器,端口是处理器和外围设备通过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存储器中的存储单元地址。 -
第十五到十七条指令
读端口0x92
的值,并将0x92
端口对应的寄存器的1
号bit位置1。
它控制的是 PS/2系统控制端口A,可以查到这个端口的bit1的功能是
bit 1= 1 indicates A20 active
即A20位,即第21个地址线被使能。 -
lidt指令:加载中断向量表寄存器(IDTR)。这个指令会把从地址0xf6ab8起始的后面6个字节的数据读入到中断向量表寄存器(IDTR)中。中断是操作系统中非常重要的一部分,有了中断操作系统才能真正实现进程。每一种中断都有自己对应的中断处理程序,那么这个中断的处理程序的首地址就叫做这个中断的中断向量。中断向量表自然是存放所有中断向量的表了。
-
把从0xf6a74为起始地址处的6个字节的值加载到全局描述符表格寄存器中GDTR中。这个表实现保护模式非常重要的一部分。
-
第20-22指令
CR0是处理器内部的控制寄存器,它是32位寄存器,包含了一系列用于控制处理器操作模式和运行状态的标志位。它的第1位是保护模式允许位,是开启保护模式大门的门把手。如果把该位置"1",则处理器进入保护模式。但是这里出现了问题,我们刚刚说过BIOS是工作在实模式之下,后面的boot loader开始的时候也是工作在实模式下,所以这里把它切换为保护模式,显然是自相矛盾。所以只能推测它在检测是否机器能工作在保护模式下。 -
23条指令之后
https://en.wikibooks.org/wiki/X86_Assembly/Global_Descriptor_Table指出,如果刚刚加载完GDTR寄存器我们必须要重新加载所有的段寄存器的值,而其中CS段寄存器必须通过长跳转指令,即23号指令来进行加载。所以这些步骤是在第19步完成后必须要做的。这样才能是GDTR的值生效。
后面的就看不懂了,应该是调用了BIOS中的各种函数来检测各种底层的设备
Part 2: The Boot Loader
-
If the disk is bootable, the first sector is called the boot sector, since this is where the boot loader code resides。
-
For 6.828, however, we will use the conventional hard drive boot mechanism, which means that our boot loader must fit into a measly 512 bytes. The boot loader must perform two main functions:
- First, the boot loader switches the processor from real mode to 32-bit protected mode, because it is only in this mode that software can access all the memory above 1MB in the processor’s physical address space.
- Second, the boot loader reads the kernel from the hard disk by directly accessing the IDE disk device registers via the x86’s special I/O instructions.
-
分析
boot/boot.S
的代码
00007c00 <start>:
7c00: fa cli
7c01: fc cld
7c02: 31 c0 xor %eax,%eax
7c04: 8e d8 mov %eax,%ds
7c06: 8e c0 mov %eax,%es
7c08: 8e d0 mov %eax,%ss
00007c0a <seta20.1>:
7c0a: e4 64 in $0x64,%al
7c0c: a8 02 test $0x2,%al
7c0e: 75 fa jne 7c0a <seta20.1>
7c10: b0 d1 mov $0x