JOS lab1 boot 加载操作系统

本文详细介绍了MIT 6.828课程的第一个实验,涉及PC的引导加载过程。从BIOS加载bootloader到实模式切换到保护模式,再到内核的加载。分析了boot.S如何开启保护模式,boot/main.c如何将kernel搬运到内存,以及kern/entry.S中初步的页表设置和进入内核的步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

MIT 6.828课程正式开始 :-D 撒花


Part 1:PC Bootstrap

这一部分主要介绍如何用qemu和gdb联调kernel :-D。
打开两个terminal,都进入到lab目录,然后其中一个输入make qemu-gdb,另一个输入make gdb,即可,你可以看到下面的画面:
这里写图片描述

刚启动时,计算机处于实模式。可以看到,当机器刚上电时候,此时PC指向的地址是0xffff0,这是硬件工程师故意这样做的。第一条指令是一个长跳转指令,这条指令是跳向BIOS的开头。你可以继续使用SI 命令继续逐条观察,这部分都是BIOS做的工作,大概是通过IO check 各种硬件(reference 里的手册真长),比较的晦涩(以及无聊,所以我大概看了看就略过了…)。

Part 2: The bootloader

BIOS做的最后一件事情是

Eventually, when it finds a bootable disk, the BIOS reads the boot loader from the disk and transfers control to it.

就是BIOS帮我们读了一个扇区(sector)的代码到0x7c00 这个地方,然后跳转到这个地方。这个扇区的代码就是JOS的bootloader。

JOS的bootloader有两部分,先是一段汇编代码,在 boot/boot.S里面,然后是一个C源代码,在boot/main.c。


我们依次来分析这两段代码。

boot/boot.S 从实模式到保护模式

正如boot.S里的注释第一句

Start the CPU: switch to 32-bit protected mode, jump into C.

这段代码的功能就是从实模式切换到保护模式。
我们跳过前面的一些准备代码(tips:boot.S这段代码里有一段是A20的设置,跳过他。
),直接看到48行左右,这才是关键

  lgdt    gdtdesc
  movl    %cr0, %eax
  orl     $CR0_PE_ON, %eax
  movl    %eax, %cr0

其中第一行代码是加载一个段描述符表,这是进入保护模式前必要的一步,因为保护模式的内存机制以段机制为基础(在加上页机制),具体可以参考我写的这篇: JOS Lab2 保护模式下的内存映射机制:段机制 页机制。

这里的gdtdesc定义在最后面

gdt:
  SEG_NULL              # null seg
  SEG(STA_X|STA_R, 0x0, 0xffffffff) # code seg
  SEG(STA_W, 0x0, 0xffffffff)           # data seg

gdtdesc:
  .word   0x17                            # sizeof(gdt) - 1
  .long   gdt                             # address gdt

这是一个最简单版本的段描述符,仅仅把内存分为数据段(data seg)和代码段(code seg)。

接下来三句代码,实现的功能就是实模式到保护模式。(其实就是改变了CR0寄存器里的一位,从0变成了1。)
关于CR0寄存器,自己去wiki看啦。

  movl    %cr0, %eax
  orl     $CR0_PE_ON, %eax
  movl    %eax, %cr0

然后通过一个ljmp跳到了prtcseg这一段代码里

  movw    $PROT_MODE_DSEG, %ax    # Our data segment selector
  movw    %ax, %ds                # -> DS: Data Segment
  movw    %ax, %es                # -> ES: Extra Segment
  movw    %ax, %fs                # -> FS
  movw    %ax, %gs                # -> GS
  movw    %ax, %ss                # -> SS: Stack Segment

随便设了一些segment的base值,下一句代码

  movl    $start, %esp

这句代码很重要,这是进入保护模式之后第一个栈顶,$start的地址大概在0x7c00 后多一些,这个随便取的,在正式的设定之前,0-start 这段空间做为栈,应该足够了:-D。
有了栈,我们可以调用函数了!
于是下一句 call bootmain我们进入main.c

boot/main.c 勤劳的搬运工
#define ELFHDR      ((struct Elf *) 0x10000) // scratch space

首先定义了一个ELFHDR的量,ELF文件的头文件。ELF是一类文件格式的名称,我们的kernel就是ELF,后续还会有很多地方会用到这个ELF,所以你有必要搞明白他,越早越好->wiki是你的朋友。
反正这一段代码就是根据elf hear里的信息,一段一段的把kernel的代码搬运到内存,从0x10000(1M)(这里还是物理内存地址)开始。理解起来挺容易,中间的一个强制类型转换可能会引起困扰:

    ph = (struct Proghdr *) ((uint8_t *) ELFHDR + ELFHDR->e_phoff);

其中的(uint8_t *)是为了让ELFHDR指针每+1增加1而不是32位指针默认的+4。
最后一行((void (*)(void)) (ELFHDR->e_entry))();在elfhdr中,定义了一个入口地址,call它,我们就开始运行kernel的第一行代码,从这里开始bootloader结束。

kern/entry.S 内核!内核!

哈哈哈哈,我们从boot文件夹跳到了kern文件夹,这意味着,我们终于进入内核啦!撒花~

冷静下来继续读代码。

    movl    $(RELOC(entry_pgdir)), %eax
    movl    %eax, %cr3
    # Turn on paging.
    movl    %cr0, %eax
    orl $(CR0_PE|CR0_PG|CR0_WP), %eax
    movl    %eax, %cr0

由于虚拟内存机制还没建立(lab2做这事儿),所以老师帮我们手写了4MB的页表,在kern/entrypgdir.c里面,大概功能就是把1M-5M这段物理地址同时映射到从0x10000和0xF0000000开始的高(虚拟)地址中(我们的内核将来就处于从这里开始的256M空间中。)把entry_pgdir加载到cr3寄存器(用来存放页表一级目录基地址的寄存器)。然后打开cr0的页机制(同时打开的还有wp,自己wiki cr0吧…)。

Now paging is enabled, but we’re still running at a low EIP
(why is this okay?).

注释里这样问,为什么可以呢?因为…老师帮我们同时map了原来的地址和加上0xf0000000两段,也就是在虚拟内存里有2个4M空间(好绕口…我实在不知道怎么表述)。

    movl    $0x0,%ebp          # nuke frame pointer

    # Set the stack pointer
    movl    $(bootstacktop),%esp

    # now to C code
    call    i386_init

这里建立了虚拟地址下的栈基地址(随便定的啦…就定0好了)和栈顶地址bootstacktop。这里是建立了保护模式下(简单的)虚拟内存机制(lab2建立完整的)后的栈。于是我们可以调用函数啦,:-D。
于是我们就

    call    i386_init

开始初始化工作,基本就没有lab1什么事了,到lab4之前基本上都在完善i386_init里的内容,建立内存机制,建立进程(JOS里叫运行环境environment),建立多CPU啦啦啦~

-EOF

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值