Skip to content

Latest commit

 

History

History
178 lines (123 loc) · 16.4 KB

File metadata and controls

178 lines (123 loc) · 16.4 KB

如第5节所讨论的,许多计算机系统被组织成用户软件和系统软件两层。系统软件(例如,操作系统内核和设备驱动程序)是负责保护和管理整个系统的软件,包括与外设交互执行输入和输出操作以及加载、调度、执行用户应用程序。用户软件通常仅限于对(普通)寄存器和主存上的数据执行操作。每当用户软件需要执行需要与系统其他部分交互的操作时,例如从文件中读取数据或在计算机显示器上显示信息,它就调用系统软件来替它执行该过程。

本章讨论保护系统免受错误或恶意用户程序侵害的硬件机制,以及如何编程实现这些机制。

11.1 特权等级

ISA定义了软件可以用来执行计算的资源集。例如它定义了指令、指令的行为和操作数,以及寄存器。

特权级别(privilege level )定义了软件可以访问ISA中定义哪些的资源(寄存器、指令等)。它们可以用来限制软件的执行,并保护系统免受试图执行不允许的操作的软件的攻击。例如,可以将系统配置为以受限制的特权级别执行用户应用程序,以防止它们直接与外设交互或访问特殊的寄存器。RISC-V的ISA定义了三种特权级别:

  • U:User/Application (译注:后面简称为U模式)
  • S:Supervisor (译注:后面简称为S模式)
  • M:Machine(译注:后面简称为M模式)

Machine特权级别具有最高的特权,允许对硬件进行完全访问。Supervisor权限级别具有第二高的权限,User/Application权限级别具有最低的权限。

实现RISC-V硬件时,可以实现这些特权级别的部分或全部。例如,当为紧凑和直接的嵌入式系统实现硬件时,可能只需要Machine特权级别。而为依赖于操作系统来管理应用程序(例如,桌面计算机系统)的系统实现硬件时,通常包括所有三个特权级别,以便于操作系统的实现。

RISC-V特权模式(privilege mode)定义了当前正在执行的软件的特权级别。例如,当Machine特权模式处于活跃状态时,当前执行的软件具有Machine特权级别,因此具有对硬件的完全访问权限。

非特权模式(unprivileged mode )是特权最低的模式。在RISCV中,无特权模式是User/Application特权模式,也称为用户模式U模式非特权ISA是在非特权模式下运行的软件可以访问的ISA子集。

为了简化讨论,本章剩下的部分将集中讨论只有U模式和Machine模式的RISC-V处理器。

11.2 保护系统

U模式限制了当前执行的软件可以访问的资源;因此,为了保护系统不受错误或恶意用户程序的影响,系统软件通常在执行(或返回控制)用户代码之前,将特权模式设置为U模式。

通常采取以下措施来保护系统:

  • **初始化系统:**上电后,硬件自动将特权模式设置为Machine模式,并开始执行boot code(它的功能是引导系统的启动,即对硬件系统进行初始化,并加载OS的代码)。boot code将OS加载到内存中,并在Machine模式下调用OS中的始化代码来配置整个系统。
  • **执行用户代码:**一旦设置完毕,OS就可以将用户程序加载到主存中执行。但是,在转移控制以执行用户代码之前,它将特权模式设置为U模式。
  • **处理非法操作:**如果用户软件试图执行特权操作,例如与外设交互,硬件将停止执行用户代码并调用OS的来处理非法操作。将在11.3节讨论硬件如何通过异常处理机制将控制权转移到OS。
  • **调用OS(的功能):**如果用户程序需要执行一个敏感的过程,例如向外设输出信息,它必须请求OS来为它执行此操作。当将控制权转移到OS时,硬件必须将特权模式改为Supervisor模式或Machine模式,这样OS才能以适当的特权执行操作。为此,ISA通常包含一种称为软中断(software interrupt)的机制,允许在非特权模式下切换到特权模式,并调用OS的代码。这种机制使得用户代码不能改变特权模式并执行自己的代码。一旦操作系统为用户程序完成相关操作,它将特权模式更改回U模式,并返回到用户程序。
  • **调用操作系统(的功能):**如果用户程序需要执行一个敏感的操作,例如向外设输出,它必须调用操作系统来为它实行该操作。
  • **处理外部中断:**当外部中断发生时,硬件将特权模式设置为Machine模式,因此ISR有足够的特权来处理中断。请注意,ISR属于系统程序。

11.3 异常(Exception)

异常是指CPU在执行指令时,因出现异常情况而产生的事件。例如,试图执行非法指令是导致RISC-V CPU产生异常的一个原因。

