清华大学操作系统实验lab1实验报告Word文档下载推荐.docx

上传人:b****5 文档编号:18894228 上传时间:2023-01-02 格式:DOCX 页数:11 大小:448.48KB
下载 相关 举报
清华大学操作系统实验lab1实验报告Word文档下载推荐.docx_第1页
第1页 / 共11页
清华大学操作系统实验lab1实验报告Word文档下载推荐.docx_第2页
第2页 / 共11页
清华大学操作系统实验lab1实验报告Word文档下载推荐.docx_第3页
第3页 / 共11页
清华大学操作系统实验lab1实验报告Word文档下载推荐.docx_第4页
第4页 / 共11页
清华大学操作系统实验lab1实验报告Word文档下载推荐.docx_第5页
第5页 / 共11页
点击查看更多>>
下载资源
资源描述

清华大学操作系统实验lab1实验报告Word文档下载推荐.docx

《清华大学操作系统实验lab1实验报告Word文档下载推荐.docx》由会员分享,可在线阅读,更多相关《清华大学操作系统实验lab1实验报告Word文档下载推荐.docx(11页珍藏版)》请在冰豆网上搜索。

清华大学操作系统实验lab1实验报告Word文档下载推荐.docx

ddif=bin/bootblockof=bin/ucore.imgconv=notrunc

记录了1+0的读入

记录了1+0的写出

512字节(512B)已复制,0.000214966秒,2.4MB/秒

从这几条指令中可以看出需要生成ucore.img首先需要生成bootblock,而生成bootblock需要先生成bootmain.o和bootasm.o还有sign,这三个文件又分别由bootmain.c、bootasm.S、sigh.c来生成。

ld-melf_i386-N-estart-Ttext0x7C00obj/boot/bootasm.oobj/boot/bootmain.o–

oobj/bootblock.o

这句话用于生成bootblock,elf_i386表示生成elf头,0x7C00为程序的入口。

这句话表示生成的bootblock的文件大小,因为大小不到512字节,所以需要给blootblock填充,填充的功能在sign.c中有所体现,最后两字节设置为了0x55,0xAA

buf[510]=0x55;

buf[511]=0xAA;

FILE*ofp=fopen(argv[2],"

wb+"

);

size=fwrite(buf,1,512,ofp);

[练习1.2]一个被系统认为是符合规范的硬盘主引导扇区的特征是什么?

前面已经提到过:

引导扇区的大小为512字节,最后两个字节为标志性结束字节0x55,0xAA,做完这样的检查才能认为是符合规范的磁盘主引导扇区。

Sign.c文件中有作检查:

if(size!

=512){

fprintf(stderr,"

write'

%s'

error,sizeis%d.\n"

argv[2],size);

return-1;

}

 

练习2:

使用qemu执行并调试lab1中的软件。

[练习2.1]从CPU加电后执行的第一条指令开始,单步跟踪BIOS的执行。

[练习2.2]在初始化位置0x7c00设置实地址断点,测试断点正常。

[练习2.3]在调用qemu时增加-din_asm-Dq.log参数,便可以将运行的汇编指令保存在q.log中。

将执行的汇编代码与bootasm.S和bootblock.asm进行比较,看看二者是否一致。

实验是基于老lab1/proj1做的,练习开始时是打算用命令行一句一句执行得到结果的,后来发现直接修改makefile和gdbinit可以大大提高调试效率。

于是在makefile中增加以下代码

fileobj/bootblock.o

targetremote:

1234

setarchitecturei8086

b*0x7c00

continue

x/10i$pc

lab1-mon:

$(UCOREIMG)

$(V)$(TERMINAL)-e"

$(QEMU)-S-s-din_asm-D$(BINDIR)/q.log-monitorstdio-hda$<

-serialnull"

$(V)sleep2

gdb-q-tui-xtools/gdbinit"

-S–s是使得qemu在执行第一条指令之前停下来,在调用qemu时增加-din_asm-Dq.log参数,便可以将运行的汇编指令保存在q.log中。

然后sleep两秒应该是给qemu充分的时间准备等待连接。

接下来使用GDB调试工具,-tui提供了代码与命令行分屏查看的界面,tools/gdbinit中存放的是gdb调试指令如下。

先是加载调试文件,然后连接qemu,设置8086的实模式,设置断点0x7c00,也就是bootloader的第一条指令,然后运行到断点。

再显示接下来的10条指令。

运行结果图如下

很明显,断点位置的代码和boot.asm文件中的代码完全一致,说明断点设置成功。

