OS 和 Virtual Memory: RISC-V 视角

这是个 Ring,代表我们用的 OS 软件的层次结构:

A379A81B-5886-404B-9DE7-B290E7A5D1BE

在操作系统中,我们可以看到操作系统的任务、职责:

  • 系统开启的第一个应用
  • 控制操作系统的 IO
  • 开启文件系统,网络栈等服务
  • 程序的加载器和运行程序的管理者

OS 工作的核心:

  • 运行进程的 isolation
  • 让系统和外部的 disk 等通信,完成 IO 等

OS 需要硬件支持的部分:

  • memory translation
    • 每个运行的进程需要从 virtual 的进程转成物理内存
    • program 实际处理的是虚拟内存,需要硬件支持
  • protection and privilege
    • 需要提供 user 和 supervisor 的模式,在 RISC-V 中有 machine 和 supervisor 模式
  • 更低的层次不能修改 memory mapping
    • supervisor 可以
    • supervisor 有独立于用户程序的 virtual 到 physical 映射
  • 提供 traps 和 interrupt,在命令层面提供了到 supervisor 的方法

在 RISC-V 中,硬件可以提供 Control and Status Registers,即 CSR 寄存器,这是一组可以原子读写的寄存器。

1
CSRRW rd rs csr

上述读取 csr 写到 rd, rs 不为0写到 csr。这不是普通的寄存器,用来与硬件沟通

我们需要 interrupt 和 exceptions,它们的相同点和区别是:

  • interrupt 是程序外部对程序的中断
  • exception:运行程序内部丢的毛病:需要读 csr, 可能是执行了违法指令,或者读了不合法内存
    • ECALL 用来实现 syscall
    • EBREAK 在现在的优先级引发 exception

trap 有一定的 flow: 由其他进程处理 —> OS 处理

在 CS61C 的定义中:

  • interrupt: 由外部引起,对于程序是异步的
  • exception: 由内部引起,同步
  • trap: 需要跳到 hardware 处理异常,并跳到 interrupt or trap handler 的代码

Trap:

  • Trap 之前的指令都被执行,之后的指令在 trap 返回之前不能被执行
  • 很多时候,如果指定了 handler,用户会存储自己的寄存器,stack 等,然后返回。
    • interrupt 处理器不一定要理硬件,其他 trap 可能会有个 trap code

硬件处理 interrupt:

  • 硬件需要调整,大部分情况会调整到 supervisor
  • 禁止 interrupt
  • 把旧的程序的 PC 用 csr 指令写到 sepc
  • 把 trap 的原因写入csr 寄存器 scause
  • 把 PC 设置成 stvec 寄存器
    • stvec 设置了 trap handler
    • 处理 exception

对于软件而言,需要处理的是:

  • 保存所有寄存器
  • 读对应的 csr ,发现除了什么问题
  • 恢复所有寄存器
  • 返回程序原先的位置

保存寄存器:

  • supervisor 模式下,在 sscratch 存储 trap handler
  • csrrw x1 x1 sscratch 将 x1 与 sccratch 交换
  • sepc 这个 CSR 寄存器处写 PC
  • 最后保存 x1, 恢复 sscratch

1A003613-A174-469A-87CE-3B6746CA8A47

所以这里先把所有 x1 相关的参数写到 sscratch 的位置,然后把 sepc 写到 x2,并继续保存它。然后恢复我们之前的,把 x1 (即原来的 sscratch )写到 sscratch ,把原来的 sscratch 写到 x2。这个时候再把 sscratch 写回。

以上流程相当于在 sscratch 保存 x1 x2 … 再保存 sepc

然后我们需要再后面决定之后的调用是什么:

  • 如果是一个 ECALL 指令,就执行具体的 ecall
  • 如果是不合法指令,直接 kill
  • memory 相关的就加载内存
  • timer interrupt 就进行 context switch

处理完之后,我们要恢复所有的寄存器,ecall 需要在 a0 写返回值。然后调用SRET 指令。

sret 指令返回硬件,我们在调用 sret 之前要先处理 ecall 之后做的软件行为:

  • 恢复所有的寄存器
  • 恢复 sepc,如果是 ecall, 返回 PC + 4 对应的位置
  • 恢复所有的其他寄存器,
    • 可能需要跳转到 sscratch
    • 如果是 ecall,设置 a0 作为返回值
  • 执行 sret

sretecall 对应,有着下列的硬件行为:

  • 恢复 interrupt
  • 从 supervisor 回到 user level
  • spec 中恢复 pc
  • 继续运行

功能和代价

interrupt 的功能:

在 user mode, 程序不能控制 virtual memory,但是可以改变虚拟内存的内容。

这份虚拟内存与 satp 这个 csr 寄存器有关,satp 是 page table pointer,表示物理内存上的页表。

这让调用 trap handler 之外不会 trap 系统调用。

interrupt 的代价:

  • 需要 flush pipeline
  • 需要保存和恢复所有的寄存器
  • 因为 trap handler 是不相关的代码,所以 cache 可能受影响

Context Switch

硬件支持了 timer interrupt, 触发的时候,硬件执行 context switch,把寄存器 sscratch 周围存储的寄存器存到现有进程的数据结构中,然后把 satp 和相关拷贝到 memory mapping 相关的数据结构中。然后根据 scheduler 来选择目标进程,恢复目标进程的 satp sepc 等,并调用 sret 返回。

IO

CPU 需要与不同的 IO 设备交互。

交互

软件希望:输入/输出一堆 bytes,同时希望能够提供一些 memory 相关的接口或者独立的指令。

对于 memory mapped I/O, 对于一段 address, 我们可以用 lw/sw 等指令处理 IO。

下面是课件上的一些 memory mapping 的效率,结论是 IO 设备处理数据的速度是远慢于 CPU 的

391D529F-58C6-49C2-9448-EF5A47ACCA76

同通用场景下, device registers 有两个寄存器:

  • control register: 表示 I/O 是否 ready
  • data register: 包含数据

常用的手段包含

  1. polling: PIO 轮询 control register ,看看是否准备好了。这样比较消耗 cpu
  2. interrupt: 硬件在有数据的时候触发 interrupt,在 IO 频率高的情况下,例如鼠标、键盘、网络等,实际上会触发很多的 IO
  3. DMA