MIT6.828_Lab 3_User Environments_Part A
Lab 3: User Environments
简介:
本实验中将实现运行受保护的用户态环境(即“进程”)所需的基本内核功能。 将增强JOS内核,以设
置数据结构来跟踪用户环境(进程),创建单个用户环境(进程),将程序映像加载到其中并开始运行。还
将使JOS内核能够处理用户环境(进程)发出的任何系统调用并处理它引起的任何其他异常。
注意:在本实验中,术语“环境”和“过程”是可互换的-两者均指允许您运行程序的抽象。 为了强调
JOS环境和UNIX进程提供不同的接口,并且不提供相同的语义,我们引入术语“环境”而不是传统的术
语“进程”。
预备工作:按课程给出的步骤切换到lab3分支,再将lab2合并
实验要求:与实验2一样,完成实验3中描述的所有常规练习以及至少一个挑战性问题。在lab目录顶层的一个名为Answers-lab3.txt的文件中,写下对实验室中提出的问题的简短答案,写下一两个段落的描述来说明是如何解决挑战性问题的。
Part A: User Environments and Exception Handling
课程中描述了一些管理用户环境(进程)的关键结构体,以及结构体中各元素的含义,值得一提的是在JOS
中,各个环境(进程)不像xv6中的进程那样具有自己的内核堆栈。一次在内核中只能有一个活动的JOS环境(进程),因此JOS只需要一个内核堆栈。
练习1:
修改kern / pmap.c中的mem_init()来分配和映射envs数组,与页面数组一样,应在合适的地方:UENVS(inc / memlayout.h)上以只读方式映射为用户,以便用户进程可以从该数组读取。
要求:运行你的代码,并保证check_kern_pgdir通过。
代码修改很简单,参考Lab2里写过的分配和映射Pages的代码就行了:
补充的代码:
测试居然报错了:
那就调试一下:
调试代码:
调试结果:
可以看到memset之后,kern_pgdir变成0了,搞不清楚,上网查波资料,最后参考了
Salvete 的博客,原来链接器提供的
end并不准确,并非指向.bss段的末尾,按文章修改链接器脚本后,总算成功输出了:
练习2:补充env.c中未完成的函数
env_init():初始化Env结构体数组,并将它们加入env_free_list链表
env_setup_vm:为用户环境(进程)分配一张页目录,并初始化用户环境的地址空间
region_alloc():为一个环境(进程)分配大小为len的物理内存,并建立映射。
load_icode():解析ELF二进制文件镜像,将内容加载到新环境的地址空间中
env_create():创建一个用户环境(进程)
env_run():让给定的用户环境(进程)上处理机运行
完成代码补充后,make qemu,系统将进入用户态并执行hello 二进制文件,由于JOS尚未设置硬件以允许
从用户空间到内核的转换,所以二进制文件hello执行到使用int 指令进行系统调用时,会看到tripple fault错误信息,后面会解决这个问题,调试一下走一遍过程,首先使用make qemu-gdb并在env_pop_tf处设置GDB断点,然后si单步调试,在iret指令之后处理器应进入用户态:
iret之前-内核态:
iret之后-用户态:
按照课程提示在hello的sys_cputs的int $30命令处打上断点,该命令是一条系统调用,用于向控制台输
出字符
int $30命令
执行完这步后就发生tripple fault,无法执行下一条指令了,这是因为处理器变为用户态后,没办法转回
内核态,因此无法执行系统调用,我们需要实现对基本的异常及系统调用的处理,才能让内核从用户模式
下夺回处理器的控制权,首先得熟悉下x86中断和异常的机制。
练习3:
阅读Chapter 9 Exceptions and Interrupts
其他参考资料:详解struct Env 与 struct Trapframe
重点:9.5 IDT Descriptors, 9.6 Interrupt Tasks and Interrupt Procedures
练习4:
编辑trapentry.S和trap.c并实现上述功能。 trapentry.S中的TRAPHANDLER和TRAPHANDLER_NOEC宏以及inc / trap.h中的T_ *定义都将提供帮助。 需要为inc / trap.h中定义的每个陷阱在trapentry.S中添加一个入口点(使用这些宏),并且必须提供TRAPHANDLER宏所引用的_alltraps。 另外还需要修改trap_init()来初始化idt,以指向trapentry.S中定义的每个入口。 SETGATE宏(mmu.h)在这里会有所帮助。
_alltraps要点:
(1)入栈以使堆栈在布局上看起来像Trapframe
(2)将GD_KD加载到%ds和%es中
(3)入栈%esp来传递一个指向trapframe的指针作为trap函数的参数
(4)调用trap(trap可以返回吗?)
(5)考虑使用Pushal指令,该指令很适合Trapframe的布局。
(6)使用用户目录中的某些测试程序测试trap handling代码。
第一步:查看trapentry.S,mmu.h,trap.h,在trap.h中先根据trapentry.S中的注释声明函数,再参考9.10 Error Code Summary根据是否存在errcode加入入口指针。
声明:
添加入口:
第二步:根据上面的_alltraps要点,参考inc/trap.h中trapframe的布局及kern/env.c中env_pop_tf的代码。
trapframe:
env_pop_tf:
可以发现入栈顺序和出栈顺序是相反的,栈顶到栈底(从低地址到高地址)应该与trapframe的布局完全一
致,入栈跟出栈反着来就行了,其余部分按上面的要点来,代码如下:
_alltraps:
第二步:补充trap.c中的trap_init函数,先看一下mmu.h中SETGATE宏函数:
函数功能:设置IDT中的中断描述符
函数参数:
gate:要设置的中断描述符项
istrap:该位为1表示该描述符为陷入门,为0表示为中断门,两者区别在于中断门会将IF位置0来阻止嵌套
中断的发生,而陷入门不会。
sel:中断/陷入处理程序的代码段选择子。
off:中断/陷入处理程序的代码段偏移量。
dpl:特权级别描述符,软件调用所需的特权等级
trap_init函数的主体部分就是调用SETGATE来初始化IDT表中各描述符,istrap参数参考9.9 Exception Summary,sel段参数是GD_KT,off为对应处理函数地址,dpl为特权级别描述符.
trap_init()代码如下:
make grade测试通过:
问题1:为每个异常/中断设置单独的处理函数的目的是什么?
答:中断,异常的类型多种多样,有些在处理完后要返回用户程序继续执行(如缺页中断),有些在处理完后直接杀死用户程序(如除0异常),如果把所有中断/异常集中到一起处理,首先函数代码量会比较大,且要对不同情况做不同处理(如中断要关IF位而陷入不用,各个中断/异常要求的DPL不同),提高了编写,调试,维护代码的难度,其次也不方便中断函数的拓展。
问题2:怎么使用户/ softint程序正常运行? 评分脚本期望它会产生一般性的保护错误(trap 13),但softint的代码是int $ 14。 为什么会产生中断向量13? 如果内核允许softint的int $ 14指令调用内核的页面错误处理程序(即中断向量14),会发生什么?
答:softint是用户程序,特权级别为3,而页面错误处理程序要求的特权级别为0,低级别程序想要调用高级别中断处理程序的结果就是产生一个general protection fault,该问题的其他几问还没解决。
本文地址:https://blog.csdn.net/userXKk/article/details/107942673