Lab03
思考题
thinking 3.1
MOS中目前使用的envid
是由三部分构成:
- 第0-9位:当前进程在
envs
数组中偏移量,因为共有1024个env
,故这部分占据10位 - 第10位:1
- 第11-16位:6位的
ASID
低10位是不会出错的,这一步需要判断高7位是否符合envid
的规则,以及ASID
是否和低10位偏移量对应。
thinking 3.2
-
UTOP以下的区域是可读写区域,UTOP-ULIM间的区域是只读区域
-
自映射:
UVPT
以上到ULIM
之间,是一个4MB的空间,正好满足对整个4GB进程空间的自映射。- 在根据页目录,以这4MB空间内的虚拟地址访问页表时,得到的
env_cr3
正是
-
物理地址和虚拟地址:
- 在MOS中,物理内存为64MB,这是所有进程共享的
- 对于
kuseg, kseg0
部分虚拟地址的访问,最终都是落到了访问对应的物理地址。物理地址是真正存储数据的地方 - 32位操作系统,虚拟内存共4GB,分为用户空间和内核空间。其中
kuseg
是用户空间,共2GB;kseg0
是内核空间的一部分,也是内核空间中我们目前接触最多的部分,其大小为512MB - 在
kuseg
空间中,通过二级页表机制将虚拟地址映射到不同的物理页;在kseg0
空间中,通过将最高位抹去,直接映射到固定的物理页 - 所有进程共享内核所在的2GB空间
thinking 3.3
user_data
为调用load_icode
函数时传入的进程参数e
thinking 3.4
根据va
和va + bin_size
是否页对齐,可以分为四种情况:
-
va
为页对齐,这种情况较为简单 -
va
不是页对齐,其中offset = va - ROUNDDOWN(va, BY2PG)
,则从va--ROUND(va, BY2PG)
这段区间需要单独加载,首先需要考虑va
是否和物理页面有映射关系,如果有,则需要找到对应的物理页面;否则需要分配一个新的物理页
-
va + bin_size
为页对齐 -
va + bin_size
非页对齐,此时offset = va + i - ROUNDDOWN(va + i, BY2PG)
,则从ROUNDDOWN(va + i, BY2PG)--va + i
这段区间需要单独加载,即用offset代替BY2PG作为加载的size
thinking 3.5
-
虚拟地址。
-
每个进程不一样。
-
e_entry
:此字段指明程序入口的虚拟地址。即当文件被加载到进程空间里后,入口程序在进程地址空间里的地址。对于可执行程序文件来说,当 ELF 文件完成加载之 后,程序将从这里开始运行;而对于其它文件来说,这个值应该是 0。
thinking 3.6
epc
是CP0
寄存器组中记录异常发生地址的寄存器。在发生中断时,CPU
会将发生中断的地址存入该寄存器。
在env_pop_tf()
函数中,最后会跳转到进程的pc
寄存器,但是在进程切换中,实际跳转的地方应该是epc
寄存器,故要将pc
寄存器中的值设置为epc
。
thinking 3.7
- 在宏
SAVE_ALL
中,将当前进程的上下文存到TIMESTACK
- 是内核区域中不同的栈区域,当出现时钟中断时,存储的区域为
TIMESTACK
,对于其它异常,存储区域为KERNEL_SP
thinking 3.8
handle_int: lib/genex.S
handle_mod, handle_tlb: lib/genex.S
,抽象成了函数handle_\exception
handle_sys: lib/syscall.S
thinking 3.9
1 |
|
1 |
|
thinking 3.10
当发生时钟中断,先通过except_vec3
异常分发进入对应处理异常函数,在本次实验中为中断处理函数handle_int
。然后通过一些处理,比如保存现场等,之后通过判断中断码进入相应的中断服务函数,本次实验中为timer_irq
。在该函数中主要工作为调用sched_yield
函数调度进程,在该函数中会通过进程的优先级切换进程并运行,这便是根据时钟周期切换进程。
实验难点
进程创建流程及函数调用图
进程创建相关函数
env_create
位置:lib/env.c
说明:创建进程
env_create_priority
位置:lib/env.c
说明:创建进程
env_alloc
位置:lib/env.c
说明:
- 调用
env_setup_vm
函数,为进程分配页目录并完成部分映射 - 初始化对应PCB,主要包括
env_id, env_parent_id, env_status, env_runs, env_tf.cp0_status, env_tf.regs[29]
- 将进程从空闲链表中移出,加入调度队列(这和物理内存管理的页链表管理体系很类似)
env_setup_vm
位置:lib/env.c
说明:
-
为进程的页目录分配物理页
-
以
UTOP
为界,将用户空间分别映射,映射的模板是之前虚拟内存初始化时得到的boot_pgdir
-
0--UTOP
间的用户空间是可读写的,且各个进程在这部分的内容是各不一样的,因此在该函数中我们将其全部清零 -
UTOP--UTPV
间的用户空间是存储pages, envs
结构体数组的,这也是在之前虚拟内存初始化时完成映射的,这样在用户空间就可以访问到这两个数组。这部分对于所有进程都是相同的,因此我们以boot_pgdir
为模板完成映射 -
UVPT--UTOP
间的用户空间是用来自映射的,这部分4MB空间正好可以映射整个4GB虚拟内存空间,我们用以下代码来初始化这块空间:1
2
3e->env_pgdir = pgdir;
e->env_cr3 = PADDR(e->env_pgdir);
e->env_pgdir[PDX(UVPT)] = e->env_cr3 | PTE_V; -
UTOP
以上的空间不属于用户空间,也是用户态无法访问的内存空间
-
进程加载相关函数
load_icode
位置:lib/env.c
说明:
- 分配物理页,并完成从用户栈空间到该物理页的映射
- 通过
load_elf
函数完成文件二进制数据到用户空间的映射 - 设置进程的入口,该入口地址通过上面的
load_elf
函数获得
load_elf
位置:lib/kernel_elfloader.c
说明:分段调用load_icode_mapper
函数完成映射
is_elf_format
位置:lib/kernel_elfloader.c
说明:判断文件是否符合ELF
文件格式
load_icode_mapper
位置:lib/env.c
说明:将文件二进制数据映射到用户空间
时钟中断处理图
异常处理相关函数
except_vec3
位置:boot/start.S
说明:负责异常分发,根据状态寄存器的ExCode
跳转到对应的异常处理函数
handle_\exception
位置:lib/genex.S
说明:异常处理函数
timer_irq
位置:lib/genex.S
说明:时钟中断的处理函数
sched_yield
位置:lib/sched.c
说明:调度函数
env_run
位置:lib/env.c
说明:运行进程
ret_from_exception
位置:lib/genex.S
说明:恢复现场
异常处理相关宏定义
SAVE_ALL
位置:include/stackframe.h
位置:boot/start.S
说明:保存现场
RESTORE_SOME/ALL
位置:include/stackframe.h
说明:恢复现场
get_sp
位置:include/stackframe.h
说明:获取保存现场的栈指针
STI
位置:include/stackframe.h
说明:开启全局中断使能
CLI
位置:include/stackframe.h
说明:禁用全局中断
总结与反思
本次实验重点就是进程的创建和调度,以及简单异常的处理。
相关函数相比之前增加了很多,导致理清函数的调用关系成为了一个重点和前提,否则难以理清逻辑,更别谈去完成所有的函数了。
因此在反复学习指导书和阅读代码之后,在自己理解的基础上画了两个思维导图,以理清逻辑关系。