实验参考信息

  1. MIT 6.828 lab1 讲义地址
  2. MIT 6.828 课程 Schedule
  3. MIT 6.828 lab 环境搭建参考
  4. MIT 6.828 lab 工具guide
  5. Brennan's Guide to Inline Assembly

实验环境搭建

笔者实验环境:ubuntu 20.02

本实验的实验环境主要包括两部分:

  1. QEMU:x86模拟器
  2. 一整套编译环境

由于实验环境搭建网上已经有很多详尽的资料,这里引用一位大佬的博客作为参考。

实验环境搭建参考链接

实验内容

该实验主要分为3个部分:

  1. 由于我们的实验是基于一个x86模拟器QEMU做的,因此首先要先熟悉一下这个工具,并且借此研究一下PC的开机程序
  2. 在这部分,我们会探究6.828内核的加载过程,探究开机后,是如何将内核加载到内存并运行的。
  3. 最后这部分我们会探究一下6.828内核的基本结构

6.828的实验代码存储在https://pdos.csail.mit.edu/6.828/2018/jos.git代码仓库中,可以通过如下代码拉取到本地:

git clone https://pdos.csail.mit.edu/6.828/2018/jos.git lab

6.828一共有6个实验,每个实验对应一个分支,因此需要切换到对应分支,即:

cd lab # lab code 被克隆到了lab目录下
git checkout lab1 # 切换到lab1分支

由于操作系统的主要编程语言是C和汇编,因此需要有一定的汇编基础,为了保证可以顺畅进行后面的实验,可以先阅读一下Brennan's Guide to Inline Assembly

做好这些准备工作后,让我们开始实验内容。

1. PC Bootstrap(初探QEMU)

这里我们会尝试利用QEMU模拟PC的启动过程。首先,我们尝试先将QEMU跑起来,依次执行如下命令:

cd 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
+ as boot/boot.S
+ cc -Os boot/main.c
+ ld boot/boot
boot block is 380 bytes (max 510)
+ mk obj/kern/kernel.img

到此为止,我们已经得到了一个镜像文件(obj/kern/kernel.img),该文件包括了两个部分:

  1. boot loader(启动加载器):obj/boot/boot
  2. kernel(内核):obj/kernel

    这两个部分后面都会分别介绍,拥有了这个镜像文件,我们就可以运行QEMU了。
make qemu-nox # 或者 make qemu,建议使用make qemu-nox,否则在虚拟机环境下还是有一点小麻烦的

然后如下内容将被显示,如果想要退出qemu请依次按下Ctrl+ax

Booting from Hard Disk...
# 下面部分是习题用的print出来的内容
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>

我们可以看到,QEMU模拟的操作系统打印出了许多类似于debug的信息,这些都是后面的习题要使用的内容,为了我们能够很好的对操作系统debug,以便后面处理上面的信息,我们需要学会使用GDB(调试工具),使用方式也很简单。

  1. 在lab目录下打开两个终端
  2. 第一个终端(终端1)输入命令make qemu-nox-gdb
  3. 第二个终端(终端2)输入命令make gdb

结果如下:

终端1是预览窗口,终端2是debug窗口。我们可以在终端2中输入一些命令来控制程序的运行,或者获取当前计算机中的信息,详细使用方式可以查看MIT 6.828 lab 工具guide,本实验我们只需要使用到3条命令,第一条为si即单句运行。

通过上图我们可以看到,我们对6.828提供的操作系统debug,运行的第一行代码是在内存0xffff0位置的代码ljmp $0xf000,$0xe05b,并且在这行代码上还有一句提示The target architecture is assumed to be i8086。这里有一个问题:

  1. 为何是从0xffff0这个位置开始运行?这里是什么?

我们考察MIT 6.828 lab1 讲义中给出的地址空间布局图:


+------------------+ <- 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

可以看到0xffff0对应BIOS ROM的最后16个字节。继续查看讲义,我们发现,原来这里是沿用了早起8088的设计,PC中的BIOS是烧录进入0x000f0000-0x000fffff位置的,这使得在PC启动时BIOS总能第一时间控制PC,毕竟此时,内存中除了BIOS的部分,都是随机的数据。因此,设计者将入口设置到了0xffff0,即CS=0xf000,IP=0xfff0。注意,BIOS只能运行在实模式下,实模式的物理地址计算方式为:

physical address = 16 * segment + offset