异常通常触发异常处理机制(exception handling mechanism,一种处理异常的流程,下面简称为EHM),以便在CPU继续执行程序之前处理异常情况。EHM通常会导致CPU将执行流重定向到一个系统例程,该系统例程会:

  1. 保存当前执行的程序的上下文
  2. 处理异常情况
  3. 恢复步骤1保存的上下文,继续执行原来的程序

请注意,异常处理流程与硬件的中断处理流程非常相似。实际上,RISC-V CPU使用相同的机制来处理中断和异常,即,它保存当前上下文的一部分(例如,pc寄存器),设置mcause寄存器,并将执行流重定向到ISR。正如第10.3.4节所讨论的,ISR可以通过检查mcause寄存器来区分中断和异常。具体来说就是通过检查mcause.INTERRUPT来发现处理的是异常还是中断。另外,mcause.EXCCODE子字段表示中断或异常的来源。在RISC-V上异常和中断有几个可能的来源。表11.1给出了中断和异常的来源,以及它们在mcause中对应的代码值。

EHM通常用于保护系统不受非法用户代码操作的影响。在这种情况下,硬件被OS配置,一旦满足以下条件就会产生异常:

  • 硬件处于U模式
  • CPU试图执行某些特权操作,例如访问映射到外设的地址,或访问只能在Machine模式下访问的CSR

在发生异常时,OS中的ISR会被调用,它可以决定如何处理用户程序。

注:异常是因为CPU执行指令而发生,因此它们是同步事件。而中断可能在任何时间发生,与CPU的执行周期无关。因此它们是异步事件。

11.4 软中断(Software Interrupts)

软中断(Software interrupts)是CPU在执行特殊指令时产生的事件。例如,在RISC-V中,环境调用(ecall)和断点(break)指令使得CPU在执行时产生软中断。它们类似于异常,因为它们是由于执行指令而发生的同步事件。尽管如此,异常只在异常情况下产生,而软中断总是在CPU执行这些特殊指令时产生。

软中断通常会触发某个机制以改变特权模式,然后执行一个用于处理中断的例程。该机制允许用户程序调用需要运行在更高特权级别的系统程序。

大多数ISA采用相同的机制来处理中断、异常和软中断,例如它们都要保存当前执行程序的部分上下文(例如pc寄存器),设置中断原因(例如设置mcause 寄存器),并将执行流重定向到ISR。

mcause.INTERRUPT mcause.EXCCODE 原因
1 0 U模式下的软中断
1 1 S模式下的软中断
1 2 保留
1 3 M模式下的软中断
1 4 U模式下的定时器中断
1 5 S模式下的定时器中断
1 6 保留
1 7 M模式下的定时器中断
1 8 U模式下的外部中断
1 9 S模式下的外部中断
1 10 保留
1 11 M模式下的外部中断
1 12~15 Reserved for future standard use
1 ≥16 Reserved for platform use

mcause.INTERRUPT mcause.EXCCODE 原因
0 0 指令地址未对齐
0 11 指令访问错误
0 2 非法指令
0 3 断点
0 4 load操作访问的地址未对齐
0 5 load访问出错
0 6 store/AMO 访问的地址未对齐
0 7 store/AMO 访问出错
0 8 U模式下的ecall出错
0 9 S模式下的ecall出错
0 10 保留
0 11 M模式下的ecall出错
0 12 指令缺页异常
0 13 Load操作缺页异常
0 14 保留
0 15 Store/AMO操作缺页异常
0 16~23 保留
0 24~31 保留
0 32~47 保留
0 48~63 保留
0 ≥64 保留

表11.1,异常和中断的源,以及它们在mcause寄存器中的值。第1个表全是中断,第2个表全是异常

11.5 保护RISC-V系统

下面将讨论如何使用RISC-V的各种特权模式,以及异常和中断处理机制,来保护系统免受错误或恶意用户程序的影响。

11.5.1 改变特权等级

RISC-V CPU将当前特权模式存储在程序不能直接访问的内部存储设备上。换句话说,程序不能直接检查或修改此存储设备,以获取或改变当前特权模式。检查当前特权模式的唯一方法是生成一个程序中断或异常,它将特权模式的代码复制到mstatus.MPP(Machine Previous Privilege)寄存器中。另外,设置当前特权模式的唯一方法是修改mstatus.mpp的值然后执行mret指令,该指令会使用mstatus.mpp的值去设置当前的特权模式。

下面的代码显示了系统如何将特权模式更改U模式然后调用用户程序。

  • 首先,将mstatus.MPP的值改为00,即U模式。
  • 然后,修改mepc寄存器,将U模式下的程序入口点加载其中。
  • 最后,执行mret指令,该指令使用mstatus.MPP的值更改特权模式,使用mpec的值修改pc寄存器。
