ucore - LAB1实验报告
更新时间:2024-03-19 10:35:01 阅读量: 综合文库 文档下载
- ucore操作系统推荐度:
- 相关推荐
LAB1实验报告
实验目的:
操作系统是一个软件,也需要通过某种机制加载并运行它。在这里我们将通过另外一个更加简单的软件-bootloader来完成这些工作。为此,我们需要完成一个能够切换到x86的保护模式并显示字符的bootloader,为启动操作系统ucore做准备。lab1提供了一个非常小的bootloader和ucore OS,整个bootloader执行代码小于512个字节,这样才能放到硬盘的主引导扇区中。通过分析和实现这个bootloader和ucore OS,通过分析和实现这个bootloader和ucore OS,我们可以了解到: ? 基于分段机制的存储管理 ? 设备管理的基本概念
? PC启动bootloader的过程 ? bootloader的文件组成
? 编译运行bootloader的过程 ? 调试bootloader的方法 ? ucore OS的启动过程
? 在汇编级了解栈的结构和处理过程 ? 中断处理机制
? 通过串口/并口/CGA输出字符的方法 实验内容: 一.练习
练习1:理解通过make生成执行文件的过程。
1. ucore.img是如何一步一步生成的?(需要比较详细地解释Makefile中每一条相关命令和命令参数的含义,以及说明命令导致的结果)
答:Makefile按照如下步骤生成ucore.img(lab1/bin下的ucore.img):
①为每一个源文件(.c和.S文件)产生一个描述其依赖关系的makefile文件,以.d为后缀。即对于一个源文件“NAME.c”,对应的这个makefile文件为“NAME.d”。包括分别生成sign.c、bootmain.c、bootasm.S的makefile依赖文件sign.d、bootmain.d、bootasm.d,具体执行的命令如下: mkdir -p obj/sign/tools gcc -Itools/ -g -Wall -O2 -MM tools/sign.c -MT \obj/sign/tools/sign.d mkdir -p obj/boot gcc -Iboot/ -fno-builtin -Wall -ggdb -m32 -nostdinc -fno-stack-protector -Ilibs/ -Os -nostdinc -MM boot/bootmain.c -MT \ gcc -Iboot/ -fno-builtin -Wall -ggdb -m32 -nostdinc -fno-stack-protector -Ilibs/ -Os -nostdinc -MM boot/bootasm.S -MT \gcc重要的编译参数: -I
-nostdinc 只为头文件寻找-I选项指定的目录
-fno-builtin 除非利用\进行引用,否则不识别所有内建函数 -fno-stack-protector 不检测缓存溢出
②编译源文件,只生成目标文件但不链接。包括由bootasm.S、bootmain.c、sign.c分别生成bootasm.o、bootmain.o、sign.o。具体执行的命令如下: gcc -Iboot/ -fno-builtin -Wall -ggdb -m32 -nostdinc -fno-stack-protector -Ilibs/ -Os -nostdinc -c boot/bootasm.S -o obj/boot/bootasm.o gcc -Iboot/ -fno-builtin -Wall -ggdb -m32 -nostdinc -fno-stack-protector -Ilibs/ -Os -nostdinc -c boot/bootmain.c -o obj/boot/bootmain.o gcc -Itools/ -g -Wall -O2 -c tools/sign.c -o obj/sign/tools/sign.o gcc重要的编译参数: -c 完成编译或汇编工作,但是不链接,以目标文件(.o)形式输出
③由目标文件sign.o生成可执行文件sign,生成sign工具(生成引导区内容) mkdir -p bin gcc -g -Wall -O2 obj/sign/tools/sign.o -o bin/sign
④链接bootasm.o、bootmain.o生成bootblock.o,设定程序入口为start,地址为0x7C00。具体执行的命令如下: ld -m elf_i386 -N -e start -Ttext 0x7C00 obj/boot/bootasm.o obj/boot/bootmain.o -o obj/bootblock.o ld链接参数: -e 设置程序入口位置 -Ttext 设置函数地址
-m elf_i386 设定为i386平台ELF执行文件格式
⑤将bootblock.o内容进行反汇编,写入文件bootblock.asm。具体执行的命令如下: objdump -S obj/bootblock.o > obj/bootblock.asm 命令参数含义:
-S 源代码中混合有反汇编
⑥将目标文件bootblock.out全部内容拷贝到bootblock.o中,并转换为raw二进制(raw binary)格式。具体执行的命令如下: objcopy -S -O binary obj/bootblock.o obj/bootblock.out 命令参数含义: -S 去除掉源文件的符号信息和重分配信息 -O binary 生成二进制(binary)格式的输出文件
⑦使用sign工具生成引导区内容装载bootblock.out,生成bin目录下的bootblock。具体执行的命令如下: bin/sign obj/bootblock.out bin/bootblock
⑧生成ucore.img。分两步:先使用空白字符(/dev/zero)初始化ucore.img,再以bootblock为输入,输出最终硬盘镜像ucore.img。具体执行的命令如下: dd if=/dev/zero of=bin/ucore.img count=10000 dd if=bin/bootblock of=bin/ucore.img conv=notrunc 命令参数含义: if=file 输入文件名,缺省为标准输入 of=file 输出文件名,缺省为标准输出
count=blocks 仅拷贝 blocks 个块,块大小等于 ibs 指定的字节数
conv=conversion[,conversion...] 用指定的参数转换文件 conv=notrunc 不截短输出文件
2.一个被系统认为是符合规范的硬盘主引导扇区的特征是什么? 答:根据代码sign.c中的内容,一个被系统认为是符合规范的硬盘主引导扇区具有如下特征: (1)硬盘主引导扇区共512(200h)字节;
(2)硬盘主引导程序大小不超过510字节,且位于主引导扇区第0-509(0-1FDh)字节,若程序大小不足510字节,剩余空间补零;
(3)引导扇区有有效标志,位于第510-511(1FEh-1FFh)字节处,值为AA55h,即1FEh字节存0x55,1FFh字节存0xAA。
练习2:使用qemu执行并调试lab1中的软件。
1. 从CPU加电后执行的第一条指令开始,单步跟踪BIOS的执行。 答:通过改写Makefile文件 () debug: $(UCOREIMG) $(V)$(TERMINAL) -e \-serial null\ //qemu模拟 $(V)sleep 2 //停止2ms $(V)$(TERMINAL) -e \ //gdb调试
在调用qemu时增加-d in_asm -D q.log参数,便可以将运行的汇编指令保存在q.log中。 为防止qemu在gdb连接后立即开始执行,删除了tools/gdbinit中的\行。
2.在初始化位置 0x7c00设置实地址断点,测试断点正常。 答:将tools/gdbinit改为: file obj/bootblock.o target remote :1234 set architecture i8086 //设置为8086模式 b *0x7c00 //设置断点为0x7c00 continue x /2i $pc //反汇编形式输出两条指令 运行\便可得到
说明断点调试成功。
3.在调用qemu 时增加-d in_asm -D q.log 参数,便可以将运行的汇编指令保存在q.log 中。将执行的汇编代码与bootasm.S和bootblock.asm进行比较,看看两者是否一致。 答:在q.log中进入BIOS之后的跳转地址与实际应跳转地址不相符,汇编代码也 与bootasm.S 和 bootblock.asm不相同。 如图
q.log与bootasm.S 和 bootblock.asm并不相同,甚至完全不一样。
这是由于在gdb之中调试的原因,可以直接输入make debug,在生成的qemu虚拟 机之中进行调试可以看到在虚拟机中运行的汇编代码,之后再与bootasm.S 和 bootblock.asm 进行比较。
这样得出的前20条指令与bootasm.S 和 bootblock.asm中的指令完全一致。
练习3:分析bootloader 进入保护模式的过程。 答:如下: (1) .globl start start: .code16 # Assemble for 16-bit mode cli # Disable interrupts cld # String operations increment
(2) //设置重要的段寄存器(DS, ES, SS). xorw %ax, %ax # Segment number zero movw %ax, %ds # -> Data Segment movw %ax, %es # -> Extra Segment movw %ax, %ss # -> Stack Segment (3) // 开启A20: 为了兼容8086,解决wrap-around的bug,IBM使用键盘控制器上剩余的一些输出线来管理第21条地址线(从0开始为20),称为A20。如果A20被开启,当程序员给出0x100000~0x10ffef之间的地址时,系统将真正访问这块内存区域;如果A20被禁用,则系统仍按照8086/8088的方式,回绕从0开始找内存区域。
seta20.1: inb $0x64, %al # Wait for not busy testb $0x2, %al jnz seta20.1 movb $0xd1, %al # 0xd1 -> port 0x64 outb %al, $0x64
seta20.2: inb $0x64, %al # Wait for not busy testb $0x2, %al jnz seta20.2 movb $0xdf, %al # 0xdf -> port 0x60 outb %al, $0x60 (4) lgdt gdtdesc //重新加载GDT表 movl %cr0, êx orl $CR0_PE_ON, êx //设置CR0寄存器PE位,PE位(第0位)是启用保护(Protection Enable)标志位。该位为0时为实地址模式,设置为1时开启保护模式,开启分页机制 movl êx, %cr0 ljmp $PROT_MODE_CSEG, $protcseg
(5)//进入32位模式,$PROT_MODE_CSEG表示段选择子,被加载到CS寄存器中,$protcseg被加载到IP寄存器中。CS、IP寄存器会重新加载,后面的代码都在32位保护模式下执行。
.code32 # Assemble for 32-bitmode protcseg:
//设置保护模式下的段寄存器
movw $PROT_MODE_DSEG, %ax # Our data segment lector movw %ax, %ds # -> DS: Data Segment movw %ax, %es # -> ES: Extra Segment movw %ax, %fs # -> FS movw %ax, %gs # -> GS
movw %ax, %ss # -> SS: Stack Segment //设置栈指针,程序跳转到0x7c00 movl $0x0, ?p movl $start, %esp
call bootmain
练习4:分析bootloader加载ELF格式的OS 1. bootloader如何读取硬盘扇区的?
答:读一个扇区的流程可参看bootmain.c 中的 readsect 函数实现。大致如下:
1. 读 I/O 地址 0x1f7,等待磁盘准备好;
2. 写 I/O 地址 0x1f2~0x1f5,0x1f7,发出读取第 offseet 个扇区处的磁盘数据的命令; 3. 读 I/O 地址 0x1f7,等待磁盘准备好;
4. 连续读 I/O 地址 0x1f0,把磁盘扇区数据读到指定内存。 /* readsect - read a single sector at @secno into @dst */ static void
readsect(void *dst, uint32_t secno) { // 第1步:等待硬盘准备好 waitdisk();
// 第2步:发出读取扇区的命令 outb(0x1F2, 1); // 读取一个扇区 outb(0x1F3, secno & 0xFF); // LBA参数的0~7位 outb(0x1F4, (secno >> 8) & 0xFF); // LBA参数的8~15位 outb(0x1F5, (secno >> 16) & 0xFF); // LBA参数的16~23位 outb(0x1F6, ((secno >> 24) & 0xF) | 0xE0); // LBA参数的24~27位 outb(0x1F7, 0x20); // 状态和命令寄存器。命令 0x20 - 读取扇区 // 第3步:等待硬盘准备好 waitdisk();
// 第4步:把磁盘扇区数据读取到指定内存 insl(0x1F0, dst, SECTSIZE / 4); }
2. bootloader是如何加载ELF格式的OS?
答:bootmain函数中完成加载ELF格式os的操作:
1: 读取ELF的头部
2: 判断ELF文件是否是合法 3: 将描述表的头地址存在ph
4: 按照描述表将ELF文件中数据载入内存
5: 根据ELF头部储存的入口信息,找到内核的入口(不再返回) 具体在bootmain.c的函数main()中实现: // 读取硬盘的第一页(载入kernel的文件头) readseg((uintptr_t)ELFHDR, SECTSIZE * 8, 0); // 判断是否为有效的ELF if (ELFHDR->e_magic != ELF_MAGIC) { goto bad; } struct proghdr *ph, *eph; // 从硬盘载入程序片段到内存(忽略ph标志) ph = (struct proghdr *)((uintptr_t)ELFHDR + ELFHDR->e_phoff); eph = ph + ELFHDR->e_phnum;
for (; ph < eph; ph ++) { readseg(ph->p_va & 0xFFFFFF, ph->p_memsz, ph->p_offset); }
// 从elf->entry为入口进入程序 // 注意:不返回
((void (*)(void))(ELFHDR->e_entry & 0xFFFFFF))();
练习5:实现函数调用堆栈跟踪函数 答:void print_stackframe(void{
uint32_t ebp = read_ebp(), eip = read_eip(); int i, j;
for (i = 0; ebp != 0 && i < STACKFRAME_DEPTH; i ++) { cprintf(\ uint32_t *args = (uint32_t *)ebp + 2; for (j = 0; j < 4; j ++) {
cprintf(\ }
cprintf(\
print_debuginfo(eip - 1); eip = ((uint32_t *)ebp)[1]; ebp = ((uint32_t *)ebp)[0];
} }
输出与原表大致一致。
ss:ebp指向的堆栈位置储存着caller的ebp,以此为线索可以得到所有使用堆栈的函数ebp。 ss:ebp+4指向caller调用时的eip,ss:ebp+8等是(可能的)参数。
输出中,堆栈最深一层为 ebp:0x00007bf8 eip:0x00007d64 args:0xc031fcfa 0xc08ed88e0x64e4d08e 0xfa7502a8
其对应的是第一个使用堆栈的函数,bootmain.c中的bootmain。
bootloader设置的堆栈从0x7c00开始,使用\转入bootmain函数。
call指令压栈,所以bootmain中ebp为0x7bf8。返回地址eip为0x0000d64,之后四个是参数args:0xc031fcfa 0xc08ed88e0x64e4d08e 0xfa7502a8.
练习6:中断初始化和处理
1. (保护模式中)中断向量表中一个表项占多少字节?其中哪几位代表中断处理代码的入口?
答:中断向量表中一个表项占8个字节,其中第0~15,48~63位代表中断处理代码的入口。
2. 完成trap.c中对中断向量表进行初始化的函数idt_init。在idt_init函数中,依次对所有中断入口进行初始化。使用mmu.h中的SETGATE宏,填充idt数组内容。注意除了系统调用中断(T_SYSCALL)以外,其它中断均使用中断门描述符,权限为内核态权限;而系统调用中断使用异常,权限为陷阱门描述符。每个中断的入口由tools/vectors.c生成,使用trap.c中声明的vectors数组即可。 答:idt_init(void) {
extern uintptr_t __vectors[]; int i;
for (i = 0; i < sizeof(idt) / sizeof(struct gatedesc); i ++) {
SETGATE(idt[i], 0, GD_KTEXT, __vectors[i], DPL_KERNEL); }//初始化每一条IDT项 // 设置内核态到用户态的转换
SETGATE(idt[T_SWITCH_TOK], 0, GD_KTEXT, __vectors[T_SWITCH_TOK], DPL_USER); // 载入IDT lidt(&idt_pd); }
3. 完成trap.c中的中断处理函数trap对时钟中断进行处理的部分填写trap函数中处理时钟中断的部分,使操作系统每遇到100次时钟中断后,调用print_ticks子程序,向屏幕上打印一行文字”100 ticks”。
答:case IRQ_OFFSET + IRQ_TIMER: ticks ++;
if (ticks % TICK_NUM == 0) { print_ticks();
}//当有100次时钟中断输出一次 break;
拓展练习:增加syscall功能,即增加一用户态函数(可执行一特定系统调用:获得时钟计数值),当内核初始完毕后,可从内核态返回到用户态的函数,而用户态的函数又通过系统调用得到内核态的服务。 答:⑴内核态切换到用户态:
lab1_switch_to_user(void) {
asm volatile (
\
// esp-8 为下一步复制的栈帧留好 tf_ss和tf_esp的位置 \
\ :
: \ );
}
case T_SWITCH_TOU:
if (tf->tf_cs != USER_CS) { switchk2u = *tf;
switchk2u.tf_cs = USER_CS;
switchk2u.tf_ds = switchk2u.tf_es = switchk2u.tf_ss = USER_DS; switchk2u.tf_esp = (uint32_t)tf + sizeof(struct trapframe) - 8; //在执行int120前系统在核心态,int不会引起栈的切换
switchk2u.tf_eflags |= FL_IOPL_MASK;
*((uint32_t *)tf - 1) = (uint32_t)&switchk2u; }
break;
最后iret时返回5个值。 ⑵用户态切换到内核态:
lab1_switch_to_kernel(void) { asm volatile ( \
\ :
: \ ); }
case T_SWITCH_TOK:
if (tf->tf_cs != KERNEL_CS) { tf->tf_cs = KERNEL_CS;
tf->tf_ds = tf->tf_es = KERNEL_DS; tf->tf_eflags &= ~FL_IOPL_MASK;
//定位临时栈的栈顶
switchu2k = (struct trapframe *)(tf->tf_esp - (sizeof(struct trapframe) - 8));
//复制
memmove(switchu2k, tf, sizeof(struct trapframe) - 8);
//在执行int120前系统在核心态,int会引起栈的切换,iret不会引起 //栈的切换
*((uint32_t *)tf - 1) = (uint32_t)switchu2k;
/*设置临时栈,指向switchu2k,这样iret返回时,CPU会从switchu2k恢复数据, 而不是从现有栈恢复数据。*/
} break;
最后iret返回三个值
二.源码分析
此次实验的代码部分主要由bootloader和ucore操作系统部分组成。 bootloader部分:
boot/bootasm.S:定义并实现了bootloader最先执行的函数start,此函数进行了一定的初始化,完成了从实模式到保护模式的转换,并调用bootmain.c中的bootmain函数; boot/bootmain.c:定义并实现了bootmain函数实现了通过屏幕、串口和并口显示字符串。bootmain函数加载ucore操作系统到内存,然后跳转到ucore的入口处执行。 boot/asm.h:是bootasm.S汇编文件所需要的头文件,主要是一些与X86保护模式的段访问方式相关的宏定义。
Ucore操作系统部分主要包含系统初始化部分、内存管理部分、外设驱动部分、中断处理部分、内核调试部分。
初始化部分:kern/init 内存管理部分:kern/mm 外设驱动部分:kern/driver 中断处理部分:kern/debug
公共库部分和工具部分:libs、tools
Bootloader启动过程: 1.
将通过读取硬盘主引导扇区到内存,并转跳到对应内存中的位置执行
2.将通过读取硬盘主引导扇区到内存,并转跳到对应内存中的位置执行bootloader。 读ELF文件:
跳转到入口:
实验总结:
通过这次实验我收获了关于C语言编程与理解,汇编语言理解,linux基本命令应用的相关知识,了解了操作系统是如何搭建起来的,结合书本上的理论知识对操作系统有了更深一步的理解,但是在做实验的过程中由于自己水平有限,还是遇到了很多困难,但是最终还是在同学的帮助以及查资料解决了它们。
正在阅读:
ucore - LAB1实验报告03-19
新苏教版四年级整数四则混合运算练习题09-07
2017年四川大学本科国际及港澳台03-08
大班阴雨天活动方案06-04
湘教版七年级下册单元检测卷9套07-06
武汉理工大学专选课 - 嵌入式系统复习题05-24
江苏省女职工劳动保护特别规定-2018年7月1日起实施05-01
国信证券基础知识培训08-14
- 天大砼方案 - 图文
- 农业科技网络书屋能力提升_玉米错题选
- DNS习题
- 浅议检察官对罪犯谈话的技巧与效果
- 高考语文文言文翻译专题训练
- AB类学科竞赛目录(2015)
- 建筑面积计算新规定(2015最新)
- Revit2012初级工程师题集一
- 十三五项目米线可行性报告
- 2013体育学院党组织建设工作总结
- 2014Revit工程师题库
- 高中数学如何实施研究性学习
- 茶艺表演 中英互译
- 小学音乐湘文艺版 四年级下册 第十一课《(歌表演)脚印》优质课公
- 山西省农村合作经济承包合同管理条例
- 2015年镇江市中考化学一模试题参考答案及评分标准(定稿)
- 统计 题集
- 批评意见清单
- 8潞安集团蒲县黑龙关煤矿矿业公司2
- 鄂教版四年级语文上册复习精要(光谷四小)
- 实验
- 报告
- ucore
- LAB1
- 监理单位安全生产管理责任制度(一)
- 2007公需科目考试答案(五套)
- 证券投资实务课后答案
- 恢复原状请求权的性质
- 2019-2020年高三一轮复习测试(二)数学理试题 含答案
- 2011河北职称计算机考试操作题答案
- Z010关于制定2014年度综合计划的通知
- 商业银行零售业务市场营销策略探析
- WQS软件介绍 - 图文
- 2019运用多媒体辅助教学手段提高数学的课堂教学效率语文
- 2016年盘条调研及发展前景分析
- 类比数学在初中数学中的重要作用
- 2018注册会计师《战略》考点:国际化经营的战略类型、国际市场进
- 肢体语1
- 检测技术 施文康 第三版 考试用课后作业(西安理工大学 光信07级
- 促进母乳喂养成功的十点措施
- 食品安全科普知识竞赛试题及答案(100题)
- 世纪彩城商业策划及营销执行方案 - 图文
- 管理信息系统第四版习题考点题库 - 黄梯云
- 2016-2022年中国计算机工作站行业全景调研及发展趋势研究报告 -