操作系统 ucore lab1实验报告

ucore lab1(系统软件启动过程)

一、实验目的

操作系统是一个软件,也需要通过某种机制加载并运行它。在这里我们将通过另外一个更加简单的软件-bootloader来完成这些工作。为此,我们需要完成一个能够切换到x86的保护模式并显示字符的bootloader,为启动操作系统ucore做准备。lab1提供了一个非常小的bootloader和ucore OS,整个bootloader执行代码小于512个字节,这样才能放到硬盘的主引导扇区中。通过分析和实现这个bootloader和ucore OS,读者可以了解到:
(1)计算机原理
CPU的编址与寻址: 基于分段机制的内存管理
CPU的中断机制
外设:串口/并口/CGA,时钟,硬盘
(2)Bootloader软件
编译运行bootloader的过程
调试bootloader的方法
PC启动bootloader的过程
ELF执行文件的格式和加载
外设访问:读硬盘,在CGA上显示字符串
(3)ucore OS软件
编译运行ucore OS的过程
ucore OS的启动过程
调试ucore OS的方法
函数调用关系:在汇编级了解函数调用栈的结构和处理过程
中断管理:与软件相关的中断处理
外设管理:时钟

二、实验内容

lab1中包含一个bootloader和一个OS。这个bootloader可以切换到X86保护模式,能够读磁盘并加载ELF执行文件格式,并显示字符。而这lab1中的OS只是一个可以处理时钟中断和显示字符的幼儿园级别OS。

三、实验步骤及流程

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

3.1.1实验要求

列出本实验各练习中对应的OS原理的知识点,并说明本实验中的实现部分如何对应和体现了原理中的基本概念和关键知识点。在此练习中,大家需要通过静态分析代码来了解:
(1)操作系统镜像文件ucore.img是如何一步一步生成的?(需要比较详细地解释Makefile中每一条相关命令和命令参数的含义,以及说明命令导致的结果)
(2)一个被系统认为是符合规范的硬盘主引导扇区的特征是什么?

3.1.2实验内容理解

Q1:作系统镜像文件ucore.img是如何一步一步生成的?(需要比较详细地解释Makefile中每一条相关命令和命令参数的含义,以及说明命令导致的结果)
①进入本次实验所需的文件路径,然后执行命令:make “V=”。
在这里插入图片描述
②通过gcc编译器将Kernel目录下的.c文件编译成obj目录下的.o文件。
在这里插入图片描述
③ld命令根据链接脚本文件kernel.ld将生成的.o文件,链接成bin目录下的kernel文件。
在这里插入图片描述
④通过gcc编译器将boot目录下的.c,.S文件以及tools目录下的sign.c文件编译成obj目录下的.o文件。
在这里插入图片描述
⑤ld命令将生成的.o文件,链接成bin目录下的bootblock文件。
在这里插入图片描述
⑥dd命令将dev/zero, bin/bootblock,bin/kernel 写入到bin/ucore.img中。
在这里插入图片描述
这点也可以从Makefile文件中得到验证:
在这里插入图片描述
dd:用指定大小的块拷贝一个文件,并在拷贝的同时进行指定的转换。
if=文件名:输入文件名,缺省为标准输入。即指定源文件。< if=input file >
of=文件名:输出文件名,缺省为标准输出。即指定目的文件。< of=output file >
count=blocks:仅拷贝blocks个块,块大小等于ibs指定的字节数。
conv=conversion:用指定的参数转换文件。
conv=notrunc:不截断输出文件。
⑦整个过程总结如下:
1.编译所有生成bin/kernel所需的文件,也就是.c文件转化为.o文件。
2.链接生成bin/kernel。
3.编译bootasm.S bootmain.c sign.c 转化为.o文件。
4.根据sign规范链接生成bin/bootblock。
5.生成ucore.img。先创建一个大小为10000字节的块,然后再将bootblock,kernel拷贝过去。通过dd命令将bootblock放到第一个sector,将kernel放到第二个sector开始的区域。可以明显看出bootblock就是引导区,kernel则是操作系统内核。
Q2:一个被系统认为是符合规范的硬盘主引导扇区的特征是什么?
截取sign.c文件的部分源代码如下:
在这里插入图片描述
特征如下:
(1)主引导扇区大小为512字节;
(2)多余的空间填0,与初始化有关;
(3)第510个(倒数第二个)字节是0x55;
(4)第511个(倒数第一个)字节是0xAA。

