mit 6.828 lab 代码和笔记,以及中文注释源代码已放置在github中:
https://github.com/yunwei37/xv6-labs
init
-
setup
实验内容采用git分发:
git clone https://pdos.csail.mit.edu/6.828/2018/jos.git lab
测试的话可以使用:
make grade
Part 1: PC Bootstrap
-
需要了解x86汇编以及内联汇编的写法,参看:
http://www.delorie.com/djgpp/doc/brennan/brennan_att_inline_djgpp.html
https://pdos.csail.mit.edu/6.828/2018/readings/pcasm-book.pdf -
运行 qemu
cd lab make make qemu
-
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
-
使用 gdb 调试qemu:
打开新的窗口:
cd lab
make qemu-gdb
在另外一个终端:
make
make gdb
开始使用gdb调试,首先进入实模式;
- IBM PC从物理地址0x000ffff0开始执行,该地址位于为ROM BIOS保留的64KB区域的最顶部。
- PC从CS = 0xf000和IP = 0xfff0开始执行。
- 要执行的第一条指令是jmp指令,它跳转到分段地址 CS = 0xf000和IP = 0xe05b。
物理地址 = 16 *网段 + 偏移量
然后,BIOS所做的第一件事就是jmp倒退到BIOS中的较早位置;
Part 2: The Boot Loader 引导加载程序
PC的软盘和硬盘分为512个字节的区域,称为扇区。
当BIOS找到可引导的软盘或硬盘时,它将512字节的引导扇区加载到物理地址0x7c00至0x7dff的内存中,然后使用jmp指令将CS:IP设置为0000:7c00,将控制权传递给引导程序装载机。
引导加载程序必须执行的两个主要功能:
- 将处理器从实模式切换到 32位保护模式;
- 通过x86的特殊I / O指令直接访问IDE磁盘设备寄存器,从硬盘读取内核;
引导加载程序的源代码:
boot/boot.S
#include <inc/mmu.h>
# 启动CPU:切换到32位保护模式,跳至C代码;
# BIOS将该代码从硬盘的第一个扇区加载到
# 物理地址为0x7c00的内存,并开始以实模式执行
# %cs=0 %ip=7c00.
.set PROT_MODE_CSEG, 0x8 # 内核代码段选择器
.set PROT_MODE_DSEG, 0x10 # 内核数据段选择器
.set CR0_PE_ON, 0x1 # 保护模式启用标志
.globl start
start:
.code16 # 汇编为16位模式
cli # 禁用中断
cld # 字符串操作增量,将标志寄存器Flag的方向标志位DF清零。
# 在字串操作中使变址寄存器SI或DI的地址指针自动增加,字串处理由前往后。
# 设置重要的数据段寄存器(DS,ES,SS)
xorw %ax,%ax # 第零段
movw %ax,%ds # ->数据段
movw %ax,%es # ->额外段
movw %ax,%ss # ->堆栈段
# 启用A20:
# 为了与最早的PC向后兼容,物理
# 地址线20绑在低电平,因此地址高于
# 1MB会被默认返回从零开始。 这边代码撤消了此操作。
seta20.1:
inb $0x64,%al # 等待其不忙状态
testb $0x2,%al
jnz seta20.1
movb $0xd1,%al # 0xd1 -> 端口 0x64
outb %al,$0x64
seta20.2:
inb $0x64,%al # 等待其不忙状态
testb $0x2,%al
jnz seta20.2
movb $0xdf,%al # 0xdf -> 端口 0x60
outb %al,$0x60
# 使用引导GDT从实模式切换到保护模式
# 并使用段转换以保证虚拟地址和它们的物理地址相同
# 因此
# 有效内存映射在切换期间不会更改。
lgdt gdtdesc
movl %cr0, %eax
orl $CR0_PE_ON, %eax
movl %eax, %cr0
# 跳转到下一条指令,但还是在32位代码段中。
# 将处理器切换为32位指令模式。
ljmp $PROT_MODE_CSEG, $protcseg
.code32 # 32位模式汇编
protcseg:
# 设置保护模式数据段寄存器
movw $PROT_MODE_DSEG, %ax # 我们的数据段选择器
movw %ax, %ds # -> DS: 数据段
movw %ax, %es # -> ES:额外段
movw %ax, %fs # -> FS
movw %ax, %gs # -> GS
movw