1
0
mirror of https://github.com/rcore-os/rCore.git synced 2024-11-25 09:26:17 +04:00
rCore/docs/RISCV.md
2018-07-12 22:46:17 +08:00

6.5 KiB
Raw Blame History

RISCV 移植记录

开发环境

具体配置过程详见Dockerfile

Rust-RISCV

目标指令集RISCV32IM

target: riscv32im_unknown_none

由于工具链二进制版本尚未内置此target因此需提供配置文件riscv32-blog_os.json

理想情况下目标指令集应为RISCV32G即使用全部扩展。但考虑到要把它跑在我们自己实现的CPU上指令集应该尽量精简即最好是RISCV32I。此外

  • 为什么用乘除指令扩展?

    Rust核心库中fmt模块会使用乘除运算若不使用乘除指令则会依赖LLVM提供的内置函数进行软计算导致链接错误。这一问题理论上可以通过在xargo中设置依赖compiler-builtin解决。但如此操作后仍有一个函数__mulsi3缺失32×32。经查compiler-builtin中实现了类似的__muldi3函数64×64),所以理论上可以用它手动实现前者。但如此操作后,还是不对,实验表明__muldi3本身也是不正确的。

    总之没有成功配置不使用M扩展的编译环境不过日后解决这一问题并不困难。

原子操作支持

配置文件中与原子操作相关的有两处:

  • feature+a使用A指令扩展
  • max-atomic-width决定能否使用core中的atomic模块设为0不可以设为32可以

二者是否相关,还不能确定。

  • 一方面,riscv-rust/rust官方配置中,二者是相关的。
  • 另一方面即使不使用A指令扩展设置max-atomic-width=32也可以编译通过。经检查生成的代码中包含了fence指令。这说明RISCV32I也可以用实现基本同步操作

然而由于LLVM后端对RISCV原子操作支持不完善无论是否+a当使用Mutex时它会调用core中的atomic_compare_exchange函数LLVM会发生错误。

鉴于更改上层实现替换Mutex工程难度较大我尝试直接修改core代码将上述问题函数手动实现。

思路是在关中断环境下,用多条指令完成目标功能。这对于单核环境应该是正确的。

我做了个补丁在进入docker环境后可运行make patch-core应用补丁确保clean后再build。

LLVM Bug

当使用(u8,u8)类型时会触发LLVM Error

llvm::MVT llvm::EVT::getSimpleVT() const: Assertion `isSimple() && "Expected a SimpleValueType!"' failed.

在开发过程中,有两个地方遇到了这个错误,一是log库,二是memory::cow模块。后者修改为(u16,u16)就解决了问题前者在Github上fork了一版换了一种实现。

BootLoader

参考bbl-ucore及后续的ucore_os_lab for RISCV32,使用bbl作为BootLoader。

然而官方版本和bbl-ucore中的fork版本都无法正常编译使用的是ucore_os_lab中的修改版本

bbl-ucore使用RISCV1.9的bblucore_os_lab使用RISCV1.10的bbl。后者相比前者去掉了对内核的内存映射因此需保证虚实地址一致。

事实上ucore_os_lab中的虚实地址并不一致且没有内存映射但依然能够运行应该是由于编译器生成的所有跳转都使用相对偏移。而Rust编译器会生成绝对地址跳转因此若虚实不一致会导致非法访存。

Trap

参考资料:

Trap

  • 中断帧32个整数寄存器 + 4个S-Mode状态寄存器
  • 开启中断:
    • stvec设置中断处理函数地址
    • sstatusSIE bit 开启中断

Timer

  • 开启时钟中断:

    • sieSTIE bit 开启时钟中断
    • sbi::set_timer设置下次中断时间
  • 读取时间:

    • mtime可读出当前时间低32bit

    • mtimeh当前时间高32bit仅RV32有效

      因此RV32下要读取完整时间u64需循环读取判等因为指令之间可能被中断要保证原子性。详见get_cycle()

  • 触发中断:

    • mtimecmp(h):下次触发时钟中断的时间

      当time>=timecmp时触发中断

      可通过sbi::set_timer设置

Memory

自映射

原x86_64版本使用页表自映射完成修改页表本身的操作。但RISCV下的页表规范阻碍了自映射的实现。原因是RISCV页表项中的flags明确表示它指向的是数据页VRW还是下层页表V。假如把一个二级页表项当做一级页表项来解读就会触发异常。而这是自映射机制中必须的操作。

为了绕开这个问题,就要求在访问一级页表虚地址期间将它所对应的二级页表项flags置为VRW。此外为了访问二级页表本身还需要再加一个自映射的二级页表项其flags为VRW。

制作一个自映射的二级页表过程示意如下:

fn set_recursive(self: &mut PageTable, recursive_index: usize, frame: Frame) {
    type EF = PageTableFlags;
    self[recursive_index].set(frame.clone(), EF::VALID);
    self[recursive_index + 1].set(frame.clone(), EF::VALID | EF::READABLE | EF::WRITABLE);
}

在自映射页表生效后,可用地址:

  • (R, R+1, 0) 访问二级页表
  • (R, P2, 0) 访问一级页表

地址格式为P2, P1, OffsetR为自映射下标

一个值得注意的现象是在编辑完一级页表、并将其对应的二级页表项flags恢复为V之后一级页表虚地址还是可以照常访问的这应该是TLB缓存未失效的缘故。

获取内存信息

原x86_64版本使用GRUB进行boot可通过Multiboot2获取内核段和可用空间的信息。RISCV下bbl无法提供这些信息只能在linker script中定义各段的起始位置符号然后在Rust中extern导入。

为了将来适配自己造的CPU8M RAM暂时规定内存划分如下

  • 0x80000000 - 0x80020000BootLoader
  • 0x80020000 - 0x80100000Kernel
  • 0x80100000 - 0x80200000Kernel heap
  • 0x80200000 - 0x80800000对应物理空间用于用户程序