lab1练习

##实验目的
完成一个能够切换到x86的保护模式并显示字符的bootloader,为启动操作系统ucore做准备。
lab1中包含一个bootloader和一个OS。这个bootloader可以切换到X86保护模式,能够读磁盘并加载ELF执行文件格式,并显示字符。而这lab1中的OS只是一个可以处理时钟中断和显示字符的幼儿园级别OS
lab1主要分为6个练习

###练习1:理解通过make生成执行文件的过程。

  1. 操作系统镜像文件 ucore.img 是如何一步一步生成的?
  2. 一个被系统认为是符合规范的硬盘主引导扇区的特征是什么?
    操作系统镜像文件 ucore.img 是如何一步一步生成的?
    在这里插入图片描述调用GCC把一些C的源代码编译成.O文件(目标文件)
    在这里插入图片描述
    ld把编译成的目标文件转换成一个执行程序,上图中bootblock.out即为BootLoader的一个执行程序
    在这里插入图片描述
    dd将BootLoader放到一个虚拟硬盘中去,上图中ucore.img count就是生成的虚拟硬盘
    硬件模拟器就会基于这个虚拟硬盘中的数据执行相应代码
    在这里插入图片描述
    一共生成两个软件,一个是BootLoader,一个是kernel,kernel是ucore的组成部分
    一个被系统认为是符合规范的硬盘主引导扇区的特征是什么?
    在这里插入图片描述
    磁盘主引导扇区只有512字节
  • 磁盘最后两个字节为0x55AA
  • 由不超过466字节的启动代码和不超过64字节的硬盘分区表加上两个字节的结束符组成

###练习2使用qemu执行并调试lab1中的软件
从CPU加电后执行的第一条指令开始,单步跟踪BIOS的执行。
在初始化位置0x7c00设置实地址断点,测试断点正常。
从0x7c00开始跟踪代码运行,将单步跟踪反汇编得到的代码与bootasm.S和 bootblock.asm进行比较。
自己找一个bootloader或内核中的代码位置,设置断点并进行测试。

在makefile中执行lab1-mon
在这里插入图片描述
让qemu将他执行的指令记录下来,放到上图位置的q.log处,结合gdb,debug,使可以调试正在执行的BootLoader
在这里插入图片描述
GDB能够识别的一些命令,1 加载并kernel,2 与qemu进行连接 6 显示指令指针寄存器的内容
BootLoader第一条指令在0x7c00处
在这里插入图片描述
练习3
分析bootloader是如何完成从实模式进入保护模式的。
为何开启A20,以及如何开启A20
如何初始化GDT表
如何使能和进入保护模式
分析BootLoader:
1、关闭中断,将各个段寄存器重置
在这里插入图片描述
首先将各个寄存器置0
2、开启A20
什么是A20:当 A20 地址线控制禁止时,则程序就像在 8086 中运行,1MB 以上的地址是不可访问的。而在保护模式下 A20 地址线控制是要打开的,所以需要通过将键盘控制器上的A20线置于高电位,使得全部32条地址线可用。
在这里插入图片描述
3、加载GDT表(全局描述符表)
在这里插入图片描述
4、将CR0的第0位置1
在这里插入图片描述
5、长跳转到32位代码段,重装CS和EIP
在这里插入图片描述
6、重装DS、ES等段寄存器等
在这里插入图片描述
7、转到保护模式完成,进入boot主方法
在这里插入图片描述
练习4
分析bootloader加载ELF格式的OS的过程

  1. bootloader如何读取硬盘扇区的?
  2. bootloader是如何加载 ELF格式的 OS?
    在这里插入图片描述
    bootloader读取硬盘扇区
    根据上述bootmain函数分析,首先是由readseg函数读取硬盘扇区,而readseg函数则循环调用了真正读取硬盘扇区的函数readsect来每次读出一个扇区
    在这里插入图片描述
    bootloader加载 ELF格式的 OS
    读取完磁盘之后,开始加载ELF格式的文件。
    在这里插入图片描述
    练习1——练习4都不需要写代码,只是对流程进行分析
    练习5与练习6就要开始写代码了

练习5实现函数调用堆栈跟踪函数 (需要编程)
1、函数调用的原理:
(1)ESP:栈指针寄存器(extended stack pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶。
(2)EBP:基址指针寄存器(extended base pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的底部。
在这里插入图片描述
eip寄存器存储着我们cpu要读取指令的地址
函数调用大概包括以下几个步骤:

  • 1、参数入栈:将参数依次压入系统栈中。
  • 2、返回地址入栈:将当前代码区调用指令的下一条指令地址压入栈中,供函数返回时继续执行。
  • 3、代码区跳转:处理器从当前代码区跳转到被调用函数的入口处。
  • 4、栈帧调整
  • 4.1保存当前栈帧状态值,已备后面恢复本栈帧时使用(EBP入栈)。
  • 4.2将当前栈帧切换到新栈帧(将ESP值装入EBP,更新栈帧底部)。
  • 4.3给新栈帧分配空间(把ESP减去所需空间的大小,抬高栈顶)。

而函数返回大概包括以下几个步骤:

  • 1、保存返回值,通常将函数的返回值保存在寄存器EAX中。
  • 2、弹出当前帧,恢复上一个栈帧。
  • 2.1在堆栈平衡的基础上,给ESP加上栈帧的大小,降低栈顶,回收当前栈帧的空间
  • 2.2将当前栈帧底部保存的前栈帧EBP值弹入EBP寄存器,恢复出上一个栈帧。
  • 2.3将函数返回地址弹给EIP寄存器。
  • 3、跳转:按照函数返回地址跳回母函数中继续执行。

2、print_stackframe函数的实现
函数注释:
在这里插入图片描述
根据注释及上面梳理的函数调用返回过程编写代码:
在这里插入图片描述

在这里插入图片描述
实验结果如上。

练习6:
1、请编程完善kern/trap/trap.c中对中断向量表进行初始化的函数idt_init。
在这里插入图片描述
一共对应三步:
第一步:声明__vertors[],其中存放着中断服务程序的入口地址。
第二步,填充中断描述符表IDT。
第三部,加载中断描述符表。

最关键的是第二步:填充中断描述符表IDT。主要使用SETGATE
在这里插入图片描述
各参数的意义:
gate:为相应的idt[]数组内容,处理函数的入口地址
istrap:系统段设置为1,中断门设置为0
sel:段选择子
off:为__vectors[]数组内容
dpl:设置特权级。这里中断都设置为内核级,即第0级

2、请编程完善trap.c中的中断处理函数trap,在对时钟中断进行处理的部分填写trap函数中处理时钟中断的部分,使操作系统每遇到100次时钟中断后,调用print_ticks子程序,向屏幕上打印一行文字”100 ticks”。
在这里插入图片描述
在这里插入图片描述
这一部分相对还是比较简单的,只需实现每一百次时钟信号便调用print_ticks()函数打印
实验结果截图如下,当用键盘输入a时,屏幕成功予以回显
在这里插入图片描述
lab1 至此完成。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值