0%

rcore-summary-yangpan

正文如下。

rust语言学习

  • Rust 语言是一种高效、可靠的通用高级语言
  • 高性能 - Rust 速度惊人且内存利用率极高。由于没有运行时和垃圾回收,它能够胜任对性能要求特别高的服务,可以在嵌入式设备上运行,还能轻松和其他语言集成。
  • 可靠性 - Rust 丰富的类型系统和所有权模型保证了内存安全和线程安全,让您在编译期就能够消除各种各样的错误。
  • 生产力 - Rust 拥有出色的文档、友好的编译器和清晰的错误提示信息, 还集成了一流的工具 —— 包管理器和构建工具, 智能地自动补全和类型检验的多编辑器支持, 以及自动格式化代码等等。
  • RUST语言特点,让我们在利用它实现操作系统的时侯有很多优势,例如它对生命周期等的约束

rcore

实验一让我更加理解了操作系统的启动过程,以及工作机制。

  • 总结系统启动过程(不限于rcore)
    加电
  • cpu寄存器归位
  • cache 关闭
  • MMU关闭,物理方式访存
    cpu读固化在硬件内存中指定地址的指令
  • 将内存布局恢复到最佳状态
  • 其他的硬件初始化
    跳到内核态(一般系统此时指UEFI,一个小的系统)
  • 初始化堆栈 (可用语言)
  • 最简单初始化串口(可输出信息)
  • 可用MMU
    加载程序
  • 系统内核加载到内存

实验二让我了解了特权级的概念,如何切换,如何保存上下文。

  • 保护操作系统不受有意或无意出错的程序破坏的机制被称为 特权级 (Privilege) 机制,它实现了用户态和内核态的隔离,需要软件和硬件的共同努力。
    • 为了保护操作系统安全,对应用程序而言需要作限制:访问的地址空间;执行的指令。
    • 一方面要对应用程序作出一定的限制,另一方面能得到操作系统的服务。即应用程序和操作系统还需要有交互的手段。使得低特权级软件都只能做高特权级软件允许它做的,且低特权级软件的超出其能力的要求必须寻求高特权级软件的帮助。
    • 处理器设置两个不同安全等级的执行环境:用户态特权级的执行环境和内核态特权级的执行环境。指令安全检查:规定内核态特权级指令子集中的指令只能在内核态特权级的执行环境中执行,如果在用户态特权级的执行环境中执行这些指令,会产生异常。
    • ecall :具有用户态到内核态的执行环境切换能力的函数调用指令
    • eret :具有内核态到用户态的执行环境切换能力的函数返回指令(RISC-V中有类似的 sret 指令)
    • 传统的函数调用方式(即通常的 call 和 ret 指令或指令组合)将会直接绕过硬件的特权级保护检查
    • 用户态应用直接触发从用户态到内核态的 异常控制流 的原因总体上可以分为两种:执行 Trap类异常 指令和执行了会产生 Fault类异常 的指令 。Trap类异常 指令 就是指用户态软件为获得内核态操作系统的服务功能而发出的特殊指令。 Fault类 的指令是指用户态软件执行了在内核态操作系统看来是非法操作的指令。
    • 与特权级无关的一般的指令和通用寄存器 x0~x31 在任何特权级都可以任意执行。而每个特权级都对应一些特殊指令和 控制状态寄存器 (CSR, Control and Status Register) ,来控制该特权级的某些行为并描述其状态
    • 在 RISC-V 调用规范中,和函数调用的情形类似,约定寄存器 a0a6 保存系统调用的参数, a0a1 保存系统调用的返回值。a7 用来传递 syscall ID,保存要请求哪个系统调用。
    • 静态编码:通过一定的编程技巧,把应用程序代码和批处理操作系统代码“绑定”在一起。
    • 动态加载:基于静态编码留下的“绑定”信息,操作系统可以找到应用程序文件二进制代码的起始地址和长度,并能加载到内存中运行。
    • 应用程序在用户态特权级运行时,是无法直接通过函数调用访问处于内核态特权级的批处理操作系统内核中的函数的.所以会通过某种机制进行特权级之间的切换,使得用户态应用程序可以得到内核态操作系统函数的服务。
    • 在 Trap 前的特权级不会高于Trap后的特权级
    • 只要是 Trap 到 S 特权级,操作系统就会使用 S 特权级中与 Trap 相关的 控制状态寄存器 (CSR, Control and Status Register) 来辅助 Trap 处理
    • 硬件机制主要负责特权级切换、跳转到异常处理入口地址(要在使能异常/中断前设置好)以及在 CSR 中保存一些只有硬件才方便探测到的硬件内的 Trap 相关信息

