您好,欢迎来到知库网。
搜索
您的当前位置:首页清华大学操作系统lab1_实验报告

清华大学操作系统lab1_实验报告

来源:知库网


实验1:系统软件启动过程 练习1:

(1) 操作系统镜像文件 ucore.img 是如何一步一步生成的?

在命令行中输入“make V=”

1、首先把C的源代码进行编译成为.o文件,也就是目标文件(红色方框内) 2、 ld命令将这些目标文件转变成可执行文件,比如此处的bootblock.out(绿色方框内) 3、dd命令把bootloder放到ucore.img count的虚拟硬盘之中 4、还生成了两个软件,一个是Bootloader,另一个是kernel。 (2)一个被系统认为是符合规范的硬盘主引导扇区的特征: 在/lab1/tools/sign.c中我们可以了解到

规范的硬盘引导扇区的大小为512字节,硬盘结束标志位55AA

练习2:

(1) 从 CPU 加电后执行的第一条指令开始,单步跟踪 BIOS 的执行

改写Makefile文件

lab1-mon: $(UCOREIMG)

$(V)$(TERMINAL) -e \"$(QEMU) -S -s -d in_asm -D $(BINDIR)/q.log -monitor stdio -hda $< -serial null\"

$(V)sleep 2

$(V)$(TERMINAL) -e \"gdb -q -x tools/lab1init\"

在调用qemu时增加-d in_asm -D q.log参数,便可以将运行的汇编指令保存在q.log

中。

(2) 在初始化位置0x7c00 设置实地址断点,测试断点正常。

在tools/gdbinit结尾加上

set architecture i8086

b *0x7c00 //在0x7c00处设置断点。 continue

x /2i $pc //显示当前eip处的汇编指令

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

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

与bootasm.S 和 bootblock.asm不相同。

这是由于在gdb之中调试的原因,可以直接输入make debug,在生成的qemu虚拟

机之中进行调试可以看到在虚拟机中运行的汇编代码,之后再与bootasm.S 和 bootblock.asm 进行比较。

与bootasm.S和bootblock.asm中的代码相同。

练习3:分析bootlloader进入保护模式的过程(/lab1/boot/bootasm.S)

.globl start start:

.code16

# 关中断,并清除方向标志,即将 DF 置“0”,这样(E)SI 及(E)DI 的修改为增量 cli cld

# 清零各数据段寄存器:DS、ES、FS

xorw %ax, %ax movw %ax, %ds movw %ax, %es movw %ax, %ss

# 使能 A20 地址线,这样 80386 就可以突破 1MB 访存现在,而可访问 4GB 的 32 位地址空间

seta20.1:

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

testb $0x2, %al jnz seta20.1

movb $0xd1, %al outb %al, $0x

seta20.2:

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

testb $0x2, %al jnz seta20.2

movb $0xdf, %al # 打开A20 outb %al, $0x60

# 初始化gdt

lgdt gdtdesc

# 进入保护模式

movl %cr0, %eax

orl $CR0_PE_ON, %eax movl %eax, %cr0

# 长跳转

ljmp $PROT_MODE_CSEG, $protcseg

.code32 protcseg:

# 设置段寄存器,并建立堆栈

movw $PROT_MODE_DSEG, %ax

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

# 设置堆栈 movl $0x0, %ebp

movl $start, %esp # 栈顶为0x7c00 # 进入bootmain,不再返回 call bootmain spin:

jmp spin

练习4:分析bootloader加载ELF格式的OS的过程

读一个扇区的流程可参看bootmain.c 中的 readsect 函数实现。大致如下:

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

4. 连续读 I/O 地址 0x1f0,把磁盘扇区数据读到指定内存。 static void readsect(void *dst, uint32_t secno) { // wait for disk to be ready waitdisk();

outb(0x1F2, 1); // count = 1 outb(0x1F3, secno & 0xFF);

outb(0x1F4, (secno >> 8) & 0xFF); outb(0x1F5, (secno >> 16) & 0xFF);

outb(0x1F6, ((secno >> 24) & 0xF) | 0xE0);

outb(0x1F7, 0x20); // cmd 0x20 - read sectors

// wait for disk to be ready

waitdisk();

// read a sector

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

该函数封装在readseg函数中,该函数完成读取任意的长度。

Notice: uint32_t secno = (offset / SECTSIZE) + 1; # 0号扇区已被引导占用。

最后在bootmain函数中完成加载ELF格式os的操作: 1: 读取ELF的头部

2: 判断ELF文件是否是合法 3: 将描述表的头地址存在ph

4: 按照描述表将ELF文件中数据载入内存

5: 根据ELF头部储存的入口信息,找到内核的入口(不再返回) Notice:可能会出现内存长度>文件长度的现象 多读入部分包含bss节,需要清0

练习5:实现函数调用堆栈跟踪函数 print_stackframe(void) {

uint32_t ebp = read_ebp(), eip = read_eip(); int i, j;

for (i = 0; ebp != 0 && i < STACKFRAME_DEPTH; i ++) { cprintf(\"ebp:0x%08x eip:0x%08x args:\ uint32_t *args = (uint32_t *)ebp + 2; //(uint32_t)calling arguments [0..4] = the contents in address (unit32_t)ebp +2 [0..4] for (j = 0; j < 4; j ++) { cprintf(\"0x%08x \ }

cprintf(\"\\n\");

print_debuginfo(eip - 1);

/*call print_debuginfo(eip-1) to print the C calling function name and line number, etc.*/ eip = ((uint32_t *)ebp)[1]; ebp = ((uint32_t *)ebp)[0]; //popup a calling stackframe } Notice:

ss:ebp指向的堆栈位置储存着caller的ebp,以此为线索可以得到所有使用堆栈的函数ebp。

ss:ebp+4指向caller调用时的eip,ss:ebp+8等是(可能的)参数。 练习6:

(1)中断向量表中一个表项占多少字节?其中哪几位代表中断处理代码的入口?

中断向量表一个表项占用8字节,其中2-3字节是段选择子,0-1字节和6-7字节拼成

位移,入口地址=段选择子+段内偏移量。

(2) 完善kern/trap/trap.c中对中断向量表进行初始化的函数idt_init

可以在/lab1/kern/mm/mmu.h中可以找到SETGATE函数,查找其具体操作。 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 }

lidt(&idt_pd); //载入IDT表

(3) 完善trap.c中的中断处理函数trap,在对时钟中断进行处理的部分填写trap函数 case IRQ_OFFSET + IRQ_TIMER:

ticks ++; //一次中断累加1 if (ticks % TICK_NUM == 0) { print_ticks(); }

break;

扩展:

(1) 内核态切换到用户态:

lab1_switch_to_user(void) {

asm volatile (

\"sub $0x8, %%esp \\n\"

// esp-8 为下一步复制的栈帧留好 tf_ss和tf_esp的位置 \"int %0 \\n\"

\"movl %%ebp, %%esp\" :

: \"i\"(T_SWITCH_TOU) ); } 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个值。 (2) 用户态切换到内核态:

lab1_switch_to_kernel(void) { asm volatile ( \"int %0 \\n\"

\"movl %%ebp, %%esp \\n\" :

: \"i\"(T_SWITCH_TOK) ); }

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返回三个值

Question:

将用户态转到内核态不用临时栈,直接在原来的栈进行操作?

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- zicool.com 版权所有 湘ICP备2023022495号-2

违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务