根据公式可以看出实模式只能访问前1MB的内存(0x00000-0xfffff)。然而16个字节的内存并不能存储多少代码,因此,真实的处理逻辑被存储在其他地方,这里只负责jump到对应的地点而已。这部分代码主要用于进行上电自检等设备检验和初始化操作,当一切硬件设备都处理好了,就要开始引导并加载操作系统内核了。

2. Boot Loader

传统意义上,操作系统存储在硬盘空间中,而硬盘又被划分为一个个的扇区,每个扇区512bytes。根据冯诺依曼体系结构,操作系统的内核映像需要被装载到内存中才能运行,因此,Boot Loader的职责就是将操作系统内核映像加载到内存中,并且将控制权限交给内核。

然而这里存在一个问题,回顾物理内存的布局结构,可以看到前1MB的内存基本已经被占用满了。

+------------------+  <- 0x00100000 (1MB)
| BIOS ROM |
+------------------+ <- 0x000F0000 (960KB)
| 16-bit devices, |
| expansion ROMs |
+------------------+ <- 0x000C0000 (768KB)
| VGA Display |
+------------------+ <- 0x000A0000 (640KB)
| |
| Low Memory |
| |
+------------------+ <- 0x00000000

这里的Low Memory也需要做保留用于他用,可见实模式的寻址已经不足以满足当前的需求,我们需要一种方式能够访问更多的内存,以便可以将内核加载到其中。因此,就需要将实模式切换到32-bit的保护模式,在这个模式下,可以访问4GB内存(32bit 30=1GB 2=4)。处理这部分的代码在boot/boot.S中。

了解了内存寻址不够的处理方案,那么还有一个问题,我们需要从哪里加载操作系统内核?怎么让机器了解这个位置?

事实上,为了从硬盘或者软盘上启动,必须把它们用于启动的第一个扇区中所存放的指令(boot/boot.S)装载到内存中执行,而这些指令再把包含内核映像的其他所有扇区拷贝到内存中。处理这部分的代码在boot/main.c中。第一个扇区的装载位置同PC的最初启动位置一样,是一个固定值,在 0x7c00 到 0x7dff中。这里我们学习GDB的第二条和第三条命令:

  1. b *ADDR 在ADDR位置设置断点
  2. c 将程序运行到断点处

这里我们尝试在0x7c00位置设置断点,即b *0x7c00,然后使用c命令,将程序运行到断点处。

对比右侧和左侧的代码可以发现,boot/boot.S被加载到了0x7c00中,我们也可以通过obj/boot/boot.asm查看代码和它的内存空间中的物理地址分布。

考察boot.S的第44行(boot.asm的第61行):

通过注释,可以看到,ljmp $PROT_MODE_CSEG, $protcseg指令后,跳转到了32-bit保护模式,如果我们将断点打在这里,并跳过这一行代码可以发现:

然而,真正造成实模式到保护模式转变的包括两个部分:

  1. 使能A20总线

  2. 设置保护模式flag



完成这两步之后,还需要对保护模式下的各大段寄存器进行初始化,并为C语言运行设置esp,保证C语言运行有栈可用:

最后调用call bootmain进入到boot/main.c中,在boot/main.c中将会读取整个内核镜像到内存中。编译后的内核镜像是一个ELF格式的文件,整个装载过程就是装载该ELF文件的过程(这个文件很复杂,我们只简单看一下装载过程),查看boot/main.c文件:

#define SECTSIZE	512
#define ELFHDR ((struct Elf *) 0x10000) // scratch space void readsect(void*, uint32_t);
void readseg(uint32_t, uint32_t, uint32_t); void
bootmain(void)
{
struct Proghdr *ph, *eph; // 读取磁盘中的第一页(4096 bytes)
readseg((uint32_t) ELFHDR, SECTSIZE*8, 0); // is this a valid ELF?
if (ELFHDR->e_magic != ELF_MAGIC)
goto bad; // load each program segment (ignores ph flags)
ph = (struct Proghdr *) ((uint8_t *) ELFHDR + ELFHDR->e_phoff);
// 根据ELFHeader中的信息,每次读取一个segment,直到读取完毕为止。
eph = ph + ELFHDR->e_phnum;
for (; ph < eph; ph++)
// p_pa is the load address of this segment (as well
// as the physical address)
readseg(ph->p_pa, ph->p_memsz, ph->p_offset); // call the entry point from the ELF header
// note: does not return!
// 进入内核
((void (*)(void)) (ELFHDR->e_entry))(); bad:
outw(0x8A00, 0x8A00);
outw(0x8A00, 0x8E00);
while (1)
/* do nothing */;
}