打开q.log文件看,看到了很奇葩的结果。

能够看到cli,cld之类熟悉的指令,但是他们的地址以及出现的顺序都不是想象的那样(从0x0x00007c00k开始,第一条指令为cld)。

之后听大神解释,在q.log中进入BIOS之后的跳转地址与实际应跳转地址不相符,汇编代码也 

与bootasm.S 

和 

bootblock.asm不相同。

可以通过makedebug之后在qemu的控制台中输入x/10i$pc看到BIOS执行bootloader部分的代码。

进过对比,这些代码与bootasm.S与bootblock.asm中的代码完全一致。

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

在开启A20之前,BIOS还做了很多事:

关中断、清除方向标志,给各个数据段清零。

cli#Disableinterrupts

cld#Stringoperationsincrement

#Setuptheimportantdatasegmentregisters(DS,ES,SS).

xorw%ax,%ax#Segmentnumberzero

movw%ax,%ds#->

DataSegment

movw%ax,%es#->

ExtraSegment

movw%ax,%ss

seta20.1:

inb$0x64,%al#等待8042键盘控制器不忙

testb$0x2,%al

jnzseta20.1

movb$0xd1,%al#发送写8042输出端口的指令

outb%al,$0x64

seta20.2:

jnzseta20.2

movb$0xdf,%al#打开A20

outb%al,$0x60

1、为何开启A20,以及如何开启A20?

当A20地址线控制禁止时,则程序就像在8086中运行,1MB以上的地是不可访问的。

在保护模式下A20地址线控制是要打开的。

为了使能所有地址位的寻址能力,必须向键盘控制器8042发送一个命令。

键盘控制器8042将会将它的的某个输出引脚的输出置高电平,作为A20地址线控制的输入。

一旦设置成功之后,内存将不会再被绕回(memorywrapping),这样我们就可以寻址intel80286CPU支持的16M内存空间,或者是寻址intel80386以上级别CPU支持的所有4G内存空间了。

lgdtgdtdesc#把gdt表的起始位置和界限装入GDTR寄存器

movl%cr0,%eax

orl$CR0_PE_ON,%eax

movl%eax,%cr0#把保护模式位开启

2、如何初始化GDT表?

gdt:

SEG_NULLASM#空段

SEG_ASM(STA_X|STA_R,0x0,0xffffffff)#代码段(起始地址,大小)

SEG_ASM(STA_W,0x0,0xffffffff)#数据段(起始地址,大小)

复习一下cr0寄存器,它的第0位为保护模式位PE:

设置PE将让处理器工作在保护模式下。

复位PE将返回到实模式工作。

此外,gdtdesc指出了全局描述符表在符号gdt处,如下

上面四句话实现了打开保护模式位。

3、如何使能进入保护模式?

通过长跳转指令

ljmp$PROT_MODE_CSEG,$protcseg

进入了保护模式。

进入保护模式之后还有一个步骤:

把所有的数据段寄存器指向上面的GDT描述符表中的数据段(0x10)。

练习四、分析bootloader加载ELF格式的OS的过程。

staticvoid

readseg(uintptr_tva,uint32_tcount,uint32_toffset){

uintptr_tend_va=va+count;

//指针移到边界

va-=offset%SECTSIZE;

//计算开始读的第一个扇区号

uint32_tsecno=(offset/SECTSIZE)+1;

//逐个读取扇区

for(;

va<

end_va;

va+=SECTSIZE,secno++){

readsect((void*)va,secno);

}

//实现了从kernel复制8个扇区(包含ELF头,共4KB)到0x10000

在proj2中,增加主要增加了对磁盘简单的读取函数readsect()readseg()),以及对ELF头的解析(ELF头结构在ELF.h文件中)。

疑问:

为什么要把ELF头读到0X10000?

从哪读?

以下为一些硬件端口上实现读取一个扇区到内存

/*readsect-readasinglesectorat@secnointo@dst*/

