f1 lab1:Booting a PC翻译

更新至2018年9月13日

介绍

这个实验室分为3个部分。第一部分,主要是熟悉x86汇编语言,x86仿真器QEMU和电脑的开机引导程序。第二部分研究6.828内核的引导加载程序(BootLoader),这个BootLoader位于源码的lab/boot目录下。最后,第三部分深入研究6.828内核本身的初始模板,名为JOS,其位于lab/kern目录下。

软件设置

本课程和后续的作业所需要的文档是通过git进行分发的,要链接有关git的更多信息,可以查看Git user's manual,如果你已经熟悉其他版本控制器了,可以通过CS-oriented overview of Git很好地过渡过来。
课程git仓库的地址为https://pdos.csail.mit.edu/6.828/2018/jos.git,可以通过下面的命令,把仓库克隆到本地。在这里必须使用x86的电脑,也就是说运行uname -a指令,你应该可以看到i386 GNU/Linux或者i686 GNU/Linux或者x86_64 GNU/Linux的字样。

athena% mkdir ~/6.828
athena% cd ~/6.828
athena% add git
athena% git clone https://pdos.csail.mit.edu/6.828/2018/jos.git lab
Cloning into lab...
athena% cd lab

因为我们是在非Athena的电脑上运行,所以我们需要自己安装qemu和gcc等工具,详细内容可以参考此页tools page。在我自己的学习笔记中也做了整理。

第一部分:PC引导

第一个练习的目的是向你介绍x86汇编语言和PC引导过程,并开始QEMU和QEMU/GDB调试。这部分不需要自己写任何代码,但是这部分需要仔细阅读,以便自己更好的理解,并为回答后面的问题做好准备。

x86程序集入门

如果你还不熟悉x86汇编语言,在本课程中你将很快熟悉它!PC Assembly Language Book是一个不错的入门书籍。
警告:不幸的是,本书中的示例是为NASM汇编程序编写的,而我们将使用GNU汇编程序。NASM使用所谓的Intel语法,而GNU使用AT&T语法。虽然语义上是等价的,但程序集文件会有很大不同,至少表面上是如此,这取决于使用的语法。幸运的是,两者之间的转换非常简单,Brennan的Brennan's Guide to Inline Assembly中对此进行了介绍。

练习1:

熟悉6.828参考页上提供的汇编语言材料。您现在不必阅读它们,但在阅读和编写x86程序集时,您几乎肯定会希望参考其中的一些资料。
我们建议阅读Brennan的Brennan's Guide to Inline Assembly中的“语法”一节。它对我们将在JOS中与GNU汇编程序一起使用的AT&T汇编语法给出了一个很好的(而且非常简短的)描述。

当然,x86汇编语言编程的最终参考是Intel的指令集体系结构参考,您可以在6.828参考页上(the 6.828 reference page)找到两种版本:一个是旧的80386程序员参考手册的HTML版本(80386 Programmer's Reference Manual
),它比更新的手册要短得多,更容易导航,但描述了所有的x86我们将在6.828中使用的处理器功能;以及英特尔提供的完整、最新和最棒的IA-32英特尔体系结构软件开发人员手册( IA-32 Intel Architecture Software Developer's Manuals
),涵盖了我们在课堂上不需要的最新处理器的所有功能,但您可能有兴趣了解这些功能。AMD提供了一套同等的(通常更友好的)手册。保存英特尔/AMD体系结构手册( available from AMD
)供以后使用,或者在您要查找特定处理器功能或指令的最终解释时使用它们作为参考。

模拟x86

我们没有在一个真实的计算机上开发操作系统,而是在一个程序模拟的仿真计算机上开发:我们为模拟器编写的代码是可以在真实的计算机上启动的。只是使用模拟器可以简化调试,例如当我们想在x86内部设置断点的时候,对于硅芯片版本的x86是很难做到的。
在6.828中,我们将使用QEMU仿真器,一个现代且相对快速的仿真器。虽然QEMU的内置监视器仅提供有限的调试支持,但QEMU可以充当GNU调试器(GDB)的远程调试目标,我们将在本实验室中使用它来逐步完成早期启动过程。
在lab目录下,执行make:

