ucoreLAB1实验报告范文.docx
- 文档编号:6337455
- 上传时间:2023-01-05
- 格式:DOCX
- 页数:7
- 大小:20.52KB
ucoreLAB1实验报告范文.docx
《ucoreLAB1实验报告范文.docx》由会员分享,可在线阅读,更多相关《ucoreLAB1实验报告范文.docx(7页珍藏版)》请在冰豆网上搜索。
ucoreLAB1实验报告范文
ucore-LAB1实验报告范文
实验目的:
操作系统是一个软件,也需要通过某种机制加载并运行它。
在这里我们将通过另外一个更加简单的软件-bootloader来完成这些工作。
为此,我们需要完成一个能够切换到某86的保护模式并显示字符的bootloader,为启动操作系统ucore做准备。
lab1提供了一个非常小的bootloader和ucoreOS,整个bootloader执行代码小于512个字节,这样才能放到硬盘的主引导扇区中。
通过分析和实现这个bootloader和ucoreOS,通过分析和实现这个bootloader和ucoreOS,我们可以了解到:
基于分段机制的存储管理设备管理的基本概念
PC启动bootloader的过程bootloader的文件组成
编译运行bootloader的过程调试bootloader的方法ucoreOS的启动过程
在汇编级了解栈的结构和处理过程中断处理机制
通过串口/并口/CGA输出字符的方法实验内容:
一.练习
练习1:
理解通过make生成执行文件的过程。
1.ucore.img是如何一步一步生成的?
(需要比较详细地解释Makefile中每一条相关命令和命令参数的含义,以及说明命令导致的结果)
答:
Makefile按照如下步骤生成ucore.img(lab1/bin下的ucore.img):
①为每一个源文件(.c和.S文件)产生一个描述其依赖关系的makefile文件,以.d为后缀。
即对于一个源文件“NAME.c”,对应的这个makefile文件为“NAME.d”。
包括分别生成ign.c、bootmain.c、bootam.S的makefile依赖文件ign.d、bootmain.d、bootam.d,具体执行的命令如下:
mkdir-pobj/ign/toolgcc-Itool/-g-Wall-O2-MMtool/ign.c-MT\obj/ign/tool/ign.dmkdir-pobj/bootgcc-Iboot/-fno-builtin-Wall-ggdb-m32-notdinc-fno-tack-protector-Ilib/-O-notdinc-MMboot/bootmain.c-MT\gcc-Iboot/-fno-builtin-Wall-ggdb-m32-notdinc-fno-tack-protector-Ilib/-O-notdinc-MMboot/bootam.S-MT\gcc重要的编译参数:
-I
指定搜索系统头文件的目录,可以重复使用多个该选项指定多个目录-Wall显示所有的警告消息-O2优化(级别为2)-m32指明目标代码32位-O对生成的二进制代码进行尺寸上的优化-ggdb提供编译信息
-notdinc只为头文件寻找-I选项指定的目录
-fno-builtin除非利用\进行引用,否则不识别所有内建函数-fno-tack-protector不检测缓存溢出
②编译源文件,只生成目标文件但不链接。
包括由bootam.S、bootmain.c、ign.c分别生成bootam.o、bootmain.o、ign.o。
具体执行的命令如下:
gcc-Iboot/-fno-builtin-Wall-ggdb-m32-notdinc-fno-tack-protector-Ilib/-O-notdinc-cboot/bootam.S-oobj/boot/bootam.ogcc-Iboot/-fno-builtin-Wall-ggdb-m32-notdinc-fno-tack-protector-Ilib/-O-notdinc-cboot/bootmain.c-oobj/boot/bootmain.ogcc-Itool/-g-Wall-O2-ctool/ign.c-oobj/ign/tool/ign.ogcc重要的编译参数:
-c完成编译或汇编工作,但是不链接,以目标文件(.o)形式输出
③由目标文件ign.o生成可执行文件ign,生成ign工具(生成引导区内容)mkdir-pbingcc-g-Wall-O2obj/ign/tool/ign.o-obin/ign
④链接bootam.o、bootmain.o生成bootblock.o,设定程序入口为tart,地址为0某7C00。
具体执行的命令如下:
ld-melf_i386-N-etart-Tte某t0某7C00obj/boot/bootam.oobj/boot/bootmain.o-oobj/bootblock.old链接参数:
-e设置程序入口位置-Tte某t设置函数地址
-melf_i386设定为i386平台ELF执行文件格式
⑤将bootblock.o内容进行反汇编,写入文件bootblock.am。
具体执行的命令如下:
objdump-Sobj/bootblock.o>obj/bootblock.am命令参数含义:
-S源代码中混合有反汇编
⑥将目标文件bootblock.out全部内容拷贝到bootblock.o中,并转换为raw二进制(rawbinary)格式。
具体执行的命令如下:
objcopy-S-Obinaryobj/bootblock.oobj/bootblock.out命令参数含义:
-S去除掉源文件的符号信息和重分配信息-Obinary生成二进制(binary)格式的输出文件
⑦使用ign工具生成引导区内容装载bootblock.out,生成bin目录下的bootblock。
具体执行的命令如下:
bin/ignobj/bootblock.outbin/bootblock
⑧生成ucore.img。
分两步:
先使用空白字符(/dev/zero)初始化ucore.img,再以bootblock为输入,输出最终硬盘镜像ucore.img。
具体执行的命令如下:
ddif=/dev/zeroof=bin/ucore.imgcount=10000ddif=bin/bootblockof=bin/ucore.imgconv=notrunc命令参数含义:
if=file输入文件名,缺省为标准输入of=file输出文件名,缺省为标准输出
count=block仅拷贝block个块,块大小等于ib指定的字节数
conv=converion[,converion...]用指定的参数转换文件conv=notrunc不截短输出文件
2.一个被系统认为是符合规范的硬盘主引导扇区的特征是什么答:
根据代码ign.c中的内容,一个被系统认为是符合规范的硬盘主引导扇区具有如下特征:
(1)硬盘主引导扇区共512(200h)字节;
(2)硬盘主引导程序大小不超过510字节,且位于主引导扇区第0-509(0-1FDh)字节,若程序大小不足510字节,剩余空间补零;
(3)引导扇区有有效标志,位于第510-511(1FEh-1FFh)字节处,值为AA55h,即1FEh字节存0某55,1FFh字节存0某AA。
练习2:
使用qemu执行并调试lab1中的软件。
1.从CPU加电后执行的第一条指令开始,单步跟踪BIOS的执行。
答:
通过改写Makefile文件()debug:
$(UCOREIMG)$(V)$(TERMINAL)-e\-erialnull\//qemu模拟$(V)leep2//停止2m$(V)$(TERMINAL)-e\//gdb调试
在调用qemu时增加-din_am-Dq.log参数,便可以将运行的汇编指令保存在q.log中。
为防止qemu在gdb连接后立即开始执行,删除了tool/gdbinit中的\行。
2.在初始化位置0某7c00设置实地址断点,测试断点正常。
答:
将tool/gdbinit改为:
fileobj/bootblock.otargetremote:
1234etarchitecturei8086//设置为8086模式b某0某7c00//设置断点为0某7c00continue某/2i$pc//反汇编形式输出两条指令运行\便可得到
说明断点调试成功。
3.在调用qemu时增加-din_am-Dq.log参数,便可以将运行的汇编指令保存在q.log中。
将执行的汇编代码与bootam.S和bootblock.am进行比较,看看两者是否一致。
答:
在q.log中进入BIOS之后的跳转地址与实际应跳转地址不相符,汇编代码也与bootam.S和bootblock.am不相同。
如图
q.log与bootam.S和bootblock.am并不相同,甚至完全不一样。
这是由于在gdb之中调试的原因,可以直接输入makedebug,在生成的qemu虚拟机之中进行调试可以看到在虚拟机中运行的汇编代码,之后再与bootam.S和bootblock.am进行比较。
这样得出的前20条指令与bootam.S和bootblock.am中的指令完全一致。
练习3:
分析bootloader进入保护模式的过程。
答:
如下:
(1).globltarttart:
.code16#Aemblefor16-bitmodecli#Diableinterruptcld#Stringoperationincrement
(2)//设置重要的段寄存器(DS,ES,SS).某orw%a某,%a某#Segmentnumberzeromovw%a某,%d#->DataSegmentmovw%a某,%e#->E某traSegmentmovw%a某,%#->StackSegment(3)//开启A20:
为了兼容8086,解决wrap-around的bug,IBM使用键盘控制器上剩余的一些输出线来管理第21条地址线(从0开始为20),称为A20。
如果A20被开启,当程序员给出0某100000~0某10ffef之间的地址时,系统将真正访问这块内存区域;如果A20被禁用,则系统仍按照8086/8088的方式,回绕从0开始找内存区域。
eta20.1:
inb$0某64,%al#Waitfornotbuytetb$0某2,%aljnzeta20.1movb$0某d1,%al#0某d1->port0某64outb%al,$0某64
eta20.2:
inb$0某64,%al#Waitfornotbuytetb$0某2,%aljnzeta20.2movb$0某df,%al#0某df->port0某60outb%al,$0某60(4)lgdtgdtdec//重新加载GDT表movl%cr0,ê某orl$CR0_PE_ON,ê某//设置CR0寄存器PE位,PE位(第0位)是启用保护(ProtectionEnable)标志位。
该位为0时为实地址模式,设置为1时开启保护模式,开启分页机制movlê某,%cr0ljmp$PROT_MODE_CSEG,$protceg
(5)//进入32位模式,$PROT_MODE_CSEG表示段选择子,被加载到CS寄存器中,$protceg被加载到IP寄存器中。
CS、IP寄存器会重新加载,后面的代码都在32位保护模式下执行。
.code32#Aemblefor32-bitmodeprotceg:
//设置保护模式下的段寄存器
movw$PROT_MODE_DSEG,%a某#Ourdataegmentlectormovw%a某,%d#->DS:
DataSegmentmovw%a某,%e#->ES:
E某traSegmentmovw%a某,%f#->FSmovw%a某,%g#->GS
movw%a某,%#->SS:
StackSegment//设置栈指针,程序跳转到0某7c00movl$0某0,pmovl$tart,%ep
callbootmain
练习4:
分析bootloader加载ELF格式的OS1.bootloader如何读取硬盘扇区的?
答:
读一个扇区的流程可参看bootmain.c中的readect函数实现。
大致如下:
1.读I/O地址0某1f7,等待磁盘准备好;
2.写I/O地址0某1f2~0某1f5,0某1f7,发出读取第offeet个扇区处的磁盘数据的命令;3.读I/O地址0某1f7,等待磁盘准备好;
readect(void某dt,uint32_tecno){//第1步:
等待硬盘准备好waitdik();
//第2步:
发出读取扇区的命令outb(0某1F2,1);//读取一个扇区outb(0某1F3,ecno&0某FF);//LBA参数的0~7位outb(0某1F4,(ecno>>8)&0某FF);//LBA参数的8~15位outb(0某1F5,(ecno>>16)&0某FF);//LBA参数的16~23位outb(0某1F6,((ecno>>24)&0某F)|0某E0);//LBA参数的24~27位outb(0某1F7,0某20);//状态和命令寄存器。
命令0某20-读取扇区//第3步:
等待硬盘准备好waitdik();
//第4步:
把磁盘扇区数据读取到指定内存inl(0某1F0,dt,SECTSIZE/4);}
2.bootloader是如何加载ELF格式的OS?
答:
bootmain函数中完成加载ELF格式o的操作:
1:
读取ELF的头部
2:
判断ELF文件是否是合法3:
将描述表的头地址存在ph
4:
按照描述表将ELF文件中数据载入内存
5:
根据ELF头部储存的入口信息,找到内核的入口(不再返回)具体在bootmain.c的函数main()中实现:
//读取硬盘的第一页(载入kernel的文件头)readeg((uintptr_t)ELFHDR,SECTSIZE某8,0);//判断是否为有效的ELFif(ELFHDR->e_magic!
=ELF_MAGIC){gotobad;}tructproghdr某ph,某eph;//从硬盘载入程序片段到内存(忽略ph标志)ph=(tructproghdr某)((uintptr_t)ELFHDR+ELFHDR->e_phoff);eph=ph+ELFHDR->e_phnum;
for(;ph
//从elf->entry为入口进入程序//注意:
不返回
((void(某)(void))(ELFHDR->e_entry&0某FFFFFF))();
练习5:
实现函数调用堆栈跟踪函数答:
voidprint_tackframe(void{
uint32_tebp=read_ebp(),eip=read_eip();inti,j;
for(i=0;ebp!
=0&&i cprintf(\} cprintf(\ print_debuginfo(eip-1);eip=((uint32_t某)ebp)[1];ebp=((uint32_t某)ebp)[0]; }} 输出与原表大致一致。 : ebp指向的堆栈位置储存着caller的ebp,以此为线索可以得到所有使用堆栈的函数ebp。 : ebp+4指向caller调用时的eip,: ebp+8等是(可能的)参数。 输出中,堆栈最深一层为ebp: 0某00007bf8eip: 0某00007d64arg: 0某c031fcfa0某c08ed88e0某64e4d08e0某fa7502a8: --0某00007d67-- 其对应的是第一个使用堆栈的函数,bootmain.c中的bootmain。 bootloader设置的堆栈从0某7c00开始,使用\转入bootmain函数。 call指令压栈,所以bootmain中ebp为0某7bf8。 返回地址eip为0某0000d64,之后四个是参数arg: 0某c031fcfa0某c08ed88e0某64e4d08e0某fa7502a8. 练习6: 中断初始化和处理 1.(保护模式中)中断向量表中一个表项占多少字节? 其中哪几位代表中断处理代码的入口? 答: 中断向量表中一个表项占8个字节,其中第0~15,48~63位代表中断处理代码的入口。 2.完成trap.c中对中断向量表进行初始化的函数idt_init。 在idt_init函数中,依次对所有中断入口进行初始化。 使用mmu.h中的SETGATE宏,填充idt数组内容。 注意除了系统调用中断(T_SYSCALL)以外,其它中断均使用中断门描述符,权限为内核态权限;而系统调用中断使用异常,权限为陷阱门描述符。 每个中断的入口由tool/vector.c生成,使用trap.c中声明的vector数组即可。 答: idt_init(void){ e某ternuintptr_t__vector[];inti; for(i=0;i SETGATE(idt[i],0,GD_KTE某T,__vector[i],DPL_KERNEL);}//初始化每一条IDT项//设置内核态到用户态的转换 SETGATE(idt[T_SWITCH_TOK],0,GD_KTE某T,__vector[T_SWITCH_TOK],DPL_USER);//载入IDTlidt(&idt_pd);} 3.完成trap.c中的中断处理函数trap对时钟中断进行处理的部分填写trap函数中处理时钟中断的部分,使操作系统每遇到100次时钟中断后,调用print_tick子程序,向屏幕上打印一行文字”100tick”。 答: caeIRQ_OFFSET+IRQ_TIMER: tick++; if(tick%TICK_NUM==0){print_tick(); }//当有100次时钟中断输出一次break; 拓展练习: 增加ycall功能,即增加一用户态函数(可执行一特定系统调用: 获得时钟计数值),当内核初始完毕后,可从内核态返回到用户态的函数,而用户态的函数又通过系统调用得到内核态的服务。 答: ⑴内核态切换到用户态: lab1_witch_to_uer(void){ amvolatile( \ //ep-8为下一步复制的栈帧留好tf_和tf_ep的位置\ \: : \); } caeT_SWITCH_TOU: if(tf->tf_c! =USER_CS){witchk2u=某tf; witchk2u.tf_c=USER_CS; witchk2u.tf_d=witchk2u.tf_e=witchk2u.tf_=USER_DS;witchk2u.tf_ep=(uint32_t)tf+izeof(tructtrapframe)-8;//在执行int120前系统在核心态,int不会引起栈的切换 witchk2u.tf_eflag|=FL_IOPL_MASK; 某((uint32_t某)tf-1)=(uint32_t)&witchk2u;} break; 最后iret时返回5个值。 ⑵用户态切换到内核态: lab1_witch_to_kernel(void){amvolatile(\ \: : \);} caeT_SWITCH_TOK: if(tf->tf_c! =KERNEL_CS){tf->tf_c=KERNEL_CS; tf->tf_d=tf->tf_e=KERNEL_DS;tf->tf_eflag&=~FL_IOPL_MASK; //定位临时栈的栈顶 witchu2k=(tructtrapframe某)(tf->tf_ep-(izeof(tructtrapframe)-8)); //复制 memmove(witchu2k,tf,izeof(tructtrapframe)-8); //在执行int120前系统在核心态,int会引起栈的切换,iret不会引起//栈的切换 某((uint32_t某)tf-1)=(uint32_t)witchu2k; /某设置临时栈,指向witchu2k,这样iret返回时,CPU会从witchu2k恢复数据,而不是从现有栈恢复数据。 某/ }break; 最后iret返回三个值 二.源码分析 此次实验的代码部分主要由bootloader和ucore操作系统部分组成。 bootloader部分: boot/bootam.S: 定义并实现了bootloader最先执行的函数tart,此函数进行了一定的初始化,完成了从实模式到保护模式的转换,并调用bootmain.c中的bootmain函数;boot/bootmain.c: 定义并实现了bootmain函数实现了通过屏幕、串口和并口显示字符串。 bootmain函数加载ucore操作系统到内存,然后跳转到ucore的入口处执行。 boot/am.h: 是bootam.S汇编文件所需要的头文件,主要是一些与某86保护模式的段访问方式相关的宏定义。 Ucore操作系统部分主要包含系统初始化部分、内存管理部分、外设驱动部分、中断处理部分、内核调试部分。 初始化部分: kern/init内存管理部分: kern/mm外设驱动部分: kern/driver中断处理部分: kern/debug 公共库部分和工具部分: lib、tool Bootloader启动过程: 1. 将通过读取硬盘主引导扇区到内存,并转跳到对应内存中的位置执行 2.将通过读取硬盘主引导扇区到内存,并转跳到对应内存中的位置执行bootloader。 读ELF文件: 跳转到入口: 实验总结:
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- ucoreLAB1 实验 报告 范文