chapter4练习

  • 本节难度: 看懂代码就和lab1一样

编程作业

申请内存

你有没有想过,当你在 C 语言中写下的 new int[100]; 执行时可能会发生哪些事情?你可能已经发现,目前我们给用户程序的内存都是固定的并没有增长的能力,这些程序是不能执行 new 这类导致内存使用增加的操作。libc 中通过 sbrk 系统调用增加进程可使用的堆空间,这也是本来的题目设计,但是一位热心的往年助教J学长表示:这一点也不酷!他推荐了另一个申请内存的系统调用。

mmap 本身主要使用来在内存中映射文件的,这里我们简化它的功能,仅仅用来提供申请内存的功能。

mmap 系统调用新定义:

  • syscall ID:222

  • C接口: int mmap(void* start, unsigned long long len, int port)

  • Rust接口: fn mmap(start: usize, len: usize, port: usize) -> i32

  • 功能:申请长度为 len 字节的物理内存(不要求实际物理内存位置,可以随便找一块),并映射到 addr 开始的虚存,内存页属性为 port。

  • 参数:

    • start:需要映射的虚存起始地址。

    • len:映射字节长度,可以为 0 (如果是则直接返回),不可过大(上限 1GiB )。

    • port:第 0 位表示是否可读,第 1 位表示是否可写,第 2 位表示是否可执行。其他位无效(必须为 0 )。

  • 说明:

    • 正确时返回实际 map size(为 4096 的倍数),错误返回 -1 。

    • 为了简单,addr 要求按页对齐(否则报错),len 可直接按页上取整。

    • 为了简单,不考虑分配失败时的页回收(也就是内存泄漏)。

  • 错误:

    • [addr, addr + len) 存在已经被映射的页。

    • 物理内存不足。

    • port & !0x7 != 0 (port 其余位必须为0)。

    • port & 0x7 = 0 (这样的内存无意义)。

munmap 系统调用新定义:

  • syscall ID:215

  • C接口: int munmap(void* start, unsigned long long len)

  • Rust接口: fn munmap(start: usize, len: usize) -> i32

  • 功能:取消一块虚存的映射。

  • 参数:同 mmap

  • 说明:

    • 为了简单,参数错误时不考虑内存的恢复和回收。
  • 错误:

    • [start, start + len) 中存在未被映射的虚存。

实验要求

  • 实现分支:ch4。

  • 完成实验指导书中的内容,实现虚拟内存,可以运行过去几个lab的程序。

  • 更新 sys_write 的范围检查,改为基于页表的检查方法。

  • 实现 mmap 和 munmap 两个自定义系统调用,并通过 Rust测例 中 chapter4 对应的所有测例,测例详情见对应仓库,系统调用具体要求参考 guide.md 中chapter4对应的所有测例。

注意:记得删除 lab3 关于程序时间片上界的规定。

challenge: 支持多核。

实验检查

  • 实验目录要求

    目录要求不变(参考lab1目录或者示例代码目录结构)。同样在 os 目录下 make run 之后可以正确加载用户程序并执行。

    加载的用户测例位置: ../user/build/bin

  • 检查

    可以正确 make run 执行,可以正确执行目标用户测例,并得到预期输出(详见测例注释)。

问答作业

  1. 请列举 SV39 页表页表项的组成,结合课堂内容,描述其中的标志位有何作用/潜在作用?

  2. 缺页

    这次的实验没有涉及到缺页有点遗憾,主要是缺页难以测试,而且更多的是一种优化,不符合这次实验的核心理念,所以这里补两道小题。

    缺页指的是进程访问页面时页面不在页表中或在页表中无效的现象,此时 MMU 将会返回一个中断,告知 os 进程内存访问出了问题。os 选择填补页表并重新执行异常指令或者杀死进程。

    • 请问哪些异常可能是缺页导致的?

    • 发生缺页时,描述相关的重要寄存器的值(lab2中描述过的可以简单点)。

    缺页有两个常见的原因,其一是 Lazy 策略,也就是直到内存页面被访问才实际进行页表操作。比如,一个程序被执行时,进程的代码段理论上需要从磁盘加载到内存。但是 os 并不会马上这样做,而是会保存 .text 段在磁盘的位置信息,在这些代码第一次被执行时才完成从磁盘的加载操作。

    • 这样做有哪些好处?

    此外 COW(Copy On Write) 也是常见的容易导致缺页的 Lazy 策略,这个之后再说。其实,我们的 mmap 也可以采取 Lazy 策略,比如:一个用户进程先后申请了 10G 的内存空间,然后用了其中 1M 就直接退出了。按照现在的做法,我们显然亏大了,进行了很多没有意义的页表操作。

    • 请问处理 10G 连续的内存页面,需要操作的页表实际大致占用多少内存(给出数量级即可)?

    • 请简单思考如何才能在现有框架基础上实现 Lazy 策略,缺页时又如何处理?描述合理即可,不需要考虑实现。

    缺页的另一个常见原因是 swap 策略,也就是内存页面可能被换到磁盘上了,导致对应页面失效。

    • 此时页面失效如何表现在页表项(PTE)上?
  3. 双页表与单页表

    为了防范侧信道攻击,我们的 os 使用了双页表。但是传统的设计一直是单页表的,也就是说,用户线程和对应的内核线程共用同一张页表,只不过内核对应的地址只允许在内核态访问。(备注:这里的单/双的说法仅为自创的通俗说法,并无这个名词概念,详情见 KPTI )

    • 如何更换页表?

    • 单页表情况下,如何控制用户态无法访问内核页面?(tips:看看上一题最后一问)

    • 单页表有何优势?(回答合理即可)

    • 双页表实现下,何时需要更换页表?假设你写一个单页表操作系统,你会选择何时更换页表(回答合理即可)?

报告要求

  • 简单总结本次实验与上个实验相比你增加的东西。(控制在5行以内,不要贴代码)

  • 完成问答问题。

  • (optional) 你对本次实验设计及难度的看法。