操作系统16----bootloader保护模式源码分析

                                              bootloader保护模式源码分析

环境准备

安装VirtualBox 虚拟机软件  

安装Ubuntu镜像    

项目组成结构

系统启动过程​​

BIOS启动过程

bootloader启动过程

qemu和gdb调试ucore

bootloader 进入保护模式过程


以清华大学ucore操作系统学习课程作为学习目标,其学习资料可以参考:

清华大学操作系统课程  https://github.com/chyyuu/os_course_info

清华大学计算机系OS课程主站   http://os.cs.tsinghua.edu.cn/oscourse/OS2018spring/

uCore OS实验指导书和源码网址 (2019)  https://chyyuu.gitbooks.io/ucore_os_docs/

学堂在线原理和实验课视频  http://www.xuetangx.com/courses/course-v1:TsinghuaX+30240243X+sp/info

实验源码与答案  https://github.com/chyyuu/ucore_os_lab

环境准备

具体来说,在ubuntu系统中使用qemu来作为x86模拟运行器,加载ucore操作系统特定版代码来分析操作系统启动过程。

如果使用Windows环境,可以通过VirtualBox来安装Ubuntu虚拟机来运行模拟环境,也可以直接安装Ubuntu操作系统。

安装VirtualBox 虚拟机软件  

https://www.virtualbox.org/wiki/Downloads

安装Ubuntu镜像    

https://pan.baidu.com/s/11zjRK

已经制作好一个运行环境Ubuntu镜像,可以直接下载在VirtualBox中运行。

启动Ubuntu系统,默认用户名为moocos,root用户密码为一个空格。

课程安排设计8个实验,其中实验1主要是系统软件启动过程,实验1源码位置

项目组成结构

主要包含boot,kern,libs,tools等。

boot  boot主要实现bootloader

boot/bootasm.S : 定义并实现了bootloader最先执行的函数start, 此函数进行了一定的初始化, 完成了从实模式到保护模式的转换, 并调用bootmain.c中的bootmain函数。
boot/bootmain.c: 定义并实现了bootmain函数实现了通过屏幕、 串口和并口显示字符串。 bootmain函数加载ucore操作系统到内存, 然后跳转到ucore的入口处执行。
boot/asm.h: 是bootasm.S汇编文件所需要的头文件, 主要是一些与X86保护模式的段访问方式相关的宏定义。
kern kern主要是关于操作系统核心代码实现

init是系统初始化部分,mm为内存管理部分,driver为外设驱动部分,trap为中断处理部分,debug为内核调试部分。


系统启动过程

系统加电,CPU从物理地址0xFFFFFFF0( 由初始化的CS: EIP确定, 此时CS和IP的值分别是0xF000和0xFFF0)) 开始执行。 在0xFFFFFFF0这里只是存放了一条跳转指令, 通过跳转指令跳到BIOS例行程序起始点。 BIOS做完计算机硬件自检和初始化后, 会选择一个启动设备( 例如软盘、 硬盘、 光盘等) , 并且读取该设备的第一扇区(即主引导扇区或启动扇区)到内存一个特定的地址0x7c00处, 然后CPU控制权会转移到那个地址继续执行。

BIOS启动过程

BIOS是被固化在计算机ROM( 只读存储器) 芯片上的一个特殊的软件, 为上层软件提供最底层的、 最直接的硬件控制与支持。

PC加电后, CS寄存器初始化为0xF000, IP寄存器初始化为0xFFF0, 所以CPU要执行的第一条指令的地址为CS:IP=0xF000:0XFFF0( Segment:Offset表示) =0xFFFF0( Linear表示) 。 这个地址位于被固化EPROM中, 指令是一个长跳转指令 JMP F000:E05B 。 这样就开启了BIOS的执行过程。初始化硬件设备、 建立系统的内存空间映射图, 从而将系统的软硬件环境带到一个合适的状态, 以便为最终调用操作系统内核准备好正确的环境。 最终引导加载程序把操作系统内核映像加载到RAM中, 并将系统控制权传递给它。

bootloader启动过程

BIOS将通过读取硬盘主引导扇区到内存, 并转跳到对应内存中的位置执行bootloader,bootloader主要功能:

切换到保护模式,启用分段机制;读磁盘中ELF执行文件格式的ucore操作系统到内存;显示字符串;把控制权交给ucore操作系统;


qemu和gdb调试ucore

从 CPU 加电后执行的第一条指令开始,单步跟踪 BIOS 的执行。

修改 lab1/tools/gdbinit,内容为

set architecture i8086
target remote :1234

在 lab1目录下,执行  make debug,在看到gdb的调试界面(gdb)后,在gdb调试界面下执行如下命令si ,即可单步跟踪BIOS。

在gdb界面下,可通过如下命令来看BIOS的代码

```
 x /2i $pc  //显示当前eip处的汇编指令
```

