硬件浮点

2020-06-23 07:31:43

本文是更大的教程“OS历险记:使用Rust制作RISC-V操作系统”中的一篇文章。

到目前为止,我们只使用整数指令。但是,既然我们希望支持用户进程,就必须能够支持硬件浮点单元(FPU)。在RISC-V中,这相当简单,但如果我们不小心,可能会导致一些麻烦。

首先,必须通过mstatus寄存器(更具体地说是FS位(位14和13))控制浮点单元。

回想一下,我们使用此寄存器更改处理器模式(MPP)。但是,现在我们关心的是这个寄存器来控制浮点单元。这两位支持浮点单元的四种不同状态:

如果我们试图在FPU关闭时加载或存储(fld或fsd)浮点寄存器,它将陷入操作系统。因此,在我们使用浮点单元进行任何工作之前,我们需要确保已打开浮点单元并做好了开始工作的准备。我们可以在编写的switch_to_user函数中添加一点,以便在上下文切换到另一个进程时将FPU打开到初始状态。

.global switch_to_userswitch_to_user:csrw msccratch,a0 ld a1,520(A0)ld a2,512(A0)ld a3,552(A0)li t0,1<;<;7|1<;<;5|1<;<;13 slli a3,a3,11或t0,t0,a3 csrw mstatus,t0 csrw mepc,a1 csrw satp,a2 li t1,0xaaa csrw mie,t1 la t2,m_trap_Vector csrw mtvec,t2 mv t6,a0.set i,0.rept 32 load_fp%i.set i,i+1.endr.set i,1.rept。

我们要做的第一件事是添加1<;<;13,它将FS位设置为01,这是初始状态。与MPP(计算机上一级权限)不同,这会立即生效。然后,我们从TrapFrame结构恢复寄存器。方便的是,这遵循32个通用整数寄存器。您可以看到,我们的宏LOAD_FP被展开为以下内容:

.altacro.set NUM_GP_REGS,32#每个上下文的寄存器数。set REG_SIZE,8#寄存器大小(字节)。宏LOAD_FP i,basereg=T6 fld f\i,((NUM_GP_REGS+(\i))*REG_SIZE)(\basereg).endm

我们跳过\(8\乘以32=256\)字节以遍历所有32个GP寄存器。现在我们到了我们想要的收银台了。就像ld将内存位置加载到通用寄存器中一样,fld也会将内存位置加载到浮点寄存器中。记住:此时必须通过FS位打开FPU(\(\text{FS}\NEQ 0\))。

我们使用FS位不仅用来控制FPU,还用来查看发生了什么。RISC-V特权规范建议在陷阱期间对FPU执行以下操作:

浮点单元具有多个开/关状态的原因是,它只允许我们在寄存器发生更改时保存它们。如果用户进程从未使用过FPU,为什么要保存这些寄存器呢?但是,浮点单元的粒度是这样的,如果32个寄存器中只有1个发生更改,我们必须保存所有32个寄存器。

因此,我们需要添加代码来检查FPU的状态,以确定是否需要保存寄存器。我们可以通过读取FS位来实现这一点:

csrr t1,mstatus srli t0,t1,13和t0,t0,3 li t3,3 bne t0,t3,1f.set i,0.rept 32 save_fp%i,t5.set i,i+1.endr1:

上面,我们读取mstatus寄存器,将其右移13位,并用3(即二进制11)对其进行掩码。这意味着我们隔离了FS位(2位),以便我们可以读取值。如果这两位不是11(回想一下,这意味着寄存器被写入),那么我们跳过保存浮点寄存器,向前跳到数字标签1(因此是1f)。

#[repr(C)]#[派生(克隆,复制)]pub struct TrapFrame{pub regs:[usize;32],//0-255 pub fregs:[usize;32],//256-511 pub satp:usize,//512-519 pub PC:usize,//520 pub hartid:usize,//528 pub qm:usize,//536 pub pid:usize。

我们的fregs(浮点寄存器)遵循32个通用寄存器。因此,我们唯一需要做的数学运算是\(8\乘以32=256\)。

这真是太棒了!我们可以在内核中使用浮点单元,前提是我们在使用任何寄存器之前将其打开。同样,如果我们在FS位为00时尝试使用FPU,我们将得到一个陷阱。