3.2 练习2:使用qemu执行并调试lab1中的软件

3.2.1实验要求

为了熟悉使用qemu和gdb进行的调试工作,我们进行如下的小练习:
(1)从CPU加电后执行的第一条指令开始,单步跟踪BIOS的执行。
(2)在初始化位置0x7c00设置实地址断点,测试断点正常。
(3)从0x7c00开始跟踪代码运行,将单步跟踪反汇编得到的代码与bootasm.S和 bootblock.asm进行比较。
(4)自己找一个bootloader或内核中的代码位置,设置断点并进行测试。

3.2.2实验操作

(1)从CPU加电后执行的第一条指令开始,单步跟踪BIOS的执行。
①修改lab1/tools/gdbinit配置文件:与qemu建立联系。
Set architecture i8086
Target remote :1234
②在lab1的目录下,执行命令 make debug;
③使用gdb命令 si 进行单步调试;
④在gdb的界面下通过命令 x /2i $pc 查看BIOS的代码;
在这里插入图片描述
(2)在初始化位置0x7c00设置实地址断点,测试断点正常。
①修改lab1/tools/gdbinit配置文件:
set architecture i8086
target remote :1234
b *0x7c00 //设置断点
c //continue之后测试断点
x/2i $pc
②在lab1的目录下,执行命令 make debug,断点测试正常:
在这里插入图片描述
(3)从0x7c00开始跟踪代码运行,将单步跟踪反汇编得到的代码与bootasm.S和 bootblock.asm进行比较。
①修改lab1/tools/gdbinit配置文件:
set architecture i8086
target remote :1234
b *0x7c00
c
x/10i $pc
②在lab1的目录下,执行命令 make debug;
③单步跟踪反汇编得到的代码如下:
在这里插入图片描述
在这里插入图片描述
④bootblock.S 中的代码为:
在这里插入图片描述
⑤bootblock.asm中的代码。
⑥经过对比发现以上三者的的汇编代码是一致的。
(4)自己找一个bootloader或内核中的代码位置,设置断点并进行测试。
①修改lab1/tools/gdbinit配置文件:
set architecture i8086
target remote :1234
b *0x7c06
c
x/2i $pc
②在lab1的目录下,执行命令 make debug,断点测试正常:
在这里插入图片描述
(5)gdb的单步命令:
next:单步到程序源代码的下一行,不进入函数。
nexti :单步一条机器指令,不进入函数。
step :单步到下一个不同的源代码行(包括进入函数)。
stepi :单步一条机器指令。

3.3 练习3:分析bootloader进入保护模式的过程。

3.3.1实验要求

BIOS将通过读取硬盘主引导扇区到内存,并转跳到对应内存中的位置执行bootloader。请分析bootloader是如何完成从实模式进入保护模式的。
提示:需要阅读小节“保护模式和分段机制”和lab1/boot/bootasm.S源码,了解如何从实模式切换到保护模式,需要了解:
(1)为何开启A20,以及如何开启A20;
(2)如何初始化GDT表;
(3)如何使能和进入保护模式

3.3.2实验操作

