练习#

课后练习#

编程题#

  1. *** 实现一个裸机应用程序A,能打印调用栈。

  2. ** 扩展内核,实现新系统调用get_taskinfo,能显示当前task的id和task name;实现一个裸机应用程序B,能访问get_taskinfo系统调用。

  3. ** 扩展内核,能够统计多个应用的执行过程中系统调用编号和访问此系统调用的次数。

  4. ** 扩展内核,能够统计每个应用执行后的完成时间。

  5. *** 扩展内核,统计执行异常的程序的异常情况(主要是各种特权级涉及的异常),能够打印异常程序的出错的地址和指令等信息。

注:上述编程基于 rcore/ucore tutorial v3: Branch ch2

问答题#

  1. * 函数调用与系统调用有何区别?

  2. ** 为了方便操作系统处理,M态软件会将 S 态异常/中断委托给 S 态软件,请指出有哪些寄存器记录了委托信息,rustsbi 委托了哪些异常/中断?(也可以直接给出寄存器的值)

  3. ** 如果操作系统以应用程序库的形式存在,应用程序可以通过哪些方式破坏操作系统?

  4. ** 编译器/操作系统/处理器如何合作,可采用哪些方法来保护操作系统不受应用程序的破坏?

  5. ** RISC-V处理器的S态特权指令有哪些,其大致含义是什么,有啥作用?

  6. ** RISC-V处理器在用户态执行特权指令后的硬件层面的处理过程是什么?

  7. ** 操作系统在完成用户态<–>内核态双向切换中的一般处理过程是什么?

  8. ** 程序陷入内核的原因有中断、异常和陷入(系统调用),请问 riscv64 支持哪些中断 / 异常?如何判断进入内核是由于中断还是异常?描述陷入内核时的几个重要寄存器及其值。

  9. * 在哪些情况下会出现特权级切换:用户态–>内核态,以及内核态–>用户态?

  10. ** Trap上下文的含义是啥?在本章的操作系统中,Trap上下文的具体内容是啥?如果不进行Trap上下文的保存于恢复,会出现什么情况?

实验练习#

实验练习包括实践作业和问答作业两部分。

实践作业#

sys_write 安全检查#

ch2 中,我们实现了第一个系统调用 sys_write,这使得我们可以在用户态输出信息。但是 os 在提供服务的同时,还有保护 os 本身以及其他用户程序不受错误或者恶意程序破坏的功能。

由于还没有实现虚拟内存,我们可以在用户程序中指定一个属于其他程序字符串,并将它输出,这显然是不合理的,因此我们要对 sys_write 做检查:

  • sys_write 仅能输出位于程序本身内存空间内的数据,否则报错。

实验要求#

  • 实现分支: ch2-lab

  • 目录要求不变

  • 为 sys_write 增加安全检查

    在 os 目录下执行 make run TEST=1 测试 sys_write 安全检查的实现,正确执行目标用户测例,并得到预期输出(详见测例注释)。

    注意:如果设置默认 log 等级,从 lab2 开始关闭所有 log 输出。

challenge: 支持多核,实现多个核运行用户程序。

实验约定#

在第二章的测试中,我们对于内核有如下仅仅为了测试方便的要求,请调整你的内核代码来符合这些要求。

  • 用户栈大小必须为 4096,且按照 4096 字节对齐。这一规定可以在实验4开始删除,仅仅为通过 lab2/3 测例设置。

注解

如何快速继承上一章练习题的修改

从这一章开始,在完成本章习题之前,首先要做的就是将上一章框架的修改继承到本章的框架代码。出于各种原因,实际上通过 git merge 并不是很方便,这里给出一种打 patch 的方法,希望能够有所帮助。

  1. 切换到上一章的分支,通过 git log 找到你在此分支上的第一次 commit 的前一个 commit 的 ID ,复制其前 8 位,记作 base-commit 。假设分支上最新的一次 commit ID 是 last-commit

  2. 确保你位于项目根目录 rCore-Tutorial-v3 下。通过 git diff <base-commit> <last-commit> > <patch-path> 即可在 patch-path 路径位置(比如 ~/Desktop/chx.patch )生成一个描述你对于上一章分支进行的全部修改的一个补丁文件。打开看一下,它给出了每个被修改的文件中涉及了哪些块的修改,还附加了块前后的若干行代码。如果想更加灵活进行合并的话,可以通过 git format-patch <base-commit> 命令在当前目录下生成一组补丁,它会对于 base-commit 后面的每一次 commit 均按照顺序生成一个补丁。

  3. 切换到本章分支,通过 git apply --reject <patch-path> 来将一个补丁打到当前章节上。它的大概原理是对于补丁中的每个被修改文件中的每个修改块,尝试通过块的前后若干行代码来定位它在当前分支上的位置并进行替换。有一些块可能无法匹配,此时会生成与这些块所在的文件同名的 *.rej 文件,描述了哪些块替换失败了。在项目根目录 rCore-Tutorial-v3 下,可以通过 find . -name *.rej 来找到所有相关的 *.rej 文件并手动完成替换。

  4. 在处理完所有 *.rej 之后,将它们删除并 commit 一下。现在就可以开始本章的实验了。

问答作业#

  1. 正确进入 U 态后,程序的特征还应有:使用 S 态特权指令,访问 S 态寄存器后会报错。请自行测试这些内容 (运行 Rust 三个 bad 测例 ) ,描述程序出错行为,注明你使用的 sbi 及其版本。

  2. 请结合用例理解 trap.S 中两个函数 __alltraps__restore 的作用,并回答如下几个问题:

    1. L40:刚进入 __restore 时,a0 代表了什么值。请指出 __restore 的两种使用情景。

    2. L46-L51:这几行汇编代码特殊处理了哪些寄存器?这些寄存器的的值对于进入用户态有何意义?请分别解释。

      ld t0, 32*8(sp)
      ld t1, 33*8(sp)
      ld t2, 2*8(sp)
      csrw sstatus, t0
      csrw sepc, t1
      csrw sscratch, t2
      
    3. L53-L59:为何跳过了 x2x4

      ld x1, 1*8(sp)
      ld x3, 3*8(sp)
      .set n, 5
      .rept 27
         LOAD_GP %n
         .set n, n+1
      .endr
      
    4. L63:该指令之后,spsscratch 中的值分别有什么意义?

      csrrw sp, sscratch, sp
      
    5. __restore:中发生状态切换在哪一条指令?为何该指令执行之后会进入用户态?

    6. L13:该指令之后,spsscratch 中的值分别有什么意义?

      csrrw sp, sscratch, sp
      
    7. 从 U 态进入 S 态是哪一条指令发生的?

  3. 对于任何中断,__alltraps 中都需要保存所有寄存器吗?你有没有想到一些加速 __alltraps 的方法?简单描述你的想法。

实验练习的提交报告要求#

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

  • 完成问答问题。

  • (optional) 你对本次实验设计及难度/工作量的看法,以及有哪些需要改进的地方,欢迎畅所欲言。