LAB3:
Q1.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?)
A1.因为目前的中断机制不能给出足够的信息来在一个函数里面识别不同的中断,除非CPU可以自动保存中断向量这样可以分辨中断的数据给函数使用。
Q2.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)?
A2.int 14 Page Fault并非一个用户可以直接调用的中断,在实现的时候是给与其内核级别DPL,所以用户直接访问的时候会产生保护错误,从而转到int 13 General Protection。如果内核允许用户直接调用int 14,用户调用时不会压入错误码,会导致栈错位。
Q3.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 idt_init). Why? How did you need to set it in order to get the breakpoint exception to work as specified above?
A3.设置断点IDT的DPL为3即可让用户直接调用。
Q4.What do you think is the point of these mechanisms, particularly in light of what the user/softint test program does?
A4.我没看懂这问题问的什么意思,难道是让解释intel 中断权限机制?查手册就好了
E1.修改kern/pmap.c,分配并映射envs数组。
1、在i386_vm_init()内为envs分配空间,参考源文件内pages的分配方式,需要页对齐。
2、在page_init()内设置envs所在页状态为被占用。
3、在i386_vm_init()内映射envs到UENVS虚拟地址。
E2.在kern/env.c文件中,完成以下函数。
1、env_init():初始化env空闲链表env_free_list,并把envs数组全部元素都插入到链表,注意反向插入,使得第一次从链表取内容时返回envs[0]
2、env_setup_vm():初始化一个用户环境的虚拟内存空间。首先要要分配一页内存作为用户空间的页目录,其内容复制内核页目录,然后把VPT和UVPT映射这个页目录,注意要增加某些页的计数,使得这些页面可以正常回收。
3、segment_alloc():给用户空间分配指定大小的内存并映射到指定虚拟地址上,注意处理页对齐这样的操作,出错则panic。
4、load_icode():从指定内存中复制ELF程序到用户空间,并给用户分配栈。需要注意当前函数操作的是用户空间,所以需要使用用户的页目录,进行复制操作。不直接映射到用户空间而是复制的原因在于,这些程序处于内核空间,直接映射可能会让用户有机会操作内核其他数据,不安全。还需要注意的就是有些段的内存大小大于于ELF文件中的大小,多余的部分需要清零。
5、env_create():调用env_alloc()和load_icode()来完成新建用户环境的操作。
6、env_run():运行用户环境,如果当前用户环境和想要运行的用户环境不同,需要切换页表并设置一些其他内容再运行。
E3.阅读intel手册了解异常和中断的处理
E4.修改kern/trapentry.S以及kern/trap.c,定义中断处理函数,并且设置IDT
1、根据kern/trapentry.S和inc/trap.h中的宏来定义异常和中断,算上系统调用共19个。
2、在trapentry.S中添加_alltraps:标号的代码,这是异常和中断传递的共同部分,主要内容是压入寄存器使得栈空间和struct Trapframe相匹配,载入内核数据段,给trap()函数压入%esp作为参数,调用trap,并处理如果trap切换失败返回回来的情况(平衡栈空间)
3、修改kern/trap.c的idt_init()函数来初始化IDT数组,使用SETGATE宏处理不同的异常/中断入口生成IDT。
E5.修改kern/trap.c中trap_dispatch()函数,把页错误分配给page_fault_handler()函数处理
1、只需要根据Trapframe中的trap类型判断是否0x14(页错误),然后调用page_fault_handler即可。
2、这里我使用了一个trap_hdls数组来存储每个中断的处理函数,中断号为下标直接调用,就不用每个中断写一个case或者if那么麻烦了。
E6.修改kern/trap.c中trap_dispatch()函数,把断点分配给monitor()函数处理
E7.添加系统调用处理机制
1、这里为了跟其他中断保持一致,我把syscall()函数原型改成跟其他中断处理函数一样的类型,可以直接在trap_hdls数组中直接定位并调用。
2、理解Trapframe中不同寄存器在系统调用中的作用,有些寄存器是作为参数传递过来的,要分别对待,返回值放到tf_eax中。
3、按照以上指示把kern/syscall.c 中的所有系统调用都在syscall()中注册。
E8.修改用户库文件,使得用户的hello world程序能正常运行
1、修改lib/libmain.c文件,给env变量赋值,这里需要调用sys_getenvid()系统调用来返回一个用户环境ID,把这个ID转化成envs下标,该下表所代表的元素就是env的所指向的内容。
E9.
1、修改kern/trap.c 中页错误处理函数,在内核(tf->tf_cs==GD_KT)发生缺页中断时,painc掉。
2、在kern/pmap.c中实现user_mem_check()函数,用来检测用户对一块内存是否具有指定的权限。用户对高于ULIM的内存没有任何权限。注意页对齐的情况。
3、修改kern/syscall.c ,对于用户传入内核的指针(sys_cputs()),我们要通过user_mem_assert()函数检查用户对这块内存有没有特定权限。
4、修改kern/kdebug.c,对用户空间的数据使用user_mem_check()函数检查当前用户空间是否对其有PTE_U权限。
5、查看breakpoint程序函数调用栈出现页错误原因是:用户栈显示到了最顶端,再往上是没有映射的空间,访问这些空间会导致页错误。
E10.因为E9的第3步所描述的原因,所以这个邪恶的程序没有实现它的企图,被绳之以法。
C1.整理kern/trapentry.S和kern/trap.c中的代码,改变这种有坏代码味道(很多看起来差不多的代码,这意味着我们要重构了)的状态。
1、在trapentry.S中修改TRAPHANDLER,利用.text 和.data伪指令定义一块数据,这块数据保存了异常入口点以及它的属性。
2、在trap.c的idt_init()函数中,利用循环来读取trapentry.S中定义的数据区,这样就减少了idt_init()中的代码重覆数内容。这体现了Unix一个很重要的设计理念:“Don't Repeat Youself”,同样的代码写一遍就可以了,其他都是引用这块代码。这样可以有效防止程序不一致(有的地方修改了,有的地方还没有修改)的情况。
C2.修改kern/monitor.c,给monitor加入单步调试,反汇编等功能。
加入了以下功能:
1、反汇编,移植了bochs内置的反汇编代码,已经作成静态库放到群共享中,其中遇到最大的问题就是C/C++混合编程(用extern "C"搞定)以及标准库依赖(把这些依赖修改为在jos的库函数即可,主要是printf系列,assert断言,可变传参等)(见disassemble()和u()函数)。
2、单步,使用标志寄存器中的trap位来使得每运行一条指令发生一次int 1中断(见 s()函数)
3、继续,把trap标志位清零即可。(见c()函数)
4、单步运行N条指令,原理同单步,但是要用一个静态变量作为指令计数器,如果到了指定数目就会停下来让用户指示下一步操作(见s(),treat_trap())
5、stepover, 就是把call当成一条指令计数的单步,原理同单步,但是需要增加一个层次变量nextflag 用来标示进入了多少层call,遇到ret就会减少一个层次,当nextflag为1的时候才会修改指令计数器(见n(),tread_trap())。
6、断点,原理是用int3指令替换断点位置的代码。说起来比较简单,实际实现起来是很繁琐的(见b(),break_point(),treat_trap())。
(1)首先要判断用户对断点位置内存是否有权限,有则保存断点指令,并替换为int3;
(2)修改反汇编函数,使得在断点所在反汇编区域的时显示的是原指令,而不是int3;
(3)处理当trap位和用户设置的断点相遇时,暂停两次的情况;
(4)当运行到断点位置时要临时禁用断点,并开启trap标志位,使得下次指令执行完毕后中断;
(5)恢复上次执行指令的时候被临时禁用的中断。
在实现上述功能时使用了很多静态变量,这在会造成程序的耦合度增大,结构变得混乱。这些静态变量可以改进成用户环境变量,每个用户程序都有这些调试专用的变量,以及对应函数来操作这些变量,这样结构就变得清晰,多任务的时候也不会出错。不过现在这样还够用,等需要的时候再修改吧。
C3.使用sysenter/sysexit代替int/iret实现系统调用。
阿,这个太麻烦,而且以后的实验也用不到,偷偷懒,不做了啦~