(1)关闭中断,将各个段寄存器重置0:
在这里插入图片描述
(2)为何开启A20,以及如何开启A20
开启A20地址线之后,用来表示内存地址的位数变多了。开启前20位,开启后是32位。如果不开启A20地址线内存寻址最大只能找到1M,对于1M以上的地址访问会变成对address mod 1M地址的访问。通过将键盘控制器上的A20线置于高电位,全部32条地址线可用,可以访问4G的内存空间。打A20地址位由8042控制,8042有2个有两个I/O端口:0x60和0x64。在开启之前为实模式,开启之后为保护模式。具体如何开启代码如下:
在这里插入图片描述
(3)如何初始化GDT表
①载入GDT表:一个简单的GDT表和其描述符已经静态储存在引导区中,载入即可。
在这里插入图片描述
②初始化GDT:
在这里插入图片描述
对上述代码理解如下:
·word 的作用可以理解为设置一个大小为 word 的数据,由于 0x17 = 23,所以 GDTR 中的 Size 为 24,即存在 3 个 GDT Entry
·long 的作用与 .word 同理。
·第 4,5,6行,分别设置了NULL段,代码段,数据段,这里调用了 SEG 函数,上面几行执行的结果大概可以视作如下:
GDT[0] = { base = 0x0, limit = 0x0, type = 0x0 }
GDT[1] = { base = 0x0, limit = 0xffffffff, type = 0xA }
GDT[2] = { base = 0x0, limit = 0xffffffff, type = 0x2 }
③进入保护模式:通过将cr0寄存器PE位置1便开启了保护模式。(cro的第0位为1表示处于保护模式)
在这里插入图片描述
④通过长跳转更新cs的基地址:上面已经打开了保护模式,所以这里需要用到逻辑地址。$PROT_MODE_CSEG的值为0x80。
在这里插入图片描述
⑤设置段寄存器,并建立堆栈:
在这里插入图片描述
⑥转到保护模式完成,调用bootmain函数:
在这里插入图片描述
(4)如何使能和进入保护模式
将cr0寄存器置1,cro的第0位为1表示处于保护模式。

3.4 练习4:分析bootloader加载ELF格式的OS的过程。

3.4.1实验要求

通过阅读bootmain.c,了解bootloader如何加载ELF文件。通过分析源代码和通过qemu来运行并调试bootloader&OS,回答:
(1)bootloader如何读取硬盘扇区的?
(2)bootloader是如何加载ELF格式的OS?
提示:可阅读“硬盘访问概述”,“ELF执行文件格式概述”这两小节。

3.4.2实验操作

(1)bootloader如何读取硬盘扇区的?
对于硬盘来说,是分成许多扇区的其中每个扇区的大小为512字节。读取扇区的流程如下:
①等待磁盘准备好;
②发出读取扇区的命令;
③等待磁盘准备好;
④把磁盘扇区数据读到指定内存。
(2)bootloader是如何加载ELF格式的OS?
①从硬盘读了8个扇区数据到内存0x10000处,并把这里强制转换成elfhdr使用。
②校验e_magic字段。
③根据偏移量分别把程序段的数据读取到内存中。
从bootmain.c文件中截取的具体代码如下:
在这里插入图片描述
ELF结构定义:
在这里插入图片描述

3.5 练习5:实现函数调用堆栈跟踪函数。

我们需要在lab1中完成kdebug.c中函数print_stackframe的实现,可以通过函数print_stackframe来跟踪函数调用堆栈中记录的返回地址。
几乎所有本地编译器都会在每个函数体之前插入类似如下的汇编指令:首先将ebp寄存器入栈,然后将栈顶指针esp赋值给ebp。
pushl %ebp
movl %esp , %ebp
在程序执行到一个函数的实际指令前,已经有以下数据顺序入栈:参数、返回地址、ebp寄存器。由此得到类似如下的函数调用栈结构:
在这里插入图片描述
可以通过read_ebp()和read_eip()函数来获取当前ebp寄存器和eip 寄存器的信息。然后通过ebp+12,ebp+16,ebp+20,ebp+24来输出4个参数的值,最后更新ebp:ebp=ebp[0],更新eip:eip=ebp[1]。直到ebp 对应地址的值为0(表示当前函数为bootmain)。
在这里插入图片描述
在lab1下执行命令make qemu:
在这里插入图片描述
最后一行:bootloader设置的堆栈从0x7c00开始,使用”call bootmain”转入bootmain函数。
call指令压栈,所以bootmain中ebp为0x7bf8。

