MIT6.828 Exercise1.3 实验报告
对于PC来说,软盘,硬盘都可以被划分为一个个大小为512字节的区域,叫做扇区。一个扇区是一次磁盘操作的最小粒度。每一次读取或者写入操作都必须是一个或多个扇区。如果一个磁盘是可以被用来启动操作系统的,就把这个磁盘的第一个扇区叫做启动扇区。这一部分介绍的boot loader程序就位于这个启动扇区之中。当BIOS找到一个可以启动的软盘或硬盘后,它就会把这512字节的启动扇区加载到内存地址0x7c00~0x7dff这个区域内。
对于6.828,我们将采用传统的硬盘启动机制,这就意味着我们的boot loader程序的大小必须小于512字节。整个boot loader是由一个汇编文件,boot/boot.S,以及一个C语言文件,boot/main.c。Boot loader必须完成两个主要的功能。
1.首先,boot loader要把处理器从实模式转换为32bit的保护模式,因为只有在这种模式下软件可以访问超过1MB空间的内容。
2.然后,boot loader可以通过使用x86的特定的IO指令,直接访问IDE磁盘设备寄存器,从磁盘中读取内核。
对于boot loader来说,有一个文件很重要,obj/boot/boot.asm。这个文件是我们真实运行的boot loader程序的反汇编版本。所以我们可以把它和它的源代码即boot.S以及main.c比较一下。
Exercise 3. Take a look at the lab tools guide, especially the section on GDB commands. Even if you're familiar with GDB, this includes some esoteric GDB commands that are useful for OS work.
Set a breakpoint at address 0x7c00, which is where the boot sector will be loaded. Continue execution until that breakpoint. Trace through the code in
boot/boot.S
, using the source code and the disassembly fileobj/boot/boot.asm
to keep track of where you are. Also use thex/i
command in GDB to disassemble sequences of instructions in the boot loader, and compare the original boot loader source code with both the disassembly inobj/boot/boot.asm
and GDB.Trace into
bootmain()
inboot/main.c
, and then intoreadsect()
. Identify the exact assembly instructions that correspond to each of the statements inreadsect()
. Trace through the rest ofreadsect()
and back out intobootmain()
, and identify the begin and end of thefor
loop that reads the remaining sectors of the kernel from the disk. Find out what code will run when the loop is finished, set a breakpoint there, and continue to that breakpoint. Then step through the remainder of the boot loader.
Be able to answer the following questions:
- Q1: At what point does the processor start executing 32-bit code? What exactly causes the switch from 16- to 32-bit mode?
- Q2: What is the last instruction of the boot loader executed, and what is the first instruction of the kernel it just loaded?
- Q3: Where is the first instruction of the kernel?
- Q4: How does the boot loader decide how many sectors it must read in order to fetch the entire kernel from disk? Where does it find this information?
解答1
在boot.S汇编文件中,运行到50行有cr0寄存器的操作,其中cr0的第一位bit0是保护模式的启动位,因此经过orl操作完,保护模式正式启动,接着的ljmp指令执行过后,就可以访问超过1MB的空间了。
解答2+3
bootloader最后执行的指令是调用bootmain函数,即进入main.c文件
63行:其中ELF文件头的e_entry字段的含义是这个可执行文件的第一条指令的虚拟地址。因此,从63行之后,控制权就来到了操作系统的内核
一步步调试过后,我们发现程序跳转到了kernel/entry.S文件中,因此这就是内核执行的第一条指令
解答4
ph = (struct Proghdr *) ((uint8_t *) ELFHDR + ELFHDR->e_phoff);
eph = ph + ELFHDR->e_phnum;
for (; ph < eph; ph++)
readseg(ph->p_pa, ph->p_memsz, ph->p_offset);
ELF文件头部有段的数量,每个段的大小,根据这些信息可以判断内核的大小,进而决定读入几个扇区的数据。
补充知识
1.ELF文件:
a.综述
可以将 ELF 可执行文件视为带有加载信息的头部,然后后面跟着几个程序段,每个程序段都是连续的代码或数据块。bootloader把程序段都加到内存里,然后就可以执行内核部分了。
b.结构
ELF 二进制文件以一个固定长度的 ELF 头部开始(ELF Header),然后是一个可变长度的程序头部(Program header table),其中列出了要加载的每个程序段。我们对下列几个段感兴趣:
.text
: 程序的执行指令段.rodata
: 只读数据, such as ASCII string constants produced by the C compiler. (We will not bother setting up the hardware to prohibit writing, however.).data
: 数据段包含程序的初始数据, such as global variables declared with initializers likeint x = 5;
.