MIT-OS实验-lab1

Lab 1


Part 1:PC Bootstrap

1、预备知识:

  • BIOS:
    • 第一代PC处理器是16位字长的Intel 8088处理器,这类处理器只能访问1MB的地址空间,即0x00000000-0x000FFFFF。这1MB中0x000F0000——0x00100000被只读内存(ROM)所使用,计算机通电后,第一件事就是读取它。ROM里的程序叫做"基本輸出輸入系統"(Basic Input/Output System),简称为BIOS。
    • SeaBIOS是qemu和kvm的默认BIOS 。在SeaBIOS中会完成虚拟硬件的初始化,中断服务函数的设置,ACPI表、SMBIOS表、E820表等的创建,最后引导启动OS。
  • 实模式与保护模式:
    • 实模式是早期CPU,比如8088处理器的工作模式,这类处理器由于只有20根地址线,所以它们只能访问1MB的内存空间。实模式下的地址由段基址(segment)和偏移(offset)两部分组成,计算公式为:segment * 16 + offset。
    • 但是CPU也在不断的发展,之后的80286/80386已经具备32位地址总线,能够访问4GB内存空间,为了能够很好的管理这么大的内存空间,保护模式被研发出来。在保护模式下,虽然段值仍然由原来的16位cs寄存器指定,但此时这些寄存器中存放的不再是段基址,而是一个索引。从这个索引,可以找到一个表项,里面存放了段基址等很多属性,这个表项称为段描述符,这个表就称为GDT。
    • 现代处理器都是工作在保护模式下的。但是为了实现向后兼容性,即原来运行在8088处理器上的软件仍旧能在现代处理器上运行,现代的CPU都是在启动时运行于实模式,启动完成后运行于保护模式。
    • BIOS就是PC刚启动时运行的软件,所以它必然工作在实模式下。

2、利用GDB跟踪ROM BIOS的指令
首先进入lab所在文件夹,分别在两个终端先后输入如下命令:

Make qemu-nox-gdb;
Make gdb;

执行的BIOS的第一条指令如下:
在这里插入图片描述
这条指令的地址是0xffff0,而最前面的[ f000:fff0 ]源于实模式下的取址方式:0xffff0=0xf0000<<4+0xfff0
在gdb的终端输入si得到下一条指令:
在这里插入图片描述
输入i r esi cs查看esi cs的值:
在这里插入图片描述
所以这条指令意思是将0xffc8和0xf0000地址处的值进行比较。
下一条指令是跳转指令:
在这里插入图片描述
如果ZF标志位为0的时候跳转,即上一条指令cmpw的结果不是0时跳转。
继续输入si,得到下一条指令地址是0xfe066:
在这里插入图片描述
可见上面的跳转指令并没有跳转。这条指令的功能是把edx寄存器清零。
接下来的几条指令对部分寄存器做了一些处理:

[f000:e068]    0xfe068:	mov    %edx,%ss
[f000:e06a]    0xfe06a:	mov    $0x7000,%sp
[f000:e070]    0xfe070:	mov    $0x2d4e,%dx
[f000:e076]    0xfe076:	jmp    0x5575ff02
[f000:ff00]	   0xfff00: cli

最后一句cli表示clear Interrupt,作用是将EFLAGS标志寄存器的IF置为0。
EFLAGS标志寄存器有32位,不同位代表不同标志,如下图所示:
在这里插入图片描述
其中IF表示中断使能标志。

关于中断:中断对于CPU来说,分为外部中断和内部中断。将 IF标志设置为0,屏蔽的是来自硬件的可屏蔽中断请求。在 IF为0期间,只有CPU外部不可屏蔽中断(NMI)引脚上发来的硬件中断请求能得到响应,其它可屏蔽请求不被响应。
启动时的操作是比较关键的,所以肯定是不能被中断的,因此需要这个关中断指令用于关闭那些可以屏蔽的中断。

查看eflags寄存器:
在这里插入图片描述
意思是此时PF ZF位为1,其他位为0,验证了此时IF标志位为0。

接下来几条指令如下所示:

0xfff01:	cld  
0xfff02:	mov    %ax,%cx
0xfff05:	mov    $0x8f,%ax
0xfff0b:	out    %al,$0x70
0xfff0d:	in     $0x71,%al
0xfff0f:	in     $0x92,%al
0xfff11:	or     $0x2,%al																		
0xfff13:	out    %al,$0x92
0xfff15:	mov    %cx,%ax

便于理解,找到seaBios的源代码相应处:

transition32:
            // Disable irqs (and clear direction flag)
            cli
            cld
    
            // Disable nmi
            movl %eax, %ecx
            movl $CMOS_RESET_CODE|NMI_DISABLE_BIT, %eax
            outb %al, $PORT_CMOS_INDEX
            inb $PORT_CMOS_DATA, %al
    
            // enable a20
            inb $PORT_A20, %al
            orb $A20_ENABLE_BIT, %al
            outb %al, $PORT_A20
            movl %ecx, %eax

根据注释,这些指令是为了准备进入32位保护模式:关闭了NMI中断,将A20位即第21个地址线使能。
接下来的源代码是:

transition32_nmi_off:
        // Set segment descriptors
        lidtw %cs:pmode_IDT_info
        lgdtw %cs:rombios32_gdt_48

        // Enable protected mode
        movl %cr0, %ecx
        andl $~(CR0_PG|CR0_CD|CR0_NW), %ecx
        orl $CR0_PE, %ecx
        movl %ecx, %cr0