3.6 练习6:分析bootloader加载ELF格式的OS的过程。

3.6.1实验要求

请完成编码工作和回答如下问题:
(1)中断描述符表(也可简称为保护模式下的中断向量表)中一个表项占多少字节?其中哪几位代表中断处理代码的入口?
(2)请编程完善kern/trap/trap.c中对中断向量表进行初始化的函数idt_init。在idt_init函数中,依次对所有中断入口进行初始化。使用mmu.h中的SETGATE宏,填充idt数组内容。每个中断的入口由tools/vectors.c生成,使用trap.c中声明的vectors数组即可。
(3)请编程完善trap.c中的中断处理函数trap,在对时钟中断进行处理的部分填写trap函数中处理时钟中断的部分,使操作系统每遇到100次时钟中断后,调用print_ticks子程序,向屏幕上打印一行文字”100 ticks”。

3.6.2实验操作

(1)中断描述符表(也可简称为保护模式下的中断向量表)中一个表项占多少字节?其中哪几位代表中断处理代码的入口?
向量表结构如下:
在这里插入图片描述
由各项相加可知,一个表项的占64bit,即8字节,其中0 ~ 15位和48 ~ 63位分别为偏移量的低16位和高16位,两者拼接为偏移量,16~31位为段选择器。通过段选择子去GDT中找到对应的基地址,然后基地址加上偏移量就是中断处理程序的地址。
(2)请编程完善kern/trap/trap.c中对中断向量表进行初始化的函数idt_init。在idt_init函数中,依次对所有中断入口进行初始化。使用mmu.h中的SETGATE宏,填充idt数组内容。每个中断的入口由tools/vectors.c生成,使用trap.c中声明的vectors数组即可。
先使用SETGATE宏,对中断描述表中的每一个表项进行设置:
①gate是中断的描述符表,为相应的idt数组内容,处理函数的入口地址;
②istrap用来判断是中断还是trap,系统段设置为1,中断门设置为0;
③sel段选择子,作用是进行段的选择,这里是GO——KTEXT;
④off为__vectors数组内容,存在vectors.s中,支持256个中断;
⑤dpl表示这个中断的优先级,0为内核级,3为用户级。
其余代码含义见注释。
在这里插入图片描述
(3)请编程完善trap.c中的中断处理函数trap,在对时钟中断进行处理的部分填写trap函数中处理时钟中断的部分,使操作系统每遇到100次时钟中断后,调用print_ticks子程序,向屏幕上打印一行文字”100 ticks”
在这里插入图片描述
这里要每中断到100次就调用一个TICK_NUM输出信息,TICK_NUM设置的值为100,由于print ticks函数已经是现成的了,因此可以直接用,补充switch即可。每次中断ticks计数加一,到了100,就回到0,同时输出一次。

四、运行结果

在这里插入图片描述

五、实验总结

5.1 实验重难点

