用户态与内核态

目前为止的学习过程中,所有的程序都是运行在 Machine 模式下,但是在哪决定程序运行在什么模式下的呢?

在学习抢占式多任务时,我们有了创建任务的概念,在汇编代码中有这么一段,使用到了mstatus寄存器:

 # Notice: default mstatus is 0
 # Set mstatus.MPP to 3, so we still run in Machine mode after MRET.
 # Set mstatus.MPIE to 1, so MRET will enable the interrupt.
 li t0, 3 << 11 | 1 << 7
 csrr a1, mstatus     # a1 = mstatus
 or t0, t0, a1          # t0 = t0 | a1
 csrw mstatus, t0     # mstatus = t0

 j start_kernel  # hart 0 jump to c

mret返回后,是根据寄存器mstatusMPP来决定接来来是处于什么模式,我们在上面将MPP配置为3MPP的功能是 记录 Machine 模式下,前一个,特权级。这里解实现了在mret之后将模式设置为 Machine 模式(3)。

Responsive Image

因为mstatus上电后默认为全 0,所以如果不对其设置,那么在mret之后,就是运行在用户态(0)。

如果想让程序跑在用户态,只要不对齐设置,保持默认即可:

 # Notice: default mstatus is 0
 # Set mstatus.MPP to 3, so we still run in Machine mode after MRET.
 # Set mstatus.MPIE to 1, so MRET will enable the interrupt.
 li t0, 1 << 7
 csrr a1, mstatus     # a1 = mstatus
 or t0, t0, a1          # t0 = t0 | a1
 csrw mstatus, t0     # mstatus = t0

 j start_kernel  # hart 0 jump to c

为什么需要系统调用?因为在用户态一些资源(寄存器)的访问是受限的,所以需要封装一些函数,这些函数里会进行模式切换,然后访问需要的资源。

那么如何进行模式的切换呢?这就需要ecall指令。它本质上是触发了异常,就会进入到 Machine 模式处理异常,在 Machine 模式下就相当于在内核态了,就没有访问资源的限制了。

系统模式的切换

Responsive Image

ECALL指令实际就是主动触发异常,根据ECALL的权限级别产生不同的异常码,如下图:

Responsive Image

从 User 模式调用ECALL异常码等于 8,从 Supervisor 模式调用异常码等于 9,从 Machine 模式调用异常码等于 11。

异常产生时epc寄存器的值存放的是ECALL指令本身的地址。

如果想触发完异常接着往下执行,需要在异常处理逻辑里把 epc 寄存器值改为下一条指令地址,否则会进入死循环。

系统调用的执行流程

Responsive Image

Responsive Image

系统调用的传参

系统调用作为操作系统的对外接口,由操作系统的实现负责定义。参考 Linux 的系统调用,RVOS 定义系统调用的传参规则如下:

  • 系统调用号放在a7
  • 系统调用参数使用a0-a5
  • 返回值使用a0

Responsive Image

系统调用的封装

Responsive Image