改写Makefile文件

  debug: $(UCOREIMG)
        $(V)$(TERMINAL) -e "$(QEMU) -S -s -d in_asm -D $(BINDIR)/q.log -parallel stdio -hda $< -serial null"
        $(V)sleep 2
        $(V)$(TERMINAL) -e "gdb -q -tui -x tools/gdbinit"

在调用qemu时增加`-d in_asm -D q.log`参数,便可以将运行的汇编指令保存在q.log中。

由q.log中汇编指令可知,CPU上电,从0xfff0处开始执行,此位置时一条长跳转指令,跳转到0xe05b

在bootloader初始化位置0x7c00 设置实地址断点,测试断点正常。

在tools/gdbinit结尾加上

    set architecture i8086  //设置当前调试的CPU是8086
    b *0x7c00  //在0x7c00处设置断点。此地址是bootloader入口点地址,可看boot/bootasm.S的start地址处
    c          //continue简称,表示继续执行
    x /2i $pc  //显示当前eip处的汇编指令
    set architecture i386  //设置当前调试的CPU是80386    

运行 "make debug" 便可得到

可知从0x7c00处开始执行bootloader代码,即开始执行boot/bootasm.S中汇编代码。


bootloader 进入保护模式过程

代码执行到0x7c00处,进行以下操作

1.清理环境:将flag置0和将段寄存器置0

.code16                                             # Assemble for 16-bit mode
    cli                                             # Disable interrupts
    cld                                             # String operations increment

    # Set up the important data segment registers (DS, ES, SS).
    xorw %ax, %ax                                   # Segment number zero
    movw %ax, %ds                                   # -> Data Segment
    movw %ax, %es                                   # -> Extra Segment
    movw %ax, %ss                                   # -> Stack Segment

CLI指令作用

用于禁止中断,具体来说使eflags中IF标志位=0,禁止响应可屏蔽硬件中断。CLI禁止中断发生,STL允许中断发生,这两个指令只能在内核模式下执行,不可以在用户模式下执行;

CLD指令作用

在计算机中,大部分数据存放在主存 中,8086CPU提供了一组处理主存中连续存放的数据串的指令——串操作指令。串操作指令中,源操作数用寄存器SI寻址,默认在数据段DS中,但允许段 超越;目的操作数用寄存器DI寻址,默认在附加段ES中,不允许段超越。每执行一次串操作指令,作为源地址指针的SI和作为目的地址指针的DI将自动修 改:+/-1(对于字节串)或+/-2(对于字串)。地址指针时增加还是减少取决于方向标志DF。在系统初始化后或者执行指令CLD指令后,DF=0,此时地址指针增1或2;在执行指令STD后,DF=1,此时地址指针减1或2。

2.开启A20:通过将键盘控制器上的A20线置于高电位,全部32条地址线可用,可以访问4G的内存空间;

关于A20 Gate   http://hengch.blog.163.com/blog/static/107800672009013104623747/

关于A20 Gate详细介绍可以参考  https://blog.csdn.net/u014106644/article/details/97002252/

# Enable A20:
    #  For backwards compatibility with the earliest PCs, physical
    #  address line 20 is tied low, so that addresses higher than
    #  1MB wrap around to zero by default. This code undoes this.
seta20.1:
    inb $0x64, %al                                  # Wait for not busy(8042 input buffer empty).
    testb $0x2, %al
    jnz seta20.1

    movb $0xd1, %al                                 # 0xd1 -> port 0x64
    outb %al, $0x64                                 # 0xd1 means: write data to 8042's P2 port

seta20.2:
    inb $0x64, %al                                  # Wait for not busy(8042 input buffer empty).
    testb $0x2, %al
    jnz seta20.2

    movb $0xdf, %al                                 # 0xdf -> port 0x60
    outb %al, $0x60                                 # 0xdf = 11011111, means set P2's A20 bit(the 1 bit) to 1

3.初始化GDT表:一个简单的GDT表和其描述符已经静态储存在引导区中,载入即可;

lgdt gdtdesc

通过lgdt汇编指令可以把GDTR描述符表的大小和起始位置存入gdtr寄存器中,指令格式如下:

      lgdt      [描述段描述符表的地址]

GDTR寄存器保存了GDT的32位基地址和16位表界限,基地址指GDT的0字节的线性地址,表界限指表中的字节个数,LGDT和SGDT指令用来分别装载和保存GDTR寄存器。对于保护模式的操作,作为处理器初始化过程一部分,一个新基地址必须装入GDTR。

全局描述符表及其定义如下所示,一共定义了三个段,第一个为空段,第二个为代码段,第三个为数据段。

# Bootstrap GDT
.p2align 2                                          # force 4 byte alignment
gdt:
    SEG_NULLASM                                     # null seg
    SEG_ASM(STA_X|STA_R, 0x0, 0xffffffff)           # code seg for bootloader and kernel
    SEG_ASM(STA_W, 0x0, 0xffffffff)                 # data seg for bootloader and kernel

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

4.进入保护模式:通过将cr0寄存器PE位置1便开启了保护模式;

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