savior@ubuntu:~$cd lab
savior@ubuntu:~/lab$ make
+ as kern/entry.S
+ cc kern/entrypgdir.c
+ cc kern/init.c
+ cc kern/console.c
+ cc kern/monitor.c
+ cc kern/printf.c
+ cc kern/kdebug.c
+ cc lib/printfmt.c
+ cc lib/readline.c
+ cc lib/string.c
+ ld obj/kern/kernel
ld: warning: section `.bss' type changed to PROGBITS
+ as boot/boot.S
+ cc -Os boot/main.c
+ ld boot/boot
boot block is 390 bytes (max 510)
+ mk obj/kern/kernel.img

(如果出现“undefined reference to\uudivdi3'”之类的错误,则可能没有32位gcc multilib。如果您运行的是Debian或Ubuntu,请尝试安装gcc multilib包。) 现在就已经准备好运行QEMU了,上面生成的 obj/kern/kernel.img就是模拟PC的硬盘映像,其中包含我们的引导加载程序(obj/boot/boot)和内核(obj/kernel)。 输入:make qemu或者make qemu-nox`运行内核,内核启动后会输出以下内容:

VNC server running on `127.0.0.1:5900'
6828 decimal is XXX octal!
entering test_backtrace 5
entering test_backtrace 4
entering test_backtrace 3
entering test_backtrace 2
entering test_backtrace 1
entering test_backtrace 0
leaving test_backtrace 0
leaving test_backtrace 1
leaving test_backtrace 2
leaving test_backtrace 3
leaving test_backtrace 4
leaving test_backtrace 5
Welcome to the JOS kernel monitor!
Type 'help' for a list of commands.
K> 

从第一句话开始,均是由我们的JOS框架打印输出的,K>这是由内核中包含的小型监视器或交互控制器打印输出的。
如果使用make qemu,内核打印的这些行将同时出现在运行qemu的常规shell窗口和qemu显示窗口中。这是因为出于测试和实验室分级的目的,我们设置了JOS内核,以便不仅将其控制台输出写入虚拟VGA显示器(如QEMU窗口中所示),而且还写入模拟PC的虚拟串行端口,QEMU依次将其输出到自己的标准输出。同样,JOS内核将从键盘和串行端口接收输入,因此您可以在VGA显示窗口或运行QEMU的终端中为其提供命令。或者,您可以通过运行make qemu-nox使用没有虚拟VGA的串行控制台。
要退出qemu,使用Ctrl+a x
这里的内核监视器,只支持两个命令,helpkerninfo
最后,需要注意的是,这个映像真的是支持真实的计算机的,但是不建议在真实的计算机上尝试,因为会导致计算机主引导记录被破坏,丢失硬盘上的东西。

PC的物理地址空间

现在,我们将深入探讨PC如何启动的更多细节。PC的物理地址空间是硬连接的,具有以下总体布局:


+------------------+  <- 0xFFFFFFFF (4GB)
|      32-bit      |
|  memory mapped   |
|     devices      |
|                  |
/\/\/\/\/\/\/\/\/\/\

/\/\/\/\/\/\/\/\/\/\
|                  |
|      Unused      |
|                  |
+------------------+  <- depends on amount of RAM
|                  |
|                  |
| Extended Memory  |
|                  |
|                  |
+------------------+  <- 0x00100000 (1MB)
|     BIOS ROM     |
+------------------+  <- 0x000F0000 (960KB)
|  16-bit devices, |
|  expansion ROMs  |
+------------------+  <- 0x000C0000 (768KB)
|   VGA Display    |
+------------------+  <- 0x000A0000 (640KB)
|                  |
|    Low Memory    |
|                  |
+------------------+  <- 0x00000000

第一批基于16位Intel 8088处理器的PC机只能寻址1MB的物理内存。因此,早期PC的物理地址空间将从0x00000000开始,但结束于0x000FFFFF而不是0xFFFFFFFF。标记为“低内存”的640KB区域是早期PC唯一可以使用的随机访问内存(RAM);事实上,最早的PC只能配置16KB、32KB或64KB的RAM!
从0x000A0000到0x000FFFFF的384KB区域由硬件保留,用于特殊用途,如视频显示缓冲区和非易失性存储器中的固件。这个保留区域最重要的部分是基本输入/输出系统(BIOS),它占用从0x000F0000到0x000FFFFF的64KB区域。在早期的PC机中,BIOS保存在真正的只读存储器(ROM)中,但当前的PC机将BIOS存储在可更新的闪存中。BIOS负责执行基本的系统初始化,例如激活显卡和检查安装的内存量。执行此初始化后,BIOS从某些适当的位置(如软盘、硬盘、CD-ROM或网络)加载操作系统,并将机器的控制权传递给操作系统。
当英特尔最终以80286和80386处理器分别突破16MB和4GB物理地址空间时,“打破了1兆字节的屏障”时,PC架构师仍然保留了低1MB物理地址空间的原始布局,以确保与现有软件的向后兼容性。因此,现代PC机的物理内存有一个从0x000A0000到0x00100000的“洞”,将RAM分为“低”或“常规内存”(前640KB)和“扩展内存”(其他所有内存)。此外,在PC的32位物理地址空间(首先是物理RAM)的最上面的一些空间,现在通常由BIOS保留给32位PCI设备使用。
最新的x86处理器可以支持超过4GB的物理RAM,因此RAM可以进一步扩展到0xFFFFFFFF以上。在这种情况下,BIOS必须在32位可寻址区域顶部的系统RAM中留下第二个孔,以便为这些32位设备留下映射空间。由于设计上的限制,JOS无论如何只能使用PC的前256MB物理内存,所以现在我们假设所有的PC都只有32位的物理地址空间。但是,处理复杂的物理地址空间和硬件组织的其他方面是操作系统开发的一个重要的实际挑战。

The ROM BIOS

在实验室的这一部分,您将使用QEMU的调试工具来研究与IA-32兼容的计算机是如何引导的。
打开两个终端窗口,将两个shell都cd到您的实验室目录中。在其中,输入make qemu-gdb(或make qemu-nox-gdb)。这会启动QEMU,但QEMU会在处理器执行第一条指令并等待GDB的调试连接之前停止。在第二个终端中,从运行make的同一个目录运行make gdb。你应该看看这样的东西,

athena% make gdb
GNU gdb (GDB) 6.8-debian
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later 
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i486-linux-gnu".
+ target remote localhost:26000
The target architecture is assumed to be i8086
[f000:fff0] 0xffff0:    ljmp   $0xf000,$0xe05b
0x0000fff0 in ?? ()
+ symbol-file obj/kern/kernel
(gdb) 

我们提供了一个.gdbinit文件,该文件将GDB设置为调试早期引导期间使用的16位代码,并将其定向到侦听QEMU。(如果不起作用,您可能需要在主目录中的.gdb in it中添加一个add auto load safe路径,以使gdb处理我们提供的.gdbinit。如果你必须这么做,gdb会有相应的提示。)
下面这一行:

[f000:fff0] 0xffff0:    ljmp   $0xf000,$0xe05b

是GDB对要执行的第一条指令的反汇编。从这个输出可以得出以下几点结论:

  • IBM PC从物理地址0x000ffff0开始执行,该地址位于为ROM BIOS保留的64KB区域的最顶端。
  • PC以CS=0xf000和IP=0xfff0开始执行。
  • 要执行的第一条指令是jmp指令,它跳到分段地址CS=0xf000和IP=0xe05b。

为什么QEMU会这样开始?这就是英特尔设计8088处理器的方式,IBM在他们的原始PC上使用了8088处理器。因为PC中的BIOS是“硬连线”到物理地址范围0x000f0000-0x000fffff的,这种设计确保BIOS总是在开机或任何系统重启后首先获得对机器的控制权,这一点至关重要,因为开机时,机器的RAM中没有其他软件可供处理器执行。QEMU模拟器带有自己的BIOS,它将BIOS放在处理器的模拟物理地址空间的这个位置。在处理器重置时,(模拟的)处理器进入真实模式,并将CS设置为0xf000,将IP设置为0xfff0,以便在该(CS:IP)段地址开始执行。分段地址0xf000:fff0如何转换为物理地址?
为了回答这个问题,我们需要了解一些实模式寻址。在实模式(PC启动的模式)下,地址转换按照以下公式工作:物理地址=16*段+偏移量。因此,当PC将CS设置为0xf000,IP设置为0xfff0时,引用的物理地址是:

16 * 0xf000 + 0xfff0   # in hex multiplication by 16 is
   = 0xf0000 + 0xfff0     # easy--just append a 0.
   = 0xffff0 

0xffff0是BIOS结束前的16字节(0x100000)。因此,BIOS做的第一件事就是将jmp向后移到BIOS中较早的位置,这并不奇怪;毕竟它在16字节内能完成多少任务?

练习2:

使用GDB的si(Step Instruction)命令跟踪ROM BIOS以获取更多的指令,并尝试猜测它可能在做什么。您可能需要查看Phil Storrs I/O端口说明( Phil Storrs I/O Ports Description),以及6.828参考资料页上的其他资料。不需要弄清楚所有的细节,只需要大致了解BIOS首先要做什么。
当BIOS运行时,它设置一个中断描述符表并初始化各种设备,如VGA显示。这就是您在QEMU窗口中看到的“Starting SeaBIOS”消息的来源。
初始化PCI总线和BIOS知道的所有重要设备后,它会搜索可引导设备,如软盘、硬盘驱动器或CD-ROM。最终,当它找到可引导磁盘时,BIOS会从磁盘读取引导加载程序(boot loader)并将控制权传递给它。

第二部分:Boot Loader

PC机的软盘和硬盘被分成512字节的区域,称为扇区。扇区是磁盘的最小传输粒度:每个读或写操作的大小必须是一个或多个扇区,并在扇区边界上对齐。如果磁盘是可引导的,则第一个扇区称为引导扇区,因为这是引导加载程序代码所在的位置。当BIOS找到可引导的软盘或硬盘时,它将512字节引导扇区加载到物理地址0x7c00到0x7dff的内存中,然后使用jmp指令将CS:IP设置为0000:7c00,并将控制权传递给引导加载程序。与BIOS加载地址一样,这些地址是相当随意的,但它们是固定的,并且是pc的标准化地址。
从CD-ROM启动的能力在PC的发展过程中出现得晚了很多,因此PC架构师利用这个机会稍微重新考虑了启动过程。因此,现代BIOS从CD-ROM引导的方式有点复杂(而且功能更强大)。CD-rom使用2048字节的扇区大小,而不是512字节,BIOS可以在将控制权转移到磁盘之前,将更大的引导映像从磁盘加载到内存中(而不仅仅是一个扇区)。更多信息,参考"El Torito" Bootable CD-ROM Format Specification.
然而,对于6.828,我们将使用传统的硬盘驱动器引导机制,这意味着我们的引导加载程序只能容纳512字节。引导加载程序由一个汇编语言源文件boot/boot.S和一个C源文件boot/main.C组成,它们仔细查看这些源文件,确保您理解发生了什么。引导加载程序必须执行两个主要功能:

  • 1 首先,引导加载程序将处理器从实际模式切换到32位保护模式,因为只有在这种模式下,软件才能访问处理器物理地址空间中大于1MB的所有内存。PC汇编语言的1.2.7和1.2.8节简要介绍了保护模式,英特尔体系 PC Assembly Language结构手册对此进行了详细说明。此时,您只需了解分段地址(segment:offset pairs)转换为物理地址在受保护模式下的情况有所不同,并且在转换偏移量之后为32位而不是16位。
  • 2 其次,引导加载程序通过x86的特殊I/O指令直接访问IDE磁盘设备寄存器,从硬盘读取内核。如果您想更好地理解这里的特定I/O指令的含义,请查看6.828参考页the 6.828 reference page上的“IDE硬盘驱动器控制器”部分。在本课程中,您无需学习如何编程特定设备:编写设备驱动程序实际上是操作系统开发的一个非常重要的部分,但从概念或体系结构的角度来看,它也是最不有趣的部分之一。

了解引导加载程序源代码后,请查看文件obj/boot/boot.asm。这个文件是在编译引导加载程序之后,GNUmakefile创建的引导加载程序的反汇编。这个反汇编文件可以很容易地查看物理内存中所有引导加载程序的代码所在的位置,并且可以更容易地跟踪在GDB中单步执行引导加载程序时发生的事情。同样,obj/kern/kernel.asm包含JOS内核的反汇编,这通常对调试很有用。
可以使用b命令在GDB中设置地址断点。例如,b*0x7c00在地址0x7c00处设置断点。在断点处,您可以使用c和si命令继续执行:c使QEMU继续执行,直到下一个断点(或在GDB中按Ctrl-c),si N一次一步地执行N个指令。
要检查内存中的指令(除了GDB自动打印的下一个即将执行的指令之外),可以使用x/i命令。此命令的语法为x/Ni ADDR,其中N是要反汇编的连续指令数,ADDR是开始反汇编的内存地址。

你可能感兴趣的:(f1 lab1:Booting a PC翻译)