关注lidt和lgdt两条指令:
在这里插入图片描述

  • LGDT或LIDT的作用是将以源操作数位地址的6 字节的值加载到全局描述符表格寄存器 (GDTR) 或中断描述符表格寄存器 (IDTR)。这6个字节包含全局描述符表格 (GDT) 或中断描述符表格 (IDT) 的基址(线性地址)与限制(表格大小,以字节计)。 2 个低位字节代表限制,4 个高位字节代表基址。
  • GDTR或IDTR寄存器的作用是在相应的全局描述符表格 (GDT) 或中断描述符表格 (IDT) 中进行定位。中断向量表:每一种中断都有自己对应的中断处理程序,这个中断的处理程序的首地址就叫做这个中断的中断向量。IDT表存放所有中断向量的。全局描述符表:与保护模式下32位寻址有关。

lidt这条指令在gdb中对应的是:
在这里插入图片描述
再次查看esi和cs的值:
在这里插入图片描述
所以这条指令的意思是把从0xf0000为起始地址处的6个字节的值加载到全局描述符表格寄存器IDTR中,查看这6个字节:
在这里插入图片描述
所以加载到全局描述符表寄存器(GDTR)的值是0x83c01901243c,并且GDT基址为0x83c01901。但是实模式下能访问的最大虚拟/物理地址为0xfffff,所以这个基址似乎是不对的。

接下来两条汇编指令:

 0xfff2e:	or     $0x1,%cx
 0xfff32:	mov    %ecx,%cr0

计算机中包含CR0~CR3四个控制寄存器,用来控制和确定处理器的操作模式。CR0寄存器的0bit是保护位,当该位被置1,代表开启了保护模式。这个操作明显是要把CR0寄存器的最低位(0bit)置1。

所以目前BIOS干了什么?
关闭中断、加载IDTR/GDTR、开启保护模式……
之后的过程比较复杂, BIOS完成一系列硬件,再进入到Boot phase找到可引导设备,并把操作系统的boot loader加载到内存中开始取址执行。

Part2:the Boot Loader

1、预备知识:

  • 上一部分最后提到BIOS会找到可引导设备,具体地说,BIOS在运行的最后会去检测可以从当前系统的哪个设备中找到操作系统,通常来说是磁盘,也有可能是U盘等等。当BIOS确定了操作系统位于磁盘中,它就会把这个磁盘的第一个扇区,通常把它叫做启动区(boot sector)先加载到内存地址0x7c00~0x7dff中,这个启动区中包括一个非常重要的程序–boot loader,它会负责完成将整个操作系统从磁盘导入内存的工作,以及一些其他的非常重要的配置工作。在这之后操作系统才会开始运行。

2、跟踪 /boot/boot.S文件
由boot.s的开头说明可知这就是boot loader程序。
输入下面指令在地址0x7c00处设置断点,并让程序继续运行到这个断点:

b *0x7c00
c

终端显示:
在这里插入图片描述
说明bootloader的第一条指令是cli——关闭中断。
接下来设置了一些重要的数据段寄存器:

cld              
xorw    %ax,%ax             # Segment number zero
movw    %ax,%ds             # -> Data Segment
movw    %ax,%es             # -> Extra Segment
movw    %ax,%ss             # -> Stack Segment

根据注释,下面又是熟悉的enable A20操作。
为什么又要enable A20?
进入Bootloader之前,有的BIOS可能会在短时间内切换到保护模式,这将允许其使用受保护模式的一些优点(例如32位作为默认地址大小),但是在进入bootloader程序之前会切换回来。

enable A20之后gdb显示下一条指令是:
在这里插入图片描述
意思是把从(%esi)地址起始的6个字节加载到gdtr寄存器中,但是查看esi的值却是0。
而在boot.asm这个反汇编文件,对应的指令是:
在这里插入图片描述
看起来和gdb显示的一样,只是下面接了一条奇怪的指令 fs jl
回到boot.S中,这里的汇编代码是 lgdt gdtdesc,在boot.S最后定义了要加载到gdtr寄存器中的gdtdesc和全局描述符表gdt:
在这里插入图片描述
gdtdesc由一个word(2字节,值为0x17,代表gdt表的大小)和一个long(4字节,值为gdt的起始地址,代表gdt表的基址)组成,正好6字节。
而GDT表的初始化用到了SEG这个宏定义,在mmu.h定义如下:
在这里插入图片描述
所以GDT表中的一项就有8字节,64位。

所以实际执行的绝不是lgdtl (%esi),错误原因可能出在反汇编时,所以直接查看该处的机器码:
0f 01是操作码lgdtl,16是指16位模式,所以gdtdesc的地址由后面两个字节组成:64 7c。又因为小端法,所以地址是0x7c64。在内存中查看该地址的6个字节:
在这里插入图片描述
前2个字节是0x0017,对应.word 0x17,后面四个字节组成的0x7c4c则是gdt表基址。根据前面分析,gdt表一项就有8个字节,而初始化时有3项,所以查看0x7c4c为起始的后24个字节:
在这里插入图片描述

关于gdt表的几个问题:
  • 这些数字要如何理解呢?
    1个gdt表项的64位有不同的含义,如下图所示:
    在这里插入图片描述
    在gdt表中的三个表项,分别代表三个段:null seg,

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值