这篇是我自己探索实现 MIT 6.828 lab3A 的笔记记录,会包含一部分代码注释和要求的翻译记录,以及踩过的坑/个人的解决方案
这里是我实现的完整代码仓库,也包含其他笔记等等:https://github.com/yunwei37/6.828-2018-labs
目录
在本实验中,您将实现运行受保护的用户模式环境(即“进程”)所需的基本内核功能。您将增强JOS内核,以设置数据结构来跟踪用户环境,创建单个用户环境,将程序映像加载到其中并开始运行。您还将使JOS内核能够处理用户环境发出的任何系统调用并处理它引起的任何其他异常。
实验3包含新的源文件,可以先浏览一下:
inc/ env.h
用户态环境的公共定义trap.h
陷阱处理的公共定义syscall.h
从用户态到内核的系统调用的公共定义lib.h
用户态支持库的公共定义
kern/ env.h
用户态环境的内核专用定义env.c
实现用户态环境的内核代码trap.h
内核专用陷阱处理定义trap.c
陷阱处理代码trapentry.S
汇编语言陷阱处理程序入口点syscall.h
系统调用处理的内核专用定义syscall.c
系统调用实现代码
lib/ Makefrag
Makefile片段,用于构建用户态库entry.S
用户环境的汇编语言入口点libmain.c
从entry.S调用的用户态库设置代码syscall.c
用户态系统调用存根函数console.c
putchar和getchar的用户模式实现 ,提供控制台I/Oexit.c
exit的用户模式实现panic.c
用户模式panic的实现
user/ *
各种测试程序来检查内核 lab3 代码
课程主页也提到了内联汇编。
记录一个奇怪的问题
在开始阶段我把代码 merge 到 lab3 分支中,开始运行的时候,发现会出现:
kernel panic at kern/pmap.c:154: PADDR called with invalid kva 00000000
经过打 log,发现在 memset 之后,会把 kern_pgdir 的值覆盖掉;
kern_pgdir = (pde_t *) boot_alloc(PGSIZE);
memset(kern_pgdir, 0, PGSIZE);
继续打log:cprintf("%x\n",&kern_pgdir);
发现 kern_pgdir 这个变量的地址是在 f018f00c…
然而 extern char end[];
的值是 f018f000…所以就会出现类似的问题,就是 memset
把 kern_pgdir
全局变量的值覆盖掉了;继续深入探讨,发现在 pmap.c 中如果是有 static 修饰的变量值是低于 end 的,如果没有修饰的话值是高于 end 的…在其他文件里面测试类似的变量也能得出类似的结果
但是从Git记录也可以看出,我们并没有修改过 kern/kernel.ld ,前面几个lab的实现也是正常的;
所以为什么会是这样呢(我也不懂)
不过对于 kern/kernel.ld
来说,我们可以看到文件记录中上一个版本有根据新版 GCC 做一些适配,因此修改了 kernel.ld,这里尝试回退一下:
.bss : {
PROVIDE(edata = .);
*(.bss)
BYTE(0)
}
PROVIDE(end = .);
看起来目前是可以用了。
A部分:用户环境和异常处理
新的包含文件 inc/env.h 包含JOS中用户环境的基本定义,内核使用Env数据结构来跟踪每个用户环境。在本实验中,您最初将仅创建一个环境,但是您将需要设计JOS内核以支持多个环境;实验4将允许用户环境进入fork其他环境,从而利用此功能。
内核维护了与环境有关的三个主要全局变量:
- struct Env *envs = NULL; // 所有环境
- struct Env *curenv = NULL; // 当前环境
- static struct Env *env_free_list; // 可用环境链表
下面是一些细节上的描述:
- 一旦JOS启动并运行,envs指针将指向Env代表系统中所有环境的结构数组。JOS内核将最多支持 NENV 个活动环境。
- JOS内核将所有非活动Env结构保留在env_free_list上。这种设计可以轻松分配和释放环境,因为只需将它们添加到空闲列表中或从空闲列表中删除.
- 内核使用该curenv符号在任何给定时间跟踪当前正在执行的环境。在启动期间,在运行第一个环境之前, curenv初始设置为NULL。
环境的状态
该 Env 结构在 inc/env.h
中定义如下:
struct Env {
struct Trapframe env_tf; // Saved registers
struct Env *env_link; // Next free Env
envid_t env_id; // Unique environment identifier
envid_t env_parent_id; // env_id of this env's parent
enum EnvType env_type; // Indicates special system environments
unsigned env_status; // Status of the environment
uint32_t env_runs; // Number of times environment has run
// Address space
pde_t *env_pgdir; // Kernel virtual address of page dir
};
以下是这些 Env 字段的用途:
-
env_tf:
在
inc/trap.h
中定义的该结构在该环境不运行时(即,在内核或其他环境正在运行时)保存该环境的已保存寄存器值。从用户模式切换到内核模式时,内核会保存这些设置,以便以后可以从中断的位置恢复环境。 -
env_link:
这是下一个链接Env上 env_free_list。 env_free_list指向列表中的第一个可用环境。
-
env_id:
内核在此处存储一个值,该值唯一地标识当前正在使用此Env结构的环境。
-
env_parent_id:
内核在此处存储env_id 创建该环境的环境的。这样,环境可以形成“家谱”,这对于制定允许哪些环境对谁做事的安全性决策很有用。
-
env_type:
这用于区分特殊环境。对于大多数环境,它将为ENV_TYPE_USER。在以后的实验中,我们将为特殊的系统服务环境引入更多类型。
-
env_status:
此变量保存以下值之一:
- ENV_FREE:表示该Env结构处于非活动状态,因此位于 env_free_list 上。
- ENV_RUNNABLE:指示该Env结构表示正在等待在处理器上运行的环境。
- ENV_RUNNING:指示该Env结构代表当前正在运行的环境。
- ENV_NOT_RUNNABLE:指示该Env结构表示当前处于活动状态的环境,但是当前尚未准备好运行:例如,它正在等待来自另一个环境的进程间通信(IPC)。
- ENV_DYING:指示该Env结构表示僵尸环境。僵尸环境在下一次捕获到内核时将被释放。
-
env_pgdir:
此变量保存 此环境的页目录的内核虚拟地址。
像Unix进程一样,JOS环境将 “线程” 和 “地址空间” 的概念结合在一起。线程主要由保存的寄存器(env_tf字段)定义,地址空间由 env_pgdir 指向的页目录和页表定义。要运行环境,内核必须设置CPU、保存的寄存器和相应的地址空间。
我们 struct Env 类似于 xv6 的 struct proc。这两个结构都在一个结构中保留环境(即进程)的用户模式寄存器状态Trapframe 。在JOS中,各个环境不像xv6中的进程那样具有自己的内核堆栈。只能有一个JOS环境在内核中被激活,所以 JOS 只需要有一个 单一的内核堆栈。
分配环境数组
现在,您将需要进一步修改 mem_init() 以分配一个相似结构数组envs。这也是练习1的内容。
开始写代码:这部分也是和 lab2 类似,根据提示即可:
...
envs = (struct Env*)boot_alloc(NENV * sizeof(struct Env));
...
boot_map_region(kern_pgdir