最近在做 NJU 的 ICS PA。前面都挺好,到了 PA 4 加入虚拟内存后有一个小坑,出现在 4.3 “超越容量的界限”中。记录下来或许可以帮助一些人。

责任全在 cte.c 里的 __am_switch 函数。这个函数切换 cr3 后会导致当前进程的栈失效。而这个函数本身就有调用栈,所以 leave 或者 ret 必定导致非法访存。其实这一点作者在思考题里涉及到了,但是没有点出,也没有警告这一个坑可能会导致读者在这一节直接失败。

并发执行多个用户进程

让Nanos-lite加载仙剑奇侠传和hello这两个用户进程; 或者是加载NTerm和hello内核线程, 然后从NTerm启动仙剑奇侠传, 你应该会在运行的时候观察到错误. 尝试分析这一错误的原因, 并总结为了支持这一功能, 我们需要满足什么样的条件.

这可以说是一周目最难的一道思考题了, 虽然我们会在PA4的最后给出分析, 喜欢挑战的同学仍然可以在这里尝试独立思考: 如果你能独立解决这个问题, 说明你对细节的理解可以说是相当了得了.

Hint: 程序是个状态机.

或者说,4.3 节本身是不完整的。读者在这一节实现出现问题,必须阅读下一节才能解决。

比如”支持虚存管理的多道程序”中,作者想让读者的内核线程使用用户进程的地址空间来隐藏这一问题,但是对于不同的实现,作者的这一意图未必能实现。

比如作者说的

kcontext()中将上下文的地址空间描述符指针设置为NULL, 来进行特殊的标记, 等到将来在__am_irq_handle()中调用__am_switch()时, 如果发现地址空间描述符指针为NULL, 就不进行虚拟地址空间的切换.

实际上是让内核线程使用上一次被调度的 PCB 的地址空间。

而 nanos-lite 第一个调度的 PCB 一定是 boot_pcb。他的地址空间是vme_init中建立的恒等内核地址空间。接下来调度用户进程还是 hello_fun 取决于读者的调度器实现。

如果读者调度的顺序是 boot_pcb -> 用户进程 -> hello_fun。那么一切顺利。

但如果顺序是 boot_pcb -> hello_fun -> 用户进程 -> hello_fun。那么在最后一次用户进程->hello_fun 调度时,__am_switch() 中会发生非法访存。