实验三让我了解了批处理系统,分时处理系统实现的核心思想

  • 分时多任务系统的前提条件,在内存中可以驻留多个应用
  • 在内存中尽量同时驻留多个应用,但只有一个程序执行完毕后或主动放弃执行,处理器才能执行另外一个程序。这种运行方式称为多道程序
  • 多个应用程序被一次性地加载到内存中,这样在切换到另外一个应用程序执行会很快,不需清空前一个应用,然后加载当前应用的过程与开销。
  • 任务切换不涉及特权级切换,它的一部分工作是由编译器帮忙完成的,它对应用是透明的。事实上,它是来自两个不同应用的 Trap 执行流之间的切换。当一个应用 Trap 到 S 模式的操作系统中进行进一步处理的时候,其 Trap 执行流可以调用一个特殊的 __switch 函数。这个函数表面上就是一个普通的函数调用:在 __switch 返回之后,将继续从调用该函数的位置继续向下执行。但是其间却隐藏着复杂的执行流切换过程。具体来说,调用 __switch 之后直到它返回前的这段时间,原 Trap 执行流会先被暂停并被切换出去, CPU 转而运行另一个应用的 Trap 执行流。之后在时机合适的时候,原 Trap 执行流才会从某一条 Trap 执行流(很有可能不是它之前切换到的那一条)切换回来继续执行并最终返回。不过,从实现的角度讲, __switch 和一个普通的函数之间的差别仅仅是它会换栈
  • 任务运行状态:任务从开始到结束执行过程中所处的不同运行状态:未初始化、准备执行、正在执行、已退出;任务控制块:管理程序的执行过程的任务上下文,控制程序的执行与暂停。任务相关系统调用:应用程序和操作系统直接的接口,用于程序主动暂停 sys_yield 和主动退出 sys_exit
  • 协作式操作系统是等待应用程序主动放弃CPU,调用sys_yield交出CPU使用权。
  • 抢占式操作系统是任务随时都有可能被切换出去
  • 对于某个处理器核而言, 陷入 与发起 陷入 的指令执行是 同步 (Synchronous) 的, 陷入被触发的原因一定能够追溯到某条指令的执行;而中断则 异步 (Asynchronous) 于当前正在进行的指令,也就是说中断来自于哪个外设以及中断如何触发完全与处理器正在执行的当前指令无关。
  • 中断每一个都有 M/S 特权级两个版本。中断的特权级可以决定该中断是否会被屏蔽,以及需要 Trap 到 CPU 的哪个特权级进行处理。 如果中断的特权级低于 CPU 当前的特权级,则该中断会被屏蔽,不会被处理。如果中断的特权级高于与 CPU 当前的特权级或相同,则需要通过相应的 CSR 判断该中断是否会被屏蔽
  • 以内核所在的 S 特权级为例,中断屏蔽相应的 CSR 有 sstatus 和 sie 。sstatus 的 sie 为 S 特权级的中断使能,能够同时控制三种中断,如果将其清零则会将它们全部屏蔽。即使 sstatus.sie 置 1 ,还要看 sie 这个 CSR,它的三个字段 ssie/stie/seie 分别控制 S 特权级的软件中断、时钟中断和外部中断的中断使能。
  • 在 RV64 架构上,该计数器保存在一个 64 位的 CSR mtime 中,我们无需担心它的溢出问题,在内核运行全程可以认为它是一直递增的。另外一个 64 位的 CSR mtimecmp 的作用是:一旦计数器 mtime 的值超过了 mtimecmp,就会触发一次时钟中断。但是他们都是M 特权级的 CSR ,而我们的内核处在 S 特权级,是不被硬件允许直接访问它们的。好在运行在 M 特权级的 SEE 已经预留了相应的接口,我们可以调用它们来间接实现计时器的控制

