risc-v linux启动流程分析1-特性描述

概述

现在RISC-V在芯片厂已经开始有一定的部署了, 从MCU级别的微控制处理器到AP CPU级别SoC都有进行一些设计或量产. 从芯片开发成本来看, RISC-V是一个非常适合用来实现减少授权费和版税的in-house CPU的指令集. 因为RISC-V指令集目前已经形成的一定生态并且有越来越好的趋势, 对于实现RISC-V的CPU, 可以直接免费使用基于这个指令集的开源生态, 如GCC, LLVM等.

虽然目前RISC-V指令集在实现AP CPU时还有一些扩展指令集正在准备或刚刚准备好, 但是RISC-V linux确实已经可以正常跑起来了. 我们就来以RISC-V 32来分析下Linux的启动过程, 我们主要分析在start_kernel之前的流程, 我们使用的linux版本时linux-6.2-rc3, 配置采用的时linux mainline上面的rv32_defconfig.

RV32 linux的特点

Memory mapping

Linux内核目前在RV32上的memory mapping如下所示:

1
2
3
4
5
6
[    0.000000] Virtual kernel memory layout:
[ 0.000000] fixmap : 0x9cc00000 - 0x9d000000 (4096 kB)
[ 0.000000] pci io : 0x9d000000 - 0x9e000000 ( 16 MB)
[ 0.000000] vmemmap : 0x9e000000 - 0xa0000000 ( 32 MB)
[ 0.000000] vmalloc : 0xa0000000 - 0xc0000000 ( 512 MB)
[ 0.000000] lowmem : 0xc0000000 - 0x00000000 (1024 MB)

可以看到内核地址空间和常规的的大约3:1有很大的不同, 变成了大约5:3, 用户地址空间被压缩的更小了. 带来的变化时内核线性地址区域增长到了1G, 并且vmalloc区域也有512MB, 比常规的3:1多了非常多. 另外地址空间中没有看到modules区域, 可能时放到了vmalloc里面. 这里先不去研究.

Relocation特点

Relocation在RISC-V linux上也是一个有特色的地方, 一是没有做动态链接, 二是地址空间切换的实现也比较有意思, 这一点我们在启动过程章节来描述

这一节我们主要说说RISC-V linux的relocation没有做动态链接这个事情. 常规的Linux实现都是采用类似动态链接的方式, 比如通过fpic/fpie编译出可执行文件, 然后通过rel段/dyn段来实现运行时的代码修改, 而RISC-V是没有这个运行时代码修改的过程的. 和linux实现比较, u-boot的relocation则是通过fpic编译选项加动态段来实现的. 那么linux不使用fpic编译选项是如何实现的呢, 如果实现能在不修改代码的情况下同时运行在两个不同的地址空间上的.

首先RISC-V linux在编译的时候的flag是fno-PIE的, 然后还有一个关键的flag是mcmodel=medany, 这个选项在RISC-V上可以有两种选择, 我们来看看gcc的文档的描述:

  • medlow: Generate code for the medium-low code model. The program and its statically defined symbols must lie within a single 2 GiB address range and must lie between absolute addresses -2 GiB and +2 GiB. Programs can be statically or dynamically linked. This is the default code model.
  • medany:Generate code for the medium-any code model. The program and its statically defined symbols must be within any single 2 GiB address range. Programs can be statically or dynamically linked.
    The code generated by the medium-any code model is position-independent, but is not guaranteed to function correctly when linked into position-independent executables or libraries.

medlow相当于可以访问-2G ~ +2G的空间, 对于RV32, 相当于完整的虚拟地址空间, 对于RV64相当于可以访问最高的2G和最低的2G空间. 具体来说, gcc会生成基于lui指令的寻址方法.
medany相当于可以访问PC-2G ~ PC+2G的空间, 对于RV32, 相当于还是还是完整的虚拟地址空间, 对于RV64则是跟着PC的位置动态变化的. 具体来说, gcc会生成基于auipc指令的寻址方法. 另外medany会生成地址无关的代码. 这个也是RISC-V linux目前实现的无运行时代码修改实现relocation的基础. 简单来说, medany会然gcc产生pic代码, 并且这个pic代码不依赖rel段和dyn段. 因此不用做运行时代码修改就可以实现直接运行在不同的地址空间之上了. 当然这会限制寻址范围, 但是基于基于PC +-2G的空间已经足够linux启动做relocation了.

