清华大学操作系统lab1-实验报告.docx
实验1:系统软件启动过程练习1:(1)操作系统镜像文件ucore.img是如何一步-一步生成的?在命令行中输入"makeV="1、首先把C的源代码进行编译成为.o文件,也就是目标文件(红色方框内)2、Id命令将这些目标文件转变成可执行文件,比方此处的bootblock.out(绿色方框内)3、dd命令把bootloder放到ucore.imgcount的虚拟硬盘之中4、还生成了两个软件,一个是Bootloader,另一个是kernelo(2)一个被系统认为是符合标准的硬盘主引导扇区的特征:labltoolssign.c中我们可以了解到标准的硬盘引导扇区的大小为512字节,硬盘结束标志位55AA练习2:(1) 从CPU加电后执行的第一条指令开始,单步跟踪BIOS的执行改写Makefile文件labl-mon:$(UCOREIMG)$(V)$(TERMINAL)-e"S(QEMU)-S-s-din_asm-D$(BINDIR)/q.log-monitorstdio-hda$<-serialnull"$(V)sleep2$(V)$(TERMINAL)-e,gdb-q-×tools/lablinit"在调用qemu时增加-din_asm-Dq.log参数,便可以将运行的汇编指令保存在q.log中。(2) 在初始化位置0x7Coo设置实地址断点,测试断点正常。在tools/gdbinit结尾加上setarchitecturei8086b*0x7c00在0x7c00处设置断点。continueX2i$pc显示当前eip处的汇编指令(3) 将执行的汇编代码与bootasm.S和bootblock.asm进行比拟,看看二者是否致。Notice:在q.log中进入BIOS之后的跳转地址与实际应跳转地址不相符,汇编代码也与bootasm.S和bootblock.asm不相同。这是由于在gdb之中调试的原因,可以直接输入makedebug,在生成的qemu虚拟机之中进行调试可以看到在虚拟机中运行的汇编代码,之后再与bootasm.S和bootblock.asm进行比拟。与bootasm.S和bootblock.asm中的代码相同。练习3:分析bootlloader进入保护模式的过程(lablbootbootasm.S).globlstartstart:.codel6# 关中断,并去除方向标志,即将DF置“0”,这样(E)Sl及(E)Dl的修改为增量clield# 清零各数据段存放器:DS、ES、FS×orw%ax,%axmovw%ax,%dsmovw%ax,%esmovw%ax,%ss# 使能A20地址线,这样80386就可以突破IMB访存现在,而可访问4GB的32位地址空间seta20.1: inb $0x64, %al jnz seta20.1movb $0Xd 1, %aloutb %al, $0x64seta20.2:inb $0x64, %altestb $0x2, %aljnz seta20.2movb $Oxdf, %al outb %al, $0x60# 初始化gdtIgdtgdtdesc# 进入保护模式 movl %crz %eax orl $CRO_PE_ON, %eax movl %eaxz %cr# 长跳转Ijmp $PROT_MODE_CSEG, Sprotcseg .code32protcseg:# 设置段存放器,并建立堆栈 movw $PROT_MODE_DSEG, %a× movw %ax, %ds movw %ax, %es movw %a×, %fs movw %a×, %gs movw %axz %ss# 设置堆栈 movl $0x0, %ebpmovl $start, %esp# 进入bootmain,不再返回 Callbootmain#等待8042键盘控制器不忙testb $0x2, %al#等待8042键盘控制器不忙#翻开A20# -> DS: Data Segment# -> ES: Extra Segment#-> FS#-> GS# -> SS: Stack Segment#栈顶为0x7Coospin:jmpspin练习4:分析bootloader加载ELF格式的OS的过程读一个扇区的流程可参看bootmain.c中的readsect函数实现。大致如下:1 .读I/O地址0xlf7,等待磁盘准备好;2 .写I/O地址OXlf20xlf5,0xlf7,发出读取第OffSeet个扇区处的磁盘数据的命令;3 .读I/O地址0xlf7,等待磁盘准备好;4 .连续读I/O地址OXlfo,把磁盘扇区数据读到指定内存。staticvoidreadsect(void*dstzuint32_tsecno)/waitfordisktobereadywaitdisk();outb(0×lF2z1);/count=1outb(0xlF3zsecno&OxFF);outb(0×lF4,(secno»8)&OxFF);outb(0xlF5z(secno»16)&OxFF);outb(0×lF6z(secno»24)&OxF)OxEO);outb(0×lF7z0x20);/cmd0x20-readsectors/waitfordisktobereadywaitdisk();/readasectorinsl(0xlF0zdst,SECTSIZE/4);该函数封装在readseg函数中,该函数完成读取任意的长度。Notice:uint32_tsecno=(offset/SECTSIZE)+1;#0号扇区已被引导占用。最后在bootmain函数中完成加载ELF格式os的操作:1:读取ELF的头部2:判断ELF文件是否是合法3:将描述表的头地址存在ph4:按照描述表将ELF文件中数据载入内存5:根据ELF头部储存的入口信息,找到内核的入口(不再返回)Notice:可能会出现内存长度>文件长度的现象多读入局部包含bss节,需要清0练习5:实现函数调用堆栈跟踪函数PrinJStaCkframe(VOid)Uint32_tebp=read_ebp(),eip=read_eip();inti,j;for(i=0;ebp!=0&&i<S1ACKFRAME_DEPTH;i+)cprintf("ebp:0x%08xeip:0x%08xargs:",ebp,eip);uint32_t*args=(uint32_t*)ebp+2;/(uint32_t)caIlingarguments0.4=thecontentsinaddress(unit32_t)ebp+20.4for(j=0;j<4;j+)cprintf("0x%08x”,argsj);cprintf("n");print-debuginfo(eip-1);*callprint_debuginfo(eip-l)toprinttheCcallingfunctionnameandlinenumber,etc.*/eip=(uint32-t*)ebp)l;ebp=(uint32-t*)ebp)0;/popupacallingstackframeNotice:ss:ebp指向的堆栈位置储存着caller的ebp,以此为线索可以得到所有使用堆栈的函数ebp。ss:ebp+4指向CalIer调用时的eip,ss:ebp+8等是(可能的)参数。练习6:(1)中断向量表中一个表项占多少字节?其中哪几位代表中断处理代码的入口?中断向量表一个表项占用8字节,其中2-3字节是段选择子,0-1字节和6-7字节拼成位移,入口地址=段选择子+段内偏移量。完善kern/trap/trap.c中对中断向量表进行初始化的函数idtjnit可以在lablkernmmmmu.h中可以找到SETGATE函数,查找其具体操作。idtjnit(void)externuintptr_t_vectors;inti;for(i=0;i<sizeof(idt)/Sizeof(Structgatedesc);i+)SETGATE(idti,0,GD_KTEXT_vectorsi,DPL_KERNEL);设置IDTIidt(&idt_pd);载入IDT表完善trap.c中的中断处理函数tr叩,在对时钟中断进行处理的局部填写仃叩函数caseIRQ_OFFSET+IRQ_TIMER:ticks+;一次中断累加1if(ticks%TICK-NUM=三0)PrinJtiCkS();break;扩展:(1)内核态切换到用户态:labl_switch_to_user(void)asmvolatile("sub$0x8,%espn"/esp-8为下一步复制的栈帧留好tf_ss和tf_esp的位置"int%0n""movl%ebpz%esp":"i"(T_SWITCH_TOU);caseT_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(Structtrapframe)-8;在执行intl20前系统在核心态,int不会引起栈的切换switchk2u.tf-eflags=FL_IOPL_MASK;*(uint32_t*)tf-1)=(uint32_t)&switchk2u;break;vectorsalltrapscsp-8CPUZl 入tCcagt(cp t(cfU 步 tfa tf Clp tf_crr tf tnno tf<h-USER DS- FL_K)PL_MASKUSER CS最后iret时返回5个值。 用户态切换到 内核态:tf CStfcftffttr胪tf_fe Srcgureg careg cdi- If krvg cditf CfTtf_trapno-USER DS= USER DSswitchk2ulabl_switch_to_kernel(void) asmvolatile("int%0n""movl%ebpz%espn":"i"(T_SWITCH_TOK)caseT_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=(Structtrapframe*)(tf->tf-esp-(Sizeof(Structtrapframe)-8);复制memmove(switchu2kztfSizeof(Structtrapframe)-8);在执行intl20前系统在核心态,int会引起栈的切换,iret不会引起栈的切换*(uint32_t*)tf-1)=(Uint32_t)SWitChU2k;/*设置临时栈,指向SWitChU2k,这样iret返回时,CPU会从switchu2k恢复数据,而不是从现有栈恢复数据。*/break;最后iret返回三个值Question:将用户态转到内核态不用临时栈,直接在原来的栈进行操作?