操作系统ucorelab1.docx
- 文档编号:29225539
- 上传时间:2023-07-21
- 格式:DOCX
- 页数:21
- 大小:1.18MB
操作系统ucorelab1.docx
《操作系统ucorelab1.docx》由会员分享,可在线阅读,更多相关《操作系统ucorelab1.docx(21页珍藏版)》请在冰豆网上搜索。
操作系统ucorelab1
HUNANUNIVERSITY
操作系统
实验报告
目录
一、内容2
二、目的2
三、实验设计思想和流程3
四、主要文件结构说明4
五、实验环境以及实验过程与结果分析(包含实验详细过程)4
练习1:
理解通过make生成执行文件的过程4
练习2:
使用qemu执行并调试lab1中的软件。
6
练习3:
分析bootloader进入保护模式的过程。
9
练习4:
分析bootloader加载ELF格式的OS的过程。
11
练习5:
实现函数调用堆栈跟踪函数14
练习6:
完善中断初始化和处理16
六、实验体会18
一、内容
lab1中包含一个bootloader和一个OS。
这个bootloader可以切换到X86保护模式,能够读磁盘并加载ELF执行文件格式,并显示字符。
而这lab1中的OS只是一个可以处理时钟中断和显示字符的幼儿园级别OS。
为了实现lab1的目标,lab1提供了6个基本练习和1个扩展练习,要求完成实验报告。
二、目的
操作系统是一个软件,也需要通过某种机制加载并运行它。
在这里我们将通过另外一个更加简单的软件-bootloader来完成这些工作。
为此,我们需要完成一个能够切换到x86的保护模式并显示字符的bootloader,为启动操作系统ucore做准备。
lab1提供了一个非常小的bootloader和ucoreOS,整个bootloader执行代码小于512个字节,这样才能放到硬盘的主引导扇区中。
通过分析和实现这个bootloader和ucoreOS,读者可以了解到:
计算机原理
CPU的编址与寻址:
基于分段机制的内存管理
CPU的中断机制
外设:
串口/并口/CGA,时钟,硬盘
Bootloader软件
编译运行bootloader的过程
调试bootloader的方法
PC启动bootloader的过程
ELF执行文件的格式和加载
外设访问:
读硬盘,在CGA上显示字符串
ucoreOS软件
编译运行ucoreOS的过程
ucoreOS的启动过程
调试ucoreOS的方法
函数调用关系:
在汇编级了解函数调用栈的结构和处理过程
中断管理:
与软件相关的中断处理
外设管理:
时钟
三、实验设计思想和流程
依照实验指导书完成了六个对应练习:
练习1:
理解通过make生成执行文件的过程
练习2:
使用qemu执行并调试lab1中的软件
练习3:
分析bootloader进入保护模式的过程
练习4:
分析bootloader加载ELF格式的OS的过程
练习5:
实现函数调用堆栈跟踪函数
练习6:
完善中断初始化和处理
四、主要文件结构说明
五、实验环境以及实验过程与结果分析(包含实验详细过程)
实验环境为:
LINUX_64系统。
练习1:
理解通过make生成执行文件的过程
1.操作系统镜像文件ucore.img是如何一步一步生成的?
(需要比较详细地解释Makefile中每一条相关命令和命令参数的含义,以及说明命令导致的结果)
#createucore.img
UCOREIMG:
=$(calltotarget,ucore.img)
$(UCOREIMG):
$(kernel)$(bootblock)
$(V)ddif=/dev/zeroof=$@count=10000
$(V)ddif=$(bootblock)of=$@conv=notrunc
$(V)ddif=$(kernel)of=$@seek=1conv=notrunc
$(callcreate_target,ucore.img)
//为了生成bootblock,首先需要生成bootasm.o、bootmain.o、sign
$(bootblock):
$(calltoobj,$(bootfiles))|$(calltotarget,sign)
@echo+ld$@
$(V)$(LD)$(LDFLAGS)-N-estart-Ttext0x7C00$^-o$(calltoobj,bootblock)
@$(OBJDUMP)-S$(callobjfile,bootblock)>$(callasmfile,bootblock)
@$(OBJCOPY)-S-Obinary$(callobjfile,bootblock)$(calloutfile,bootblock)
@$(calltotarget,sign)$(calloutfile,bootblock)$(bootblock)
$(callcreate_target,bootblock)
(1)通过GCC编译器将Kernel目录下的.c文件编译成OBJ目录下的.o文件。
(2)ld命令根据链接脚本文件kernel.ld将生成的*.o文件,链接成BIN目录下的kernel文件
(3)通过GCC编译器将boot目录下的.c,.S文件以及tools目录下的sign.c文件编译成OBJ目录下的*.o文件。
(4)ld命令将生成的*.o文件,链接成BIN目录下的bootblock文件。
(5)dd命令将dev/zero,bin/bootblock,bin/kernel写入到bin/ucore.img
注:
/dev/zero文件代表一个永远输出0的设备文件,使用它作输入可以得到全为空的文件。
因此可用来创建新文件和以覆盖的方式清除旧文件。
下面使用dd命令将从zero设备中创建一个10K大小(bs决定每次读写1024字节,count定义读写次数为10次),但内容全为0的文件。
dd是Linux/UNIX下的一个非常有用的命令,作用是用指定大小的块拷贝一个文件,并在拷贝的同时进行指定的转换。
2.一个被系统认为是符合规范的硬盘主引导扇区的特征是什么?
从以下代码可以看出:
buf缓冲区最后两位为0x55和0xAA,并且需要扇区大小满足512字节。
(1)编译过程:
在解压缩后的ucore源码包中使用make命令即可。
例如lab1中:
chy@laptop:
~/lab1$make
在lab1目录下的bin目录中,生成一系列的目标文件:
ucore.img:
被qemu访问的虚拟硬盘文件
kernel:
ELF格式的toyucorekernel执行文,被嵌入到了ucore.img中
bootblock:
虚拟的硬盘主引导扇区(512字节),包含了bootloader执行代码,被嵌入到了ucore.img中
sign:
外部执行程序,用来生成虚拟的硬盘主引导扇区
还生成了其他很多文件,这里就不一一列举了。
练习2:
使用qemu执行并调试lab1中的软件。
从CPU加电后执行的第一条指令开始,单步跟踪BIOS的执行。
1.在初始化位置0x7c00设置实地址断点,测试断点正常。
2.
3.从0x7c00开始跟踪代码运行,将单步跟踪反汇编得到的代码与bootasm.S和bootblock.asm进行比较。
(1)修改Makefile中debug段代码,使其默认执行如下命令:
-S-s
$(V)$(QEMU)-e“$(QEMU)–S–s–din_asm–D$(BINDIR)/q.log-parallelstdio-hda$<-serialnull“
为了与qemu配合进行源代码级别的调试,需要先让qemu进入等待gdb调试器的接入并且还不能让qemu中的CPU执行,因此启动qemu的时候,我们需要使用参数-S、–s这两个参数来做到这一点。
修改gdbinit文件如下:
执行makedebug
(2)q.log
(3)q.log与bootasm.S和bootblock.asm中的代码相同。
4.自己找一个bootloader或内核中的代码位置,设置断点并进行测试。
修改debuginit如下:
断点设置正常:
练习3:
分析bootloader进入保护模式的过程。
BIOS将通过读取硬盘主引导扇区到内存,并转跳到对应内存中的位置执行bootloader。
请分析bootloader是如何完成从实模式进入保护模式的。
//关中断和清除段数据:
包括将flag置0和将段寄存器置0
.globlstart
start:
.code16
cli//关中断
cld//清除方向标志
xorw%ax,%ax//ax清0
movw%ax,%ds//ds清0
movw%ax,%es//es清0
movw%ax,%ss//ss清0
开启A20:
通过将键盘控制器上的A20线置于高电位,全部32条地址线可用,
可以访问4G的内存空间。
seta20.1:
#等待8042键盘控制器不忙
inb$0x64,%al#
testb$0x2,%al#
jnzseta20.1#
movb$0xd1,%al#发送写8042输出端口的指令
outb%al,$0x64#
seta20.1:
#等待8042键盘控制器不忙
inb$0x64,%al#
testb$0x2,%al#
jnzseta20.1#
movb$0xdf,%al#打开A20
outb%al,$0x60#
初始化GDT表:
一个简单的GDT表和其描述符已经静态储存在引导区中,载入即可
lgdtgdtdesc
进入保护模式:
通过将cr0寄存器PE位置1便开启了保护模式
movl%cr0,%eax
orl$CR0_PE_ON,%eax
movl%eax,%cr0
通过长跳转更新cs的基地址
ljmp$PROT_MODE_CSEG,$protcseg
.code32
protcseg:
设置段寄存器,并建立堆栈
movw$PROT_MODE_DSEG,%ax
movw%ax,%ds
movw%ax,%es
movw%ax,%fs
movw%ax,%gs
movw%ax,%ss
movl$0x0,%ebp
movl$start,%esp
转到保护模式完成,进入boot主方法
callbootmain
练习4:
分析bootloader加载ELF格式的OS的过程。
通过阅读bootmain.c,了解bootloader如何加载ELF文件。
通过分析源代码和通过qemu来运行并调试bootloader&OS
bootloader如何读取硬盘扇区的?
bootloader是如何加载ELF格式的OS?
要了解bootloader是如何读取硬盘扇区的需要了解elf.h文件
读取扇区
staticvoid
readsect(void*dst,uint32_tsecno){
waitdisk();
outb(0x1F2,1);//设置读取扇区的数目为1
outb(0x1F3,secno&0xFF);
outb(0x1F4,(secno>>8)&0xFF);
outb(0x1F5,(secno>>16)&0xFF);
outb(0x1F6,((secno>>24)&0xF)|0xE0);
//上面四条指令联合制定了扇区号
//在这4个字节线联合构成的32位参数中
//29-31位强制设为1
//28位(=0)表示访问"Disk0"
//0-27位是28位的偏移量
outb(0x1F7,0x20);//0x20命令,读取扇区
waitdisk();
insl(0x1F0,dst,SECTSIZE/4);//读取到dst位置,
//幻数4因为这里以DW为单位
}
/*fileheader*/
structelfhdr{
uint32_te_magic;//mustequalELF_MAGICelf的模数
uint8_te_elf[12];
uint16_te_type;//1=relocatable,2=executable,3=sharedobject,4=coreimage
uint16_te_machine;//3=x86,4=68K,etc.
uint32_te_version;//fileversion,always1
uint32_te_entry;//entrypointifexecutable入口地址
uint32_te_phoff;//filepositionofprogramheaderor0第一个programheader的位置,
//这是个结构体,通过这个指针可以找到结构体数组的位置结合e_phnum可以取得所有ph结构体
uint32_te_shoff;//filepositionofsectionheaderor0
uint32_te_flags;//architecture-specificflags,usually0
uint16_te_ehsize;//sizeofthiselfheader
uint16_te_phentsize;//sizeofanentryinprogramheader
uint16_te_phnum;//numberofentriesinprogramheaderor0
uint16_te_shentsize;//sizeofanentryinsectionheader
uint16_te_shnum;//numberofentriesinsectionheaderor0
uint16_te_shstrndx;//sectionnumberthatcontainssectionnamestrings
};
/*programsectionheader*/
structproghdr{
uint32_tp_type;//loadablecodeordata,dynamiclinkinginfo,etc.
uint32_tp_offset;//fileoffsetofsegment
uint32_tp_va;//virtualaddresstomapsegment
uint32_tp_pa;//physicaladdress,notused
uint32_tp_filesz;//sizeofsegmentinfile
uint32_tp_memsz;//sizeofsegmentinmemory(biggerifcontainsbss)
uint32_tp_flags;//read/write/executebits
uint32_tp_align;//requiredalignment,invariablyhardwarepagesize
};
bootloader如何加载ELF格式的OS:
在bootmain函数中,
void
bootmain(void){
//首先读取ELF的头部
readseg((uintptr_t)ELFHDR,SECTSIZE*8,0);
//通过储存在头部的幻数判断是否是合法的ELF文件
if(ELFHDR->e_magic!
=ELF_MAGIC){
gotobad;
}
structproghdr*ph,*eph;
//ELF头部有描述ELF文件应加载到内存什么位置的描述表,
//先将描述表的头地址存在ph
ph=(structproghdr*)((uintptr_t)ELFHDR+ELFHDR->e_phoff);
eph=ph+ELFHDR->e_phnum;
//按照描述表将ELF文件中数据载入内存
for(;ph readseg(ph->p_va&0xFFFFFF,ph->p_memsz,ph->p_offset); } //ELF文件0x1000位置后面的0xd1ec比特被载入内存0x00100000 //ELF文件0xf000位置后面的0x1d20比特被载入内存0x0010e000 //根据ELF头部储存的入口信息,找到内核的入口 ((void(*)(void))(ELFHDR->e_entry&0xFFFFFF))(); bad: outw(0x8A00,0x8A00); outw(0x8A00,0x8E00); while (1); } 练习5: 实现函数调用堆栈跟踪函数 我们需要在lab1中完成kdebug.c中函数print_stackframe的实现,可以通过函数print_stackframe来跟踪函数调用堆栈中记录的返回地址。 在如果能够正确实现此函数,可在lab1中执行“makeqemu”后,在qemu模拟器中得到类似如下的输出: 代码部分如下: uint32_tebp=read_ebp(),eip=read_eip();//获取ebp和eip的值 inti,j; //#defineSTACKFRAME_DEPTH20 for(i=0;ebp! =0&&i cprintf("ebp: 0x%08xeip: 0x%08xargs: ",ebp,eip); uint32_t*args=(uint32_t*)ebp+2;//参数的首地址 for(j=0;j<4;j++){ cprintf("0x%08x",args[j]);//打印4个参数 } cprintf("\n"); print_debuginfo(eip-1);//打印函数信息 eip=((uint32_t*)ebp)[1];//更新eip ebp=((uint32_t*)ebp)[0];//更新ebp } 从上图中可以看见每次代码自动跟踪了ebp和eip以及四个参数的地址。 练习6: 完善中断初始化和处理 请完成编码工作和回答如下问题: 1.中断描述符表(也可简称为保护模式下的中断向量表)中一个表项占多少字节? 其中哪几位代表中断处理代码的入口? IDT(InterruptDescriptionTable)一个表项为8个字节。 kern/mm/mmu.h中的structgatedesc数据结构对中断描述符的具体定义: 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子程序,向屏幕上打印一行文字”100ticks”。 运行结果如下图: 六、实验体会 体会: 在实验过程中碰到了非常多的问题,包括了对概念的模糊认知,和对题意的理解发生各种偏差。 在经由引导程序执行之后,操作系统的运行流程如下: 转跳到ucore操作系统在内存中的入口位置(kern/init.c中的kern_init函数的起始地址),这样ucore就接管了整个控制权。 当前的ucore功能很简单,只完成基本的内存管理和外设中断管理。 ucore主要完成的工作包括: 初始化终端; 显示字符串; 显示堆栈中的多层函数调用关系; 切换到保护模式,启用分段机制; 初始化中断控制器,设置中断描述符表,初始化时钟中断,使能整个系统的中断机制; 执行while (1)死循环。 以下是实验时碰到的一些概念问题,在此将其记录下来: 什么是Makefile文件? makefile关系到了整个工程的编译规则。 一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令。 makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。 make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如: Delphi的make,VisualC++的nmake,Linux下GNU的make。 可见,makefile都成为了一种在工程方面的编译方法。 文件还保存了编译器和连接器的参数选项,还表述了所有源文件之间的关系(源代码文件需要的特定的包含文件,可执行文件要求包含的目标文件模块及库等).创建程序(make程序)首先读取makefile文件,然后再激活编译器,汇编器,资源编译器和连接器以便产生最后的输出,最后输出并生成的通常是可执行文件.创建程序利用内置的推理规则来激活编译器,以便通过对特定CPP文件的编译来产生特定的OBJ文件. 什么是实模式? 计算机加电的时候其实就是按照8086的寻址方式来寻址,只能访问1MB内存,这个能访问1MB内存的状态我们称为实模式,而把能寻址更多的内存的模式称为保护模式,而这1MB内存的实模式内容分布示意图(引用自新浪博客)如下: 在引导程序加载区: boot/bootasm.S: 定义并实现了bootloader最先执行的函数start,此函数进行了一定的初始化,完成了从实模式到保护模式的转换,并调用bootmain.c中的bootmain函数。 boot/bootmain.c: 定义并实现了bootmain函数实现了通过屏幕、串口和并口显示字符串。 bootmain函数加载ucore操作系统到内存,然后跳转到ucore的入口处执行。 boot/asm.h: 是bootasm.S汇编文件所需要的头文件,主要是一些与X86保护模式的段访问方式相关的宏定义。 ELF文件是什么? ELF(Executableandlinkingformat)文件格式是Linux系统下的一种常用目标文件(ob
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 操作系统 ucorelab1