MIT 6.828 学习笔记5 Lab3实验报告
来源:互联网 发布:指纹校准软件 编辑:程序博客网 时间:2024/05/19 22:50
Lab3 实验报告
Exercise 1
- Modify mem_init() in kern/pmap.c to allocate and map the envs array.
// mem_int()// 第一处envs = (struct Env *) boot_alloc(NENV * sizeof(struct Env));memset(pages, 0, NENV * sizeof(struct Env));// 第二处boot_map_region(kern_pgdir, UENVS, PTSIZE, PADDR(envs), PTE_U | PTE_P);
这里仿照上一个实验可以比较轻松的写出来,注意,由于内核已用内存多出了 NENV
这一段,所以还需要修改 page_init
函数内的上限,参照 LAB 2
实验报告的代码,修改如下
// page_init()size_t right_i = PGNUM(PADDR(envs + NENV));
Exercise 2
- In the file env.c, finish coding the following functions.
// env_init()env_free_list = envs;struct Env *pre = envs;for (int i = 1; i != NENV; ++i) { pre -> env_link = envs + i; pre = envs + i;}
这里是初始化 envs
数组,然后插入到空闲链表中,由注释可知,需要确保分配 env
的时候是从 env[0]
开始分配的,也就是使用尾接法建链表,这和之前的 pages
不同
// env_setup_vm()p->pp_ref++;e->env_pgdir = page2kva(p);memcpy(e->env_pgdir, kern_pgdir, PGSIZE);
初始化用户地址空间中内核部分的虚拟内存并设置一级页表,可以将之前的 kern_pgdir
复制过去
// region_alloc()uintptr_t low = ROUNDDOWN((uintptr_t) va, PGSIZE);uintptr_t high = ROUNDUP((uintptr_t) va + len, PGSIZE);if (high > UTOP) { panic("allocation fails");}while (low < high) { struct PageInfo *pp = page_alloc(ALLOC_ZERO); if (pp == NULL) { panic("allocation falis"); } pp->pp_ref++; int r = page_insert(e->env_pgdir, pp, (void *) low, PTE_P | PTE_U | PTE_W); if (r != 0) { panic("region_alloc: %e", r); } low += PGSIZE;}
为 env
分配物理内存,接着映射到虚拟内存,注意需要对齐以及考虑边界情况
// load_icode()// 第一处struct Elf *elf = (struct Elf *) binary;struct Proghdr *ph = (struct Proghdr *) (binary + elf->e_phoff);struct Proghdr *eph = ph + elf->e_phnum;lcr3(PADDR(e->env_pgdir));while (ph < eph) { if (ph->p_type == ELF_PROG_LOAD) { region_alloc(e, (void *) ph->p_va, ph->p_memsz); memcpy((void *) ph->p_va, binary + ph->p_offset, ph->p_filesz); } ph++;}lcr3(PADDR(kern_pgdir));e->env_tf.tf_eip = elf->e_entry;// 第二处region_alloc(e, (void *) (USTACKTOP - PGSIZE), PGSIZE);
把 elf
二进制文件读入用户地址空间中,仿照 bootloader
中 main.c
的做法,需要了解 elf
文件的结构,这个之前学习过,需要注意的是,为了读入用户地址空间,需要切换到用户的页表,还有记得设置 eip
指向入口处
// env_create()struct Env *e;int r = env_alloc(&e, 0);if (r < 0) { panic("env_alloc: %e", r);}load_icode(e, binary);e->env_type = type;
利用之前的函数创建 env
,对于 parent_id
由于分配页面的时候,把页面都清 0
了,所以这里不显式设置也行
// env_run()if (curenv && curenv->env_status == ENV_RUNNING) { curenv->env_status = ENV_RUNNABLE;}curenv = e;curenv->env_status = ENV_RUNNING;lcr3(PADDR(curenv->env_pgdir));env_pop_tf(&(curenv->env_tf));
运行 env
,需要设置状态并切换页表,这里起到关键作用的函数是 env_pop_tf
,通过查看代码可以知道,它将 trapframe
里保存的信息 pop
到了对应的寄存器上,注意在 load_icode
函数的最后一句把 trapframe
中的 eip
设置到了二进制文件的入口,所以在执行完 env_pop_tf
后,下一条指令的地址将会是二进制文件的入口,因此达到了切换的目的
Exercise 3
- Read Chapter 9, Exceptions and Interrupts in the 80386 Programmer’s Manual.
这个需要读一读,不然后面可能会遇到一些困难,不过我也没认真读完,主要是英文看起来好累……过程中到网上查一些相关的中文资料,收获还是挺大的
Exercise 4
- Edit trapentry.S and trap.c and implement the features described above.
// trapentry.S// 第一处TRAPHANDLER_NOEC(divide_error, T_DIVIDE)TRAPHANDLER_NOEC(debug_exception, T_DEBUG)TRAPHANDLER_NOEC(non_maskable_interrupt, T_NMI)TRAPHANDLER_NOEC(break_point, T_BRKPT) // 注意这个地方千万不能用 breakpoint 作为函数名TRAPHANDLER_NOEC(overflow, T_OFLOW)TRAPHANDLER_NOEC(bounds_check, T_BOUND)TRAPHANDLER_NOEC(illegal_opcode, T_ILLOP)TRAPHANDLER_NOEC(device_not_available, T_DEVICE)TRAPHANDLER(double_fault, T_DBLFLT)TRAPHANDLER(invalid_task_switch_segment, T_TSS)TRAPHANDLER(segment_not_present, T_SEGNP)TRAPHANDLER(stack_fault, T_STACK)TRAPHANDLER(general_protection_fault, T_GPFLT)TRAPHANDLER(page_fault, T_PGFLT)TRAPHANDLER_NOEC(floating_point_error, T_FPERR)TRAPHANDLER(alignment_check, T_ALIGN)TRAPHANDLER_NOEC(machine_check, T_MCHK)TRAPHANDLER_NOEC(SIMD_floating_point_exception, T_SIMDERR)
使用上面给的宏定义来设置处理 trap
的函数,这里的函数名可以自己取,但是要注意, T_BRKPT
的函数名不能是 breakpoint
,因为 inc/x86.h
中含有同名函数,系统在调用时会调用 inc/x86.h
里的那个函数,关于是否需要 errorcode
可以查看 LEC 8
的 handouts
,里面含有相关信息
// trapentry.S// 第二处_alltraps: pushl %ds pushl %es pushal movl $GD_KD, %eax movw %ax, %ds movw %ax, %es pushl %esp call trap popal popl %es popl %ds addl $8, %esp iret
根据之前所说,在引发异常时 CPU
会把 SS
寄存器到 EIP
寄存器压入栈中,如果需要 error code
的话也会压入,而在上面宏定义的函数中,trapno
也被压入了,所以这里只需要 push
余下的寄存器,注意需要根据 trapframe
的结构倒序压入, pushal
指令会按顺序将 eax
到 edi
压入栈中,call
之后的指令是当 call trap
失败时可以还原相关寄存器
// trap.c// trap_init()extern void divide_error();extern void debug_exception();extern void non_maskable_interrupt();extern void break_point();extern void overflow();extern void bounds_check();extern void illegal_opcode();extern void device_not_available();extern void double_fault();extern void invalid_task_switch_segment();extern void segment_not_present();extern void stack_fault();extern void general_protection_fault();extern void page_fault();extern void floating_point_error();extern void alignment_check();extern void machine_check();extern void SIMD_floating_point_exception();SETGATE(idt[T_DIVIDE], 0, GD_KT, divide_error, 0);SETGATE(idt[T_DEBUG], 0, GD_KT, debug_exception, 0);SETGATE(idt[T_NMI], 0, GD_KT, non_maskable_interrupt, 0);SETGATE(idt[T_BRKPT], 0, GD_KT, break_point, 3);SETGATE(idt[T_OFLOW], 0, GD_KT, overflow, 0);SETGATE(idt[T_BOUND], 0, GD_KT, bounds_check, 0);SETGATE(idt[T_ILLOP], 0, GD_KT, illegal_opcode, 0);SETGATE(idt[T_DEVICE], 0, GD_KT, device_not_available, 0);SETGATE(idt[T_DBLFLT], 0, GD_KT, double_fault, 0);SETGATE(idt[T_TSS], 0, GD_KT, invalid_task_switch_segment, 0);SETGATE(idt[T_SEGNP], 0, GD_KT, segment_not_present, 0);SETGATE(idt[T_STACK], 0, GD_KT, stack_fault, 0);SETGATE(idt[T_GPFLT], 0, GD_KT, general_protection_fault, 0);SETGATE(idt[T_PGFLT], 0, GD_KT, page_fault, 0);SETGATE(idt[T_FPERR], 0, GD_KT, floating_point_error, 0);SETGATE(idt[T_ALIGN], 0, GD_KT, alignment_check, 0);SETGATE(idt[T_MCHK], 0, GD_KT, machine_check, 0);SETGATE(idt[T_SIMDERR], 0, GD_KT, SIMD_floating_point_exception, 0);
设置 IDT
,需要先声明函数,需要注意,由于 break_point
普通用户也可以使用,所以 DPL = 3
, SETGATE
的定义在 inc/mmu.h
之中
Questions
- What is the purpose of having an individual handler function for each exception/interrupt? (i.e., if all exceptions/interrupts were delivered to the same handler, what feature that exists in the current implementation could not be provided?)
不同异常或中断的处理方式与结果不相同,例如是否可以恢复或从哪里恢复,条件也不一定相同,例如对权限等级与 errorcode
等参数的要求不同,所以需要拥有不同的处理函数
- Did you have to do anything to make the user/softint program behave correctly? The grade script expects it to produce a general protection fault (trap 13), but softint’s code says int $14. Why should this produce interrupt vector 13? What happens if the kernel actually allows softint’s int $14 instruction to invoke the kernel’s page fault handler (which is interrupt vector 14)?
由于 trap 14
在 IDT
内描述符的 DPL = 0
,而此时 CPL = 3
即权限不足,所以执行这条指令会引发 trap 13
Exercise 5
- Modify trap_dispatch() to dispatch page fault exceptions to page_fault_handler().
// trap.c// trap_init()switch (tf->tf_trapno) { case T_PGFLT: page_fault_handler(tf); break; default: print_trapframe(tf); if (tf->tf_cs == GD_KT) panic("unhandled trap in kernel"); else { env_destroy(curenv); return; }}
根据 trapno
判断异常的类型,然后分配给相应函数
Exercise 6
- Modify trap_dispatch() to make breakpoint exceptions invoke the kernel monitor.
// trap_init()switch (tf->tf_trapno) { case T_PGFLT: page_fault_handler(tf); break; case T_BRKPT: monitor(tf); break; default: print_trapframe(tf); if (tf->tf_cs == GD_KT) panic("unhandled trap in kernel"); else { env_destroy(curenv); return; }}
简单加上相应分支即可
Questions
- The break point test case will either generate a break point exception or a general protection fault depending on how you initialized the break point entry in the IDT (i.e., your call to SETGATE from trap_init). Why? How do you need to set it up in order to get the breakpoint exception to work as specified above and what incorrect setup would cause it to trigger a general protection fault?
这个和上一个问题类似,如果设置 break point
的 DPL = 0
则会引发权限错误,由于这里设置的 DPL = 3
,所以会引发断点
- What do you think is the point of these mechanisms, particularly in light of what the user/softint test program does?
这个机制有效地防止了一些程序恶意任意调用指令,引发一些危险的错误,所以我认为这个粒度的权限机制时十分必要的
Exercise 7
- Add a handler in the kernel for interrupt vector T_SYSCALL.
// trap.c// trap_dispatch()struct PushRegs regs = tf->tf_regs;switch (tf->tf_trapno) { case T_PGFLT: page_fault_handler(tf); break; case T_BRKPT: monitor(tf); break; case T_SYSCALL: tf->tf_regs.reg_eax = syscall(regs.reg_eax, regs.reg_edx, regs.reg_ecx, regs.reg_ebx, regs.reg_edi, regs.reg_esi); break; default: print_trapframe(tf); if (tf->tf_cs == GD_KT) panic("unhandled trap in kernel"); else { env_destroy(curenv); return; }}
还是和之前一样,分配相关的异常处理函数,这里需要将结果保存到 eax
寄存器中,记得在 IDT
中增加相应表项
// trapentry.STRAPHANDLER_NOEC(system_call, T_SYSCALL)// trap.c// trap_init()extern void system_call();SETGATE(idt[T_SYSCALL], 0, GD_KT, system_call, 3);
这里需要设置 syscall
的 DPL = 3
,接下来是 syscall.c
的部分
// syscall.c// syscall()switch (syscallno) { case SYS_cputs: sys_cputs((const char *) a1, a2); return 0; case SYS_cgetc: return sys_cgetc(); case SYS_getenvid: return sys_getenvid(); case SYS_env_destroy: return sys_env_destroy(a1); default: return -E_NO_SYS;}
只需简单地根据 syscallno
调用不同的函数即可
Exercise 8
- Add the required code to the user library, then boot your kernel.
// libmain.c// libmain()thisenv = &envs[ENVX(sys_getenvid())];
由于之前没有设置 thisenv
的值,所以运行到 hello
的第二句时会出现错误,这里根据 id
取出索引,然后找到相应 env
Exercise 9
- Change kern/trap.c to panic if a page fault happens in kernel mode. Implement user_mem_check in that same file.Change kern/syscall.c to sanity check arguments to system calls.
// trap.c// page_fault_handler()if ((tf->tf_cs & 3) == 0) { panic("page fault in kern");}
由于 cs
寄存器的低 2
位的值与 CPL
相等,所以可以根据 cs
寄存器判断是否在内核态
// pmap.c// user_mem_check()uintptr_t high = ROUNDUP((uintptr_t) va + len, PGSIZE);for (uintptr_t low = (uintptr_t) va; low < high; low = ROUNDUP(low + 1, PGSIZE)) { pte_t *pte = pgdir_walk(env->env_pgdir, (void *) low, false); if (!pte || (~(*pte) & perm) || low >= ULIM) { user_mem_check_addr = low; return -E_FAULT; }}return 0;
通过页表找到相应的 pte
,然后判断是否具有权限,这里需要记录第一个出错的虚拟地址,所以一开始不能将 va
对齐,还有这种写法的 high
不能向下对齐,因为结果可能会比 low
还小
// syscall.c// sys_cputs()user_mem_assert(curenv, s, len, 0);
利用刚才的函数检查这一段地址
// kdebug.c// debuginfo_eip()// 第一处if (user_mem_check(curenv, (void *) USTABDATA, sizeof(struct UserStabData), 0) < 0) { return -1;}// 第二处if (user_mem_check(curenv, (void *) stabs, stab_end - stabs, 0) < 0 || user_mem_check(curenv, (void *) stabstr, stabstr_end - stabstr, 0) < 0) { return -1;}
同理,检查相应的地址
关于最后一个问题,在调用 backtrace
后显示如下
K> backtraceStack backtrace: ebp efffff20 eip f0100c79 args 00000001 efffff38 f0198000 00000000 f0175840 kern/monitor.c:217: monitor+276 ebp efffff90 eip f01039a0 args f0198000 efffffbc 00000000 00000082 00000000 kern/trap.c:192: trap+199 ebp efffffb0 eip f0103aa8 args efffffbc 00000000 00000000 eebfdfd0 efffffdc kern/trapentry.S:80: <unknown>+0 ebp eebfdfd0 eip 00800073 args 00000000 00000000 eebfdff0 00800049 00000000 lib/libmain.c:26: libmain+58 ebp eebfdff0 eip 00800031 args 00000000 00000000Incoming TRAP frame at 0xeffffea4kernel panic at kern/trap.c:261: page fault in kern
这里引发页错误的原因是访问到了用户栈顶以上,可以看到, libmain
的两个参数都是 0
,回想一下,在 lib/entry.S
中,系统在 USTACKTOP
执行了两次 pushl $0
,所以当往上找第三个参数时就到达了上面的空内存,所以引发了页错误
Exercise 10
- Boot your kernel, running user/evilhello. The environment should be destroyed, and the kernel should not panic.
这里只要完成了 Exercise 9
就可以通过这题,即 make grade
全部通过
- MIT 6.828 学习笔记5 Lab3实验报告
- MIT 6.828 学习笔记3 Lab1实验报告
- MIT 6.828 学习笔记4 Lab2实验报告
- MIT 6.828 学习笔记6 Lab4实验报告
- ucore-lab3 实验报告
- ucore lab3实验报告
- 操作系统实验报告 lab3
- MIT 操作系统实验 MIT JOS lab3
- 操作系统ucore lab3实验报告
- 操作系统ucore lab3实验报告
- 同步机制实验报告_Nachos Lab3
- OOAD Lab3实验文档
- 操作系统 lab3 笔记
- CSAPP 六个重要实验 lab3
- CSAPP:Lab3 bufbomb实验记录
- Torque2D MIT 学习笔记(27)
- MIT 6.828 学习笔记1 阅读boot.S
- MIT 6.828 学习笔记2 阅读main.c
- 走进增长黑客
- Windows 10 操作系统Administrator管理员账户开启方法
- hdoj 3037 lucas定理+逆元
- leetcode_c++:Maximum Subarray(053)
- Python学习 (四 函数式编程)
- MIT 6.828 学习笔记5 Lab3实验报告
- 学习kali 关于安装以及其他
- Spark学习16之Spark 2.0.0-preview编译安装
- 第一章 JAVA入门(Android结构图)
- 如何引用自定义标签库中的tld文件
- 关于浏览器和网络的20项须知-前言
- 特殊二极管的学习
- org.apache.log4j.Logger用法
- 添加任务备份Mysql数据库