用户态与内核态
目前为止的学习过程中,所有的程序都是运行在 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
返回后,是根据寄存器mstatus
的MPP
来决定接来来是处于什么模式,我们在上面将MPP
配置为3
, MPP
的功能是 记录 Machine 模式下,前一个,特权级。这里解实现了在mret
之后将模式设置为 Machine 模式(3)。
因为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 模式下就相当于在内核态了,就没有访问资源的限制了。
系统模式的切换
ECALL
指令实际就是主动触发异常,根据ECALL
的权限级别产生不同的异常码,如下图:
从 User 模式调用ECALL
异常码等于 8,从 Supervisor 模式调用异常码等于 9,从 Machine 模式调用异常码等于 11。
异常产生时epc
寄存器的值存放的是ECALL
指令本身的地址。
如果想触发完异常接着往下执行,需要在异常处理逻辑里把 epc 寄存器值改为下一条指令地址,否则会进入死循环。
系统调用的执行流程
系统调用的传参
系统调用作为操作系统的对外接口,由操作系统的实现负责定义。参考 Linux 的系统调用,RVOS 定义系统调用的传参规则如下:
- 系统调用号放在
a7
中 - 系统调用参数使用
a0-a5
- 返回值使用
a0