其中CR0_PE_ON定义如下

.set CR0_PE_ON,             0x1                     # protected mode enable flag

CR0为控制寄存器,其中第0位PE控制处理器启用保护模式

当PE=1时,处理器处于保护模式,启用段机制;PG为分页机制启用位,当PE=1,并且PG=1时,此时处理器启用分段,分页模式,逻辑地址到物理地址的转换需要经过段处理以及页处理。

5.通过长跳转更新cs的基地址;

# Jump to next instruction, but in 32-bit code segment.
    # Switches processor into 32-bit mode.
    ljmp $PROT_MODE_CSEG, $protcseg
.set PROT_MODE_CSEG,        0x8                     # kernel code segment selector

将kernel代码段的基地址更新到CS寄存器

6.设置段寄存器,并建立堆栈;

.set PROT_MODE_DSEG,        0x10                    # kernel data segment selector
protcseg:
    # Set up the protected-mode data segment registers
    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

    # Set up the stack pointer and call into C. The stack region is from 0--start(0x7c00)
    movl $0x0, %ebp
    movl $start, %esp

将kernel数据段基地址更新到数据段寄存器DS,ES,SS中。ebp指向栈顶,esp指向栈底。

7.转到保护模式完成,进入boot主方法;

 call bootmain

bootmain方法则从硬盘开始加载操作系统文件。

bootasm.S完整汇编代码如下:

#include <asm.h>

# Start the CPU: switch to 32-bit protected mode, jump into C.
# The BIOS loads this code from the first sector of the hard disk into
# memory at physical address 0x7c00 and starts executing in real mode
# with %cs=0 %ip=7c00.

.set PROT_MODE_CSEG,        0x8                     # kernel code segment selector
.set PROT_MODE_DSEG,        0x10                    # kernel data segment selector
.set CR0_PE_ON,             0x1                     # protected mode enable flag

# start address should be 0:7c00, in real mode, the beginning address of the running bootloader
.globl start
start:
.code16                                             # Assemble for 16-bit mode
    cli                                             # Disable interrupts
    cld                                             # String operations increment

    # Set up the important data segment registers (DS, ES, SS).
    xorw %ax, %ax                                   # Segment number zero
    movw %ax, %ds                                   # -> Data Segment
    movw %ax, %es                                   # -> Extra Segment
    movw %ax, %ss                                   # -> Stack Segment

    # Enable A20:
    #  For backwards compatibility with the earliest PCs, physical
    #  address line 20 is tied low, so that addresses higher than
    #  1MB wrap around to zero by default. This code undoes this.
seta20.1:
    inb $0x64, %al                                  # Wait for not busy(8042 input buffer empty).
    testb $0x2, %al
    jnz seta20.1

    movb $0xd1, %al                                 # 0xd1 -> port 0x64
    outb %al, $0x64                                 # 0xd1 means: write data to 8042's P2 port

seta20.2:
    inb $0x64, %al                                  # Wait for not busy(8042 input buffer empty).
    testb $0x2, %al
    jnz seta20.2

    movb $0xdf, %al                                 # 0xdf -> port 0x60
    outb %al, $0x60                                 # 0xdf = 11011111, means set P2's A20 bit(the 1 bit) to 1

    # Switch from real to protected mode, using a bootstrap GDT
    # and segment translation that makes virtual addresses
    # identical to physical addresses, so that the
    # effective memory map does not change during the switch.
    lgdt gdtdesc
    movl %cr0, %eax
    orl $CR0_PE_ON, %eax
    movl %eax, %cr0

    # Jump to next instruction, but in 32-bit code segment.
    # Switches processor into 32-bit mode.
    ljmp $PROT_MODE_CSEG, $protcseg

.code32                                             # Assemble for 32-bit mode
protcseg:
    # Set up the protected-mode data segment registers
    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

    # Set up the stack pointer and call into C. The stack region is from 0--start(0x7c00)
    movl $0x0, %ebp
    movl $start, %esp
    call bootmain

    # If bootmain returns (it shouldn't), loop.
spin:
    jmp spin

# Bootstrap GDT
.p2align 2                                          # force 4 byte alignment
gdt:
    SEG_NULLASM                                     # null seg
    SEG_ASM(STA_X|STA_R, 0x0, 0xffffffff)           # code seg for bootloader and kernel
    SEG_ASM(STA_W, 0x0, 0xffffffff)                 # data seg for bootloader and kernel

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

参考资料

清华大学操作系统课程  https://github.com/chyyuu/os_course_info

清华大学计算机系OS课程主站   http://os.cs.tsinghua.edu.cn/oscourse/OS2018spring/

uCore OS实验指导书和源码网址 (2019)  https://chyyuu.gitbooks.io/ucore_os_docs/

学堂在线原理和实验课视频  http://www.xuetangx.com/courses/course-v1:TsinghuaX+30240243X+sp/info

实验源码与答案  https://github.com/chyyuu/ucore_os_lab

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值