Page based cacheable控制

RISC-V的一个特点是: cacheable, atomic, ordering, idempoteny这些特性都是通过PMA和物理地址进行绑定的, 这个和目前比较常规的通过页表绑定到虚拟地址的方式是不一样的, 这样设计的优点是简化了控制, 在设计之初就确定好各个地址段的属性, 系统中所有的observer都按照这个属性去配置就可以了. 缺点是要求SoC自身要做到coherent, 这个是有硬件的开销的, 比如通过MOESI协议去实现. 如果系统没有做到coherent, 那么在RISC-V的这样的体系下, 要想动态申请一段uncached空间来和non-coherent device保持coherent就无法做到了. 可能我们只能预留一段memory, 将其配置为uncached来使用. 这样的话这块memory大小很难控制, 容易产生内存不足或浪费的问题.

在这样的情况下, RISC-V后面发布了Svpbmt(Page-Based Memory Types)扩展, 在RV64上, 将PTE的[62:61]这两个bit用来控制特定Page上的属性, 可以配置的值为:

  • 0 None
  • 1 Non-cacheable, idempotent, weakly-ordered (RVWMO), main memory
  • 2 Non-cacheable, non-idempotent, strongly-ordered (I/O ordering), I/O
  • 3 Reserved for future standard use

这样在RV64上就可以实现通过Page/虚拟地址来控制某块地址空间的属性了.

但是RV32上就没有这样的好运了. 目前RV32的PTE一共32bit已经全部都花光了, 没有地方来添加这个扩展了. 不知道后续是否会有新的扩展来帮助实现这个功能.

目前work around的方案有:

  1. 将物理地址空间拆成两部分, 比如高物理地址区域作为cached区域, 低物理地址空间作为uncached预取, 这样这两块空间只有一个bit会不同, 我们可以把这个bit对应到上面的1, 2这两种case来间接实现这种方案.
  2. 还有一种需要实现上来实现的hack, 因为RV32本来有34bit的物理地址寻址空间, 我们在硬件上可以实现为只做32bit的寻址空间, 剩下2个bit用来实现上面pbmt的功能

方案1是符合标准的, 是一个软方案, 但是需要魔改linux实现, 并且可以寻址的物理地址空间相当于要变少了. 方案2是不符合标准的, 也需要魔改linux实现. 总的来说在RV32上想实现pbmt都是不通用的.

Non-coherent memory支持

前面讲了pbmt, 现在再讲到non-coherent memory支持, 如果要支持non-coherent memory, 也就是在non-coherent的system上, 我们的device和cpu访问同一块空间, 这块空间对于CPU来讲是cached, 对于device来说是uncached. 这样这块地址空间就存在cache同步的问题, 需要进行cache的clean/invalid/flush. 最开始RISC-V并没有在spec上对cached的操作进行规范, 因为spec基本上就是定在系统级实现了coherent的基础上. 正常是不需要cache操作的. 随着发展, 大家发现non-coherent system还是很有存在的必要(省面积, 功耗等). 所以发展出了pbmt用于控制page级的cache策略, 发展出了Zicbom, Zicbop, Zicboz扩展用于实现cache的操作. 其中Zicbom就是对应到我们这一节, cbom的意思是(Cache block operation management), 提供了三个指令cbo.flush, cbo.clean, cbo.inval. 根据spec这三个指令作用在effective address上, 看起来是在U/S mode上就是虚拟地址了, 在M mode上就是物理地址了. 如果特定的SoC没有实现这个扩展, 那么要实现non-coherent memory也需要魔改linux, 比如通过sbi去控制等.

结语

前面我们讲解的RISC-V和linux相关的一些特性的分析, 我们可以看到RISC-V正在发展的特点, 当然也是不够成熟的特点, 后续慢慢发展应该就能满足更多复杂的需求了. 下一篇文章, 我们将从代码出发来研究实际的启动过程.