MIT 6.828学习笔记(lab1)

MIT 6.828学习笔记

本系列为MIT经典课程,6.828的学习笔记,会在该系列笔记中记载整个课程的学习笔记和作业,实验代码。

环境的搭建

本课程需要两套工具,一个是X86系统仿真器QEMU,另一个是编译器工具链(一般系统自带),可以通过以下命令来检查是否安装了编译器工具链:

% objdump -i

显示结果中应该含有elf32-i386

% gcc -m32 -print-libgcc-file-name

显示结果中应该含有 /usr/lib/gcc/i486-linux-gnu/version/libgcc.a or
/usr/lib/gcc/x86_64-linux-gnu/version/32/libgcc.a。

QEMU的安装

环境的搭建主要是安装x86仿真器,QEMU。QEMU是一种现代,快速的PC模拟器。构建自己的修补版本的QEMU:

git clone https://github.com/mit-pdos/6.828-qemu.git qemu 

接着安装需要的几个依赖文件:libsdl1.2-dev, libtool-bin, libglib2.0-dev, libz-dev, and libpixman-1-dev。

./configure --disable-kvm --disable-werror [–prefix=PFX] [–target-list=“i386-softmmu x86_64-softmmu”]

make && make install. 即可

Lab1 Booting A PC

part1 PC Bootstrap

在这里插入图片描述
硬件保留了从0x000A0000到0x000FFFFF的384KB区域,用于特殊用途,此保留区中最重要的部分是基本输入/输出系统(BIOS),它占用从0x000F0000到0x000FFFFF的64KB区域。 在早期的PC中,BIOS被保存在真正的只读存储器(ROM)中,但是当前的PC将BIOS存储在可更新的闪存中。

BIOS的作用: BIOS负责执行基本的系统初始化,例如激活视频卡和检查已安装的内存量。 执行此初始化后,BIOS从某个适当的位置(例如软盘,硬盘,CD-ROM或网络)加载操作系统,并将计算机的控制权传递给操作系统。

Lab2 The boot loader

PC机的软盘和硬盘被分为一个个512B的扇区,扇区是磁盘的最小传输粒度,每次读写的单位都必须是一个或多个扇区,并且必须在扇区边界上对齐。

现代BIOS可以使用CD-ROM进行引导,CD-ROM使用的是2048MB而不是512MB,可以将更大的磁盘映像从硬盘加载到内存。

对实际加载程序进行剖析,加载程序包含一个汇编程序boot.s和一个C语言程序main.c。

boot/boot.s汇编文件的运行流程:
1.设置code段段选择器和data段段选择器,段选择器的理解:段选择器可以理解为描述符数组(GDT或LDT)的索引,但与索引不同的是,selector = index + table_to_use + privilege。
在这里插入图片描述
及段选择器中除了存描述符的索引,还要存TI(通过TI判断是采用的GDT还是LDT),和RPL(请求等级)。

2.在默认情况下,将1MB地址换为0。执行操作取消这个转换。

3.使用GDT将实模式切换为保护模式。

4.切换到32代码段,继续执行指令,同时将CPU切换为32位模式

5.设置保护模式下的段寄存器

6.设置堆栈指针,调用bootmain函数,bootmain函数在main.c文件中

接着对main.c文件的流程进行梳理:
main.c中主要包含了bootmain函数,该函数的唯一功能就是启动内核映像。

1.读取硬盘的第一个扇区,并判断该扇区的ELF是否是合法的ELF

2.通过检查后,加载每一个段

3.需要注意的是如果没有通过检查,会执行goto bad,这时候会有返回值,而成功加载时不会有返回值。(返回值会交给boot.s最后的汇编语言来处理,汇编语言会执行一个死循环)

C语言指针的学习

对pointer.c文件的学习:

c = (int *) ((char *) c + 1);
    *c = 500;
    printf("5: a[0] = %d, a[1] = %d, a[2] = %d, a[3] = %d\n",
    a[0], a[1], a[2], a[3]);

输出结果为:
5: a[0] = 200, a[1] = 128144, a[2] = 256, a[3] = 302

为什么此处a[1]和a[2]的结果看起来很奇怪呢? 主要是因为此处给c赋值时采用的是 (char *)c+1而不是(int *)c+1,现在详细分析:

a的起始地址为0x0062FDC0,此时c指向a[1](前面的语句将c指向了a[1].)

原来a[1]的地址为0x0062FDC4(int在32位机上为4个字节,而char为一个字节)

则使用(char *)c+1后,c指向的地址为0x0062FDC5。一个int类型的数字用4位地址存放,因为int为4个字节32位,一个地址可以放
8位(一个地址对应一个存储单元,一个存储单元8个字节,8位),因此是4个地址存放int数。

a[1]原来存放的值为400,十六进制为0x00000190。存放情况为:

0x0062FDC5 0x0062FDC6 0x0062FDC7 0x0062FDC8
90 01 00 00

a[2]原来存放的值为301,十六进制为0x0000012D。存放情况为:

0x0062FDC9 0x0062FDCA 0x0062FDCB 0x0062FDCC
2D 01 00 00

执行 *c=500后,十六进制为0x000001F4,存放情况变为:

0x0062FDC5 0x0062FDC6 0x0062FDC7 0x0062FDC8
90 F4 01 00

0x0062FDC9 0x0062FDCA 0x0062FDCB 0x0062FDCC
00 01 00 00

而0x0001F490=128144,0x00000100=256,因此得到上面的结果:
5: a[0] = 200, a[1] = 128144, a[2] = 256, a[3] = 302

对于ELF的理解:可以将ELF可执行文件视为具有加载信息的标头,然后是几个程序段,每个程序段都是要在指定地址加载到内存中的连续代码或数据块。ELF二进制文件以固定长度的ELF头开头,后跟可变长度的程序头,列出要加载的每个程序段。

使用objdump -h obj/kern/kernel命令可以显示这些程序段:
在这里插入图片描述
可以看到有VMA(link address)和LMA(load address)两项。

VMA可以理解为逻辑地址,LMA理解为实际物理地址。

在boot loader(引导加载程序中)VMA和LMA一般是相同的,但是在内核中VMA和LMA一般是不相同的。

objdump -f obj/kern/kernel命令可以查看entry point
在这里插入图片描述
现在我们可以更好地理解 boot/main.c程序了
(1)它实际上是加载elf头中列出的每个需要加载的程序段到内存中:

ph = (struct Proghdr *) ((uint8_t *) ELFHDR + ELFHDR->e_phoff);
 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);

(2)然后跳到内核的入口地址处:

((void (*)(void)) (ELFHDR->e_entry))();
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值