可以看到,ELF文件的Header中存放了各个segment的信息,boot/main.c根据这些信息将整个内核加载入内存中,最后进入内核。

3. The kernel

扩展内容

通过本次实验,我们了解了6.828的使用的QEMU的整个BOOT流程,真实的Linux是如何启动的呢?笔者考察了《深入理解linux内核》这一著作,其附录一《系统启动》描述了该问题:

  1. BIOS:在开始启动时,有一个特殊的硬件电路在CPU的一个引脚上产生一个RESET逻辑值,在RESET产生以后,就把处理器的一些寄存器(包括cs和eip)设置成固定的值,并执行在物理地址0xfffffff0处找到的代码。硬件把这个地址映射到某个只读、持久的存储芯片(ROM),ROM中存放的程序集在80x86体系中通常叫做基本输入/输出系统(BIOS),因为它包括几个终端驱动的低级过程。所有的操作系统在启动时,都要通过这些过程对计算机硬件进行设备初始化。BIOS的启动过程主要包括4个操作:

    1. 上电自检(POST),对计算机硬件执行一系列的测试,用来检测现在都有什么设备以及这些设备是否正常工作。
    2. 初始化硬件设备。这个阶段在现代基于PCI的体系结构中相当重要,因为它可以保证所有的硬件设备操作不会引起IRQ线和IO端口的冲突。
    3. 搜索一个操作系统来启动,根据BIOS的设置,这个阶段可能要试图访问系统中软盘、硬盘和CD-ROM的第一个扇区(引导扇区)
    4. 只要找到一个有效的设备,就把第一个扇区的内容拷贝到RAM中从物理地址0x00007c00开始的位置,然后跳转到这个地址处,开始执行刚才装载进来的代码。
  2. 引导装入程序:
    1. 为了从软盘启动,必须把第一个扇区中所有存放的指令装载到RAM中,这些指令再把包含内核映像的其他所有扇区拷贝到RAM中。
    2. 从硬盘启动:硬盘的第一个扇区成为主引导记录(Master Boot Record, MBR),该扇区中包括分区表和一个小程序,该小程序用来装载被启动的操作系统所在分区的第一个扇区。(注意:在Linux早期版本(一直到2.4系列),在第一个512字节有一个最小的引导程序,因此在第一个扇区拷贝一个内核映像就可以使软盘可启动,但是到2.6中不再有这样的引导装入程序)。
    3. 从磁盘启动:从磁盘启动Linux内核需要一个两步的引导装入程序,在80x86体系中,众所周知的linux引导装入程序叫Linux Loader(LILO)。LILO引导装入程序被分为两部分,因为不划分的话,它就太大无法装进单个扇区。MBR或者分区引导扇区包括一个小的引导装入程序,由BIOS把这个小程序装入从地址0x00007c00开始的RAM中,这个小程序又把自己移到地址0x00096a00,建立实模式栈(0x00098000-0x000969ff),并把LILO的第二部分装入到从地址0x00096c00开始的RAM中。第二部分又一次从磁盘读取可用操作系统的映射表,并提供给用户一个提示符,用户就可以从中选择一个操作系统,最后用户选择了的操作系统被装入内核后,引导装入程序就可以把相应分区的引导扇区拷贝到RAM并执行它,或者直接把内核映像拷贝到RAM中。

      注意,这里调用BIOS过程从磁盘装载内核映像时,对于小内核映像,装入低地址到0x00010000上,对于大内核映像,则装载到0x00100000上。
  3. 进入内核进行初始化。