readsect(void*dst,uint32_tsecno){

//waitfordisktobeready

waitdisk();

outb(0x1F2,1);

//count=1

outb(0x1F3,secno&

0xFF);

outb(0x1F4,(secno>

>

8)&

outb(0x1F5,(secno>

16)&

outb(0x1F6,((secno>

24)&

0xF)|0xE0);

outb(0x1F7,0x20);

//cmd0x20-readsectors

//readasector

insl(0x1F0,dst,SECTSIZE/4);

0x10000。

Readsect()函数的工作大致是:

1.读I/O地址0x1f7,等待磁盘准备好;

2.写I/O地址0x1f2~0x1f5,0x1f7,发出读取第offseet个扇区处的磁盘数据的命令;

3.读I/O地址0x1f7,等待磁盘准备好;

4.连续读I/O地址0x1f0,把磁盘扇区数据读到指定内存。

练习五、实现函数调用堆栈跟踪函数(需要编程)

uint32_tebp=read_ebp(),eip=read_eip();

inti,j;

for(i=0;

ebp!

=0&

&

i<

STACKFRAME_DEPTH;

i++){

cprintf("

ebp:

0x%08xeip:

0x%08xargs:

"

ebp,eip);

uint32_t*args=(uint32_t*)ebp+2;

for(j=0;

j<

4;

j++){

0x%08x"

args[j]);

\n"

print_debuginfo(eip-1);

eip=((uint32_t*)ebp)[1];

ebp=((uint32_t*)ebp)[0];

可以获知栈底是在高地址,栈顶在低地址,压栈的次序为:

参数(编程的时候默认有四个参数)、返回地址、上一层EBP、局部变量。

注:

read_ebp()和readeip()都是通过内联汇编实现的。

Eip-1是为了能找到上一条指令

结果图:

练习六、完善中断初始化和处理(需要编程)

[练习6.1]中断向量表中一个表项占多少字节?

其中哪几位代表中断处理代码的入口?

中断向量表一个表项占用8字节,其中2-3字节是段选择子,0-1字节和6-7字节拼成位移,两者联合便是中断处理程序的入口地址。

2.请编程完善kern/trap/trap.c中对中断向量表进行初始化的函数idt_init。

idt_init(void){

externuintptr_t__vectors[];

inti;

sizeof(idt)/sizeof(structgatedesc);

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);

caseIRQ_OFFSET+IRQ_TIMER:

ticks++;

if(ticks%TICK_NUM==0){

print_ticks();

}//当有100次时钟中断输出一次

break;

3.请编程完善trap.c中的中断处理函数trap,在对时钟中断进行处理的部分填写trap函数中处理时钟中断的部分,使操作系统每遇到100次时钟中断后,调用print_ticks子程序,向屏幕上打印一行文字”100ticks”。

练习7、增加syscall功能,即增加一用户态函数(可执行一特定系统调用:

获得时钟计数值),当内核初始完毕后,可从内核态返回到用户态的函数,而用户态的函数又通过系统调用得到内核态的服务。

先附上两个最重要的图,分别是内核态切换到用户态、用户态切换到内核态的过程。

这部分是最花时间的,光是看代码就有很多疑问。

1、为什么需要构建一个临时的trapframe来实现切换栈,切换栈说白了不就是需要修改那几个寄存器吗?

2、PPT上两个切换过程中的的两个老栈顶是一个地址吗?

老栈顶是什么意思?

3、切换到用户态的过程中,trapframe的tf_esp为什么要指向原来的内核栈?

有什么意义吗?

4、切换到内核态的过程中,CPU压入的ss和esp是用户栈的还是内核栈的?

种种疑惑都出现在我的脑海里,加上不同的同学对这些问题有不同的理解,我也不想再去回想他们的理解了。

直到看到“在执行int120之前,系统在核心态,int不会引起切换,切换工作需在iret中完成。

在执行int121之前,系统在用户态,int引起切换,iret不需要再切换。

”,感觉好像明白了一点。

说一下我对上述两个过程的理解。

●内核到用户:

首先,我认为esp-8这个步骤不是必要的,因为从后续的过程看来,空出来的8个字节从来没有被使用过。

然后开始中断,由于是在内核态,所以ss和esp暂时还不需要改变,在原来的内核栈里就可以处理。

然后通过CPU压入和操作系统压入内核栈的寄存器数据,构造了一个临时trapframe——switchk2u,通过popesp跳到switchk2u,然后修改switchk2u的tf_esp,tf_ds,tf_es,tf_cs,tf_eflags,再通过一系列的pop和iret实现了对这些寄存器的修改——即实现了堆栈段、代码段和数据段的切换。

●用户到内核:

因为是int121的关系,ss和esp在执行中断例程之前就已经切换到了内核态,此后的操作都是在内核栈,还是由压入的寄存器状态构造了一个临时的trapframe——switchk2u,把tf_cs,tf_ds,tf_es,tf_eflags改掉之后,听过一系列pop和iret跳到了正确的代码段和数据段,堆栈段就不用跳了,因为中断的时候就已经跳到内核堆栈了。

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 人文社科 > 设计艺术

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1