实验四让我对虚拟内存有了深刻的理解

  • 为了限制应用访问内存空间的范围并给操作系统提供内存管理的灵活性,计算机硬件引入了各种内存保护/映射硬件机制,如RISC-V的基址-边界翻译和保护机制。它们的共同之处在于CPU访问的数据和指令内存地址是虚地址,需要进行转换形成合法的物理地址或产生非法的异常
  • 抽象地将物理内存管理起来的时候需要达成下面这几个目标:对应用程序透明,不应该带来过多开销,检测恶意行为并有效阻止。
  • 操作系统要达到地址空间抽象的设计目标,需要有计算机硬件的支持,这就是计算机组成原理课上讲到的 MMUTLB 等硬件机制
  • 操作系统可以设计巧妙的数据结构来表示地址空间。但如果完全由操作系统来完成转换每次处理器地址访问所需的虚实地址转换,那开销就太大了。这就需要扩展硬件功能来加速地址转换过程
  • 一条访存指令的时候,它都是在以虚拟地址为索引读写自己的地址空间。此时,CPU 中的 内存管理单元(MMU, Memory Management Unit) 自动将这个虚拟地址进行 地址转换(Address Translation) 变为一个物理地址,也就是物理内存上这个应用的数据真实被存放的位置。也就是说,在 MMU 的帮助下,应用对自己地址空间的读写才能被实际转化为对于物理内存的访问。
  • 对于不同的应用来说,该映射可能是不同的,即 MMU 可能会将来自不同两个应用地址空间的相同虚拟地址翻译成不同的物理地址。要做到这一点,就需要硬件提供一些寄存器,软件可以对它进行设置来控制 MMU 按照哪个应用的地址空间进行地址转换。
  • 地址空间只是一层抽象接口,它有很多种具体的实现策略。对于不同的实现策略来说,操作系统内核如何规划应用数据放在物理内存的位置,而 MMU 又如何进行地址转换也都是不同的
  • 当一个应用的地址空间比较大的时候,页表里面的项数会很多(事实上每个虚拟页面都应该对应页表中的一项,上图中我们已经 省略掉了那些未被使用的虚拟页面),导致它的容量极速膨胀,已经不再是像之前那样数个寄存器便可存下来的了,CPU 内也没有 足够的硬件资源能够将它存下来。因此它只能作为一种被内核管理的数据结构放在内存中,但是 CPU 也会直接访问它来查页表, 这也就需要内核和硬件之间关于页表的内存布局达成一致
  • 默认情况下 MMU 未被使能,此时无论 CPU 位于哪个特权级,访存的地址都会作为一个物理地址交给对应的内存控制单元来直接 访问物理内存。可以通过修改 S 特权级的一个名为 satp 的 CSR 来启用分页模式,在这之后 S 和 U 特权级的访存 地址会被视为一个虚拟地址,它需要经过 MMU 的地址转换变为一个物理地址,再通过它来访问物理内存
  • RV64 架构下 satp 的字段分布中,当 MODE 设置为 0 的时候,代表所有访存都被视为物理地址;而设置为 8 的时候,SV39 分页机制被启用,所有 S/U 特权级的访存被视为一个 39 位的虚拟地址,它们需要先经过 MMU 的地址转换流程, 如果顺利的话,则会变成一个 56 位的物理地址来访问物理内存;否则则会触发异常,这体现了该机制的内存保护能力。
  • 重要思想,在分配页地时候返回值类型并不是 FrameAllocator 要求的物理页号 PhysPageNum ,而是将其 进一步包装为一个 FrameTracker 。这里借用了 RAII 的思想,将一个物理页帧的生命周期绑定到一个 FrameTracker 变量上,只需为 FrameTracker 实现 Drop Trait,当一个 FrameTracker 实例被回收的时候,它的 drop 方法会自动被编译器调用,通过之前实现的 frame_dealloc 我们就将它控制的物理页帧回收以供后续使用了