(1)bootloader启动过程:
BIOS将通过读取硬盘主引导扇区到内存,并转跳到对应内存中的位置执行bootloader。bootloader完成的工作包括:
①切换到保护模式,启用分段机制;
②读磁盘中ELF执行文件格式的ucore操作系统到内存;
③显示字符串信息;
④把控制权交给ucore操作系统。
(2)实模式与保护模式
①实模式:在bootloader接手BIOS的工作后,当前的PC系统处于实模式(16位模式)运行状态,在这种状态下软件可访问的物理内存空间不能超过1MB,且无法发挥Intel 80386以上级别的32位CPU的4GB内存管理能力。实模式将整个物理内存看成分段的区域,程序代码和数据位于不同区域,操作系统和用户程序并没有区别对待,而且每一个指针都是指向实际的物理地址。这样,用户程序的一个指针如果指向了操作系统区域或其他用户程序区域,并修改了内容,那么其后果就很可能是灾难性的。
②保护模式:在保护模式下,80386的全部32根地址线有效,可寻址高达4G字节的线性地址空间和物理地址空间,可访问64TB(有214个段,每个段最大空间为232字节)的逻辑地址空间,可采用分段存储管理机制和分页存储管理机制。这不仅为存储共享和保护提供了硬件支持,而且为实现虚拟存储提供了硬件支持。通过提供4个特权级和完善的特权检查机制,既能实现资源共享又能保证代码数据的安全及任务的隔离。
③通过修改A20地址线可以完成从实模式到保护模式的转换,即开启A20。
(3)中断初始化
系统将所有的中断事件统一进行了编号(0~255),这个编号称为中断向量。以ucore为例,操作系统内核启动以后,会通过 idt_init 函数初始化 idt 表 (参见trap.c),而其中 vectors 中存储了中断处理程序的入口地址。vectors 定义在 vector.S 文件中,通过一个工具程序 vector.c 生成。其中仅有 System call 中断的权限为用户权限 (DPL_USER),即仅能够使用 int 0x80 指令。此外还有对 tickslock 的初始化,该锁用于处理时钟中断。
vector.S 文件通过 vectors.c 自动生成,其中定义了每个中断的入口程序和入口地址 (保存在 vectors 数组中)。其中,中断可以分成两类:一类是压入错误编码的 (error code),另一类不压入错误编码。对于第二类, vector.S 自动压入一个 0。此外,还会压入相应中断的中断号。在压入两个必要的参数之后,中断处理函数跳转到统一的入口 alltraps 处。

5.2 实验心得

通过本次实验对CPU中断机制,关于Bootloader软件的编译与运行,调试还有ELF执行文件,以及ucore OS 软件都有了一定的了解,之前仅仅通过课程学习没有实践掌握的比较粗略,通过这次实验页明白了许多细节方面的知识。在验收过程中通过助教老师的提问也发现了自己一些不足,对自己在实验过程中遗漏的部分知识点也进行了查缺补漏。相信本次实验的内容与收获会对今后的学习起到很好的帮助。

  • 6
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
北航操作系统课程设计的Lab0旨在帮助学生熟悉实际的操作系统开发环境和相关工具的使用。在这个实验中,我们首先需要安装Linux操作系统并进行相关配置,以便进行后续的实验操作。 在Lab0中,我们需要安装和配置虚拟机软件,如VirtualBox或VMware,以创建一个类似操作系统的虚拟环境。然后,我们需要从北航操作系统课程设计网站下载并安装提供的操作系统实验环境,如ucore OS等。 安装完成后,我们需要熟悉Linux系统的基本命令,并了解与操作系统开发相关的编译工具链,如gcc和make。这些工具在操作系统的编译和运行过程中起着至关重要的作用。我们将学习如何使用gcc编译C语言程序,并使用make工具自动化编译过程。 在实验中,我们还将学习如何通过交叉编译和链接,将编译生成的操作系统镜像加载到虚拟机中并运行。我们将通过VirtualBox或VMware的网络设置,将虚拟机与宿主机连接起来,以便进行操作系统的调试和测试。 通过完成Lab0,我们将能够顺利地进行后续的操作系统实验,掌握操作系统开发的基本技能和工具。我们将能够编写C语言的系统代码,实现操作系统的基本功能和特性。此外,我们还将学习到操作系统的底层原理和相关的调试技巧。 总而言之,北航操作系统课程设计的Lab0是一个重要的起点,它帮助我们搭建实验环境、熟悉操作系统开发工具和命令,并为后续的实验打下坚实的基础。通过此实验,我们将能够更深入地理解操作系统的工作原理,并为我们未来的学习和研究提供良好的支持。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值