MIT 6.828 Lab实验记录 —— lab1 Booting PC的更多相关文章

  1. 《MIT 6.828 Lab 1 Exercise 12》实验报告

    本实验的网站链接:MIT 6.828 Lab 1 Exercise 12. 题目 Exercise 12. Modify your stack backtrace function to displa ...

  2. 《MIT 6.828 Lab 1 Exercise 8》实验报告

    本实验的网站链接:MIT 6.828 Lab 1 Exercise 8. 题目 Exercise 8. Read through kern/printf.c, lib/printfmt.c, and ...

  3. 《MIT 6.828 Lab 1 Exercise 11》实验报告

    本实验的网站链接:MIT 6.828 Lab 1 Exercise 11. 题目 The above exercise should give you the information you need ...

  4. 《MIT 6.828 Lab 1 Exercise 10》实验报告

    本实验的网站链接:MIT 6.828 Lab 1 Exercise 10. 题目 Exercise 10. To become familiar with the C calling conventi ...

  5. 《MIT 6.828 Lab 1 Exercise 3》实验报告

    本实验的网站链接:mit 6.828 lab1 Exercise 3. 题目 Exercise 3. Take a look at the lab tools guide, especially th ...

  6. 《MIT 6.828 Lab 1 Exercise 7》实验报告

    本实验链接:mit 6.828 lab1 Exercise 7. 题目 Exercise 7. Use QEMU and GDB to trace into the JOS kernel and st ...

  7. 《MIT 6.828 Lab 1 Exercise 4》实验报告

    本实验链接:mit 6.828 lab1 Exercise 4. 题目 Exercise 4. Read about programming with pointers in C. The best ...

  8. 《MIT 6.828 Lab 1 Exercise 2》实验报告

    本实验链接:mit 6.828 lab1 Exercise2. 题目 Exercise 2. Use GDB's si (Step Instruction) command to trace into ...

  9. MIT 6.828 Lab 1/ Part 2

    Exercise 03 - obj/boot/boot.asm 反汇编文件 截取asm部分文件并注释理解 # Set up the important data segment registers ( ...

  10. 《MIT 6.828 Lab1: Booting a PC》实验报告

    <MIT 6.828 Lab1: Booting a PC>实验报告 本实验的网站链接见:Lab 1: Booting a PC. 实验内容 熟悉x86汇编语言.QEMU x86仿真器.P ...

随机推荐

  1. Value '0000-00-00 00:00:00' can not be represented as java.sql.Timestamp; nested exception is java.sql.SQLException: Value '0000-00-00 00:00:00' can not be represented as java.sql.Timestamp

    好久没记录了,最近一直在忙着工作以及码前后端 记录下这种大多数人都会碰到但是又不一定找得到问题的错误 1 Value '0000-00-00 00:00:00' can not be represen ...

  2. Dapr在Java中的实践 之 环境准备

    Dapr简介 Dapr (Distributed Application Runtime)是一个可移植的.事件驱动的运行时,它使任何开发人员都可以轻松地构建运行在云和边缘上的弹性.无状态和有状态的应用 ...

  3. 【HarmonyOS】【ArkTS】如何使用HTTP网络请求获取动态数据刷新UI界面

    ​ [关键字] HttpRequest.ArkTS.网络数据请求.@ohos.net.http [前言] 在使用ArkTS开发HarmonyOS应用时,需要调用HTTP网络请求 @ohos.net.h ...

  4. JAVA SE基础《一》----JAVA入门

    初识Java 1.Java背景知识 java是美国sun公司(Stanford University Network)在1995年推出的一门计算机高级编程语言. Java早期称为Oak(橡树),后期改 ...

  5. 洛谷 P8026 [ONTAK2015] Bajtocja

    简要题意 有 \(d\) 张初始为空的无向图,每张中都有 \(n\) 个点,标号从 \(1\) 到 \(n\),\(m\) 次操作,每次往一张图加一条边,并询问有多少有序数对 \((a, b)\) 使 ...

  6. uni-app基于原生input增强选择picker插件 可用于地图定位选位置 页面跳转选数据

    前端基于原生input增强选择picker插件 可用于地图定位选位置 页面跳转选数据, 下载完整代码请访问uni-app插件市场地址: https://ext.dcloud.net.cn/plugin ...

  7. ArrayList 扩容机制

    ArrayList 基本介绍 ArrayList实现了List接口.它可以存储包括null的任何类型的对象,允许重复元素.ArrayList在内部使用一个数组来存储元素,当元素数量超过数组容量时,Ar ...

  8. PRF评价

    PRF评价指标: 精确率P:预测结果正类数量占全部结果的比率: P= $\frac{TP}{TP+FP}$ TP:预测为真且实际为真,FP为预测真实际为假. 召回率R:在所有正类样本中,能回想到的比例 ...

  9. Cisco命令中login和login local的区别

    login是开启远程登录密码验证,login local不但要求密码,还要求提供用户名 如果同时设置login和login local,login local有效 (config-line)#line ...

  10. 基于词袋(Bag of Words)和SVM的图片分类

    目录 摘要 源码及完整报告: 词袋(Bag of Words, BoW) 基于词袋模型的图片分类基本流程 多尺度空间极值点检测 关键点精确定位 关键点主方向计算 生成描述子 特征词典的生成 SVM分类 ...