Lab3实验报告
思考题
Thinking 3.1
请结合 MOS 中的页目录自映射应用解释代码中
e->env_pgdir[PDX(UVPT)]= PADDR(e->env_pgdir) | PTE_V
的含义。
0x7fc00000-0x80000000 这 4MB 空间的起始位置(也就是第一个二级页表的基地址)对应着页目录的第一个页目录项。同时由于 1M 个页表项和 4GB 地址空间是线性映射的,不难算出 0x7fc00000 这一个地址对应的应该是第 0x7fc00000 >> 12 个页表项。
每个页表项是4B
,一共有1M
个页表项,占据4MB
的空间,正好是UVPT
到ULIM
这个User VPT
这4MB的空间。页表自映射使得页目录的某一项正好映射到这个页表。由于线性性质,那么正好就是UVPT >> 22
这个页目录中的一项就是页目录所在页表的物理地址。
后面就是页表的标志位了。
Thinking 3.2
elf_load_seg 以函数指针的形式,接受外部自定义的回调函数 map_page。请你找到与之相关的 data 这一参数在此处的来源,并思考它的作用。没有这个参数可不可以?为什么?
在map_page()
中的参数data
就是传入elf_load_seg()
中传入的参数data
,也就是在load_icode()
中传入的参数struct Env *e
,一个进程控制块。
在map_page()
中data
的作用为最后的env
return page_insert(env->env_pgdir, env->env_asid, p, va, perm);
也就是指定当前进程的页目录的内核虚拟地址,和该进程的asid
不可以去掉这个参数,这是因为每个进程有自己的tlb
,这个tlb
是由asid
确定的。
Thinking 3.3
结合 elf_load_seg 的参数和实现,考虑该函数需要处理哪些页面加载的情况。
- 当前页面不可写的情况:调整传入map_page()中断权限位
- 段头和页面不对齐的情况:注意开始的位置
- 创建页面不成功的情况:return
- 二进制文件大小小于段在内存的大小的情况:继续创建填充0的页面
Thinking 3.4
思考上面这一段话,并根据自己在 Lab2 中的理解,回答:
- 你认为这里的 env_tf.cp0_epc 存储的是物理地址还是虚拟地址?
虚拟地址。
(lab2啥理解呀)应该是说在CPU看来都是虚拟地址,MIPS是有一个MMU的作为地址变换的。
Thinking 3.5
试找出 0、 1、 2、 3 号异常处理函数的具体实现位置。 8 号异常(系统调用)涉及的 do_syscall() 函数将在 Lab4 中实现
在kern/genex.S
中。
0号异常处理handle_int
是处理时钟中断,其中主要是调用了schedule
这个函数,但是这里还特判一下$t1
是否为0这个我不太理解,理论上前面刚和STATUS_IM7
这个做完与运算应该就不会是0才对。
1号异常,存储异常会执行do_tlb_mod
,这个在kern/tlbex.c
中实现
2、3号异常,tlb
异常会执行do_tlb_refill
,也就是tlb重填,这个在kern/tlb_asm.S
中实现
Thinking 3.6
阅读 entry.S、 genex.S 和 env_asm.S 这几个文件,并尝试说出时钟中断在哪些时候开启,在哪些时候关闭。
在entry.S
中的handle_int
里面开启,时钟中断过程进入schedule
函数,然后其中调用env_run
,再调用env_pop_tf
,,env_pop_tf
的具体实现在env_asm.S
中,其中调用RESET_KCLOCK
,也就是在include/kclock.h
中的实现。
Thinking 3.7
阅读相关代码,思考操作系统是怎么根据时钟中断切换进程的。
根据3.6的分析,时钟中断后会调用schedule
在这里面切换进程,具体而言包括保存上下文和进入新的进程的上下文环境,同时时钟初始化。
难点分析
本次lab其实主要包括两个部分,一个是进程方面,包括进程的创建和调度;一个是时钟中断方面。
实际上进程的创建和调度应该分开说,但是我认为这两个其实都是在软件层面上的实现(似乎这么说也不准确),反正就这么说了罢。
本次lab的难点我认为主要在于对这几个的流程的理解。
进程创建与调度
进程的创建是在内核态中执行的。
创建进程包括
- 初始化Env 这个进程控制块,包括其中的一些变量的取值
- 在
struct Env
中还有一个关键的env_pgdir
,这个就是确定虚拟地址 - 进程是运行中的程序,那这个程序从哪里来呢?这个在
load_icode
中加以实现,由于一个程序可以有多个进程,所以肯定不能用程序的那块内存空间直接操作,同时还有一些动态的空间需要进程创建,那么这块地址空间在哪呢,对于进程来说,他看到的都是自己的虚拟地址,就是这个了~
进程调度实际上就是去掉这个进程然后把那个进程拉过来
- 去掉这个进程:这一步需要保存CPU上下文信息,同时需要在两个队列中进行处理,包括空闲env区,和调度队列区
- 把新的进程拉过来:从调度队列拿一个过来,然后恢复他的上下文信息
时钟中断
我觉得这里主要是函数之间的调用挺复杂的,其实工作流程还是很好理解的
- handle_int
- schedule
- env_run
- env_pop_tf
害,真是中中又断断啊~
其中对于kern/genex.S
中的timer_irq
不甚了解,但是当我看到guide_book
中的描述时(顺便吐槽一下,咋这还不相同的),我好像懂了,或许是更加不同情况会进入不同的异常处理,只需要将bnez
稍微调整为beq
为某个特定的数就可以了,由于目前只实现了是时钟中断,因此一律调用这个就好啦~
实验体会
在Lab2的时候说这个lab好难啊,在Lab3的时候也还是这么说。Lab2对于List的使用确实是我遇到的很大的一个障碍,这次使用起来更加得手了。
同时自己实现了一次进程后,对于比如“每个结构都有一个数据结构”、“linux一切皆文件”,这些有了更深的认识。
当需要实现某个具有特定功能的结构时,首要就是分析其数据结构,课程组似乎没有特别强调env
的数据结构,其实处处都是优雅。基于上面这一点,进程其实就是一段地址空间,与“一切皆文件”的观点不谋而合。当然需要清楚到底是哪一段内存,是什么样子的内存。
这次还用到了更多debug
的功能,下次可以多多研究objdump
。