# 切换到U模式
csrr t1, mstatus     # 读取mstatus.MPP的值到t1
li t2, ~0x1800       # 掩码,用于修改mstatus.MPP位于第11~12
and t1, t1, t2       # 修改位于t1中mstatus.MPP的值
csrw mstatus, t1     # 回写到mstatus中

la t0, user_main     # 加载用户的程序地址到t0
csrw mepc, t0        # 将t0复制到mepc中

mret                 # 执行该指令,使得:PC<=MEPC; mode<=MPP;

11.5.2 配置执行与软中断机制

RISC-V CPU使用类似的机制来处理中断、异常和软中断,也就是说,它保存当前上下文的一部分(例如pc寄存器),设置mcause等其他CSR,然后将执行流重定向到ISR。因此,配置异常/软中断的处理机制,与10.3.5节讨论中讨论过的,配置外部中断的处理机制非常类似。

系统通过注册异常/软中断的服务例程来初始化异常/软中断的处理流程。这与注册ISR处理外部中断的方式相同。

  • 在direct模式下,需要注册一个例程,该例程负责检查mcause寄存器以识别事件源并调用适当的例程。
  • 而在vector模式,外部中断、异常和软中断处理例程必须在中断向量化表上注册。10.3.5节给出了在direct模式和vector模式下配置中断处理机制的代码片段。

在RISC-V上,必须通过设置mstatusmie这两个CSR来启用外部中断。而异常和软中断则总是启用的,不需要额外的配置。

11.5.3 处理非法操作

当指令试图执行非法操作时,RISC-V CPU会产生异常,例如执行CPU无法识别的指令。

包含U模式和M模式的RISC-V系统通常包括一个内存保护单元。 可以配置该单元,使得CPU试图从特定地址读写数据/取指执行时产生异常。操作系统可以通过配置这个单元来保护系统,当以U模式执行的代码试图访问受保护的地址时(例如映射到外设的地址或OS专属的地址时)该单元就会产生异常。此时,如果CPU试图从受保护的地址读/写数据,内存保护单元产生 Load访问出错 或者 Store/AMO访问出错 (此时mstatus.EXCCODE的值分别为5/7,如表11.1所示)。同样,如果CPU试图从受保护的地址取指令执行,会产生一个 指令访问错误 异常(此时 mstatus.EXCCODE = 1)。

当一个异常产生的时候,RISC-V CPU会执行以下操作:

  • 保存当前pc寄存器的值到mepc寄存器
  • 设置mcause的值,该值能够分辨出异常的类型
  • 将当前特权模式的值复制到mstatus.MPP
  • 将当前特权模式切换到M模式
  • 设置pc寄存器,将执行流重定向到异常处理例程

一些异常也会设置mtval寄存器,以额外的信息。例如,当load或store操作异常发生时,mtval会被设置为异常产生时访问的虚拟地址。根据异常的不同,修复导致异常的问题,并让产生异常的程序继续执行可能是有意义的。缺页异常是系统可以处理的异常,因此产生异常的程序可以继续执行。在这些情况下,处理异常通常类似于处理外部中断,即异常处理例程必须保存上下文,处理异常,最后恢复上下文,以便(之前)在CPU上运行的程序可以继续执行。如果异常是因为非常操作产生,并且无法从错误中恢复时,OS可能会终结这个试图执行非法操作的进程。

11.5.4 处理系统调用

如6.7.4节所述,在RISC-V中,用户代码可以通过执行环境调用(ecall)指令来调用OS的功能,即执行一个系统调用。该指令产生一个触发异常/中断处理流程的软中断。在U模式下执行ecall指令时,硬件会将mcause.INTERRUPT设置为0,将mcause.EXCCODE设置为8。因此,如果中断/异常处理机制配置为direct模式,主ISR(如上所述,direct模式下只有一个主ISR负责处理中断)可以通过mcause寄存器的值来判断用户程序请求系统调用。

当发生异常或软中断时,系统将pc寄存器的值保存在mepc中。在处理异常(例如缺页异常)之后,系统可能会返回之前导致异常的那一条指令,因此程序会尝试再次执行(相同的)操作。但在软中断时,系统不能返回到同一条指令。否则它将再次进行系统调用。在这种情况下,系统必须返回到(触发异常的指令)后续的指令。为此,软中断的ISR必须在执行mret指令之前调整mepc中的值,使其指向(触发软中断的指令的)下一条指令。

下面的代码展示了如何在执行mret指令之前调整mepc寄存器,使其指向下一条指令。

# 调整MEPC寄存器的值, 使得mret之后返回到ecall指令的下一条指令
csrr a1, mepc   # 将mepc的值加载到a1
addi a1, a1, 4  # a1 = a1 + 4
csrw mepc, a1   # 将a1的值更新到mepc