第三章 RISC-V汇编语言

3.2 函数调用规范

图3.2

3.3 汇编器

这类指令在巧妙配置常规指令的基础上实现,称为伪指令。图 3.3和 3.4列出了 RISC-V伪指令。

汇编程序的开头是一些汇编指示符,它们是汇编器的命令。图 3.9是RISC-V的汇编指示符。其中图 3.6中用到的指示符有:

  • .text:进入代码段。
  • .align 2:后续代码按2^2字节对齐。
  • .globl main:声明全局符号 “main”
  • .section .rodata:进入只读数据段
  • .balign 4:数据段按4字节对齐
  • .string “Hello, %s!\n": 创建空字符结尾的字符串
  • .string “world": 创建空字符结尾的字符串

图3.3

图3.4

图3.6

3.4 链接器

图3.9

图3.10

3.5 静态链接和动态链接

上一节对 静态链接进行了说明,在程序运行前所有的库都进行了链接和加载。如果这样的库很大,链接一个库到多个程序中会十分占用内存。另外,链接时库是绑定的,即使它们后来的更新修复了 bug,强制的静态链接的代码仍然会使用旧的、有bug的版本。

为了解决这两个问题,现在的许多系统使用动态链接,外部的函数在第一次被调用时才会加载和链接。后续所有调用都使用快速链接,因此只会产生一次动态开销。每次程序开始运行,它都会按照需要链接最新版本的库函数。另外,如果多个程序使用了同一个动态链接库,库代码在内存中只会加载一次。

编译器产生的代码和静态链接的代码很相似。其不同之处在于,跳转的目标不是实际的函数,而是一个只有三条指令的存根函数( stub function)。存根函数会从内存中的一个表中加载实际的函数的地址并跳转。不过,在第一次调用时,表中还没有实际的函数的地址,只有一个动态链接的过程的地址。当这个动态链接过程被调用时,动态链接器通过符号表找到实际要调用的函数,复制到内存中,更新记录实际的函数地址的表。后续的每次调用的开销就是存根函数的三条指令的开销。

第四章 乘法和除法指令

RV32M向RV32I中添加了整数乘法和除法指令。图4.1是RV32M扩展指令集的图形表示,图4.2列出了它们的操作码。

RV32M具有有符号和无符号整数的除法指令:divide(div)和divide unsigned(divu),它们将商放入目标寄存器。在少数情况下,程序员需要余数而不是商,因此RV32M提供remainder(rem)和remainder unsigned(remu),它们在目标寄存器写入余数,而不是商。

图4.1

图4.2

将两个32位数相乘得到的是64位的乘积。为了正确地得到一个有符号或无符号的64位积,RISC-V中带有四个乘 法指令。要得到整数要得到整数32位乘积( 64位中的低 32位)就用 mul指令。要得到高32位,如果操作数都是有符号数,就用mulh指令;如果操作数都是无符号数,就用mulhu指令。指令;如果一个有符号一个无符号,可以用mulhsu指令。在一条指令中完成把64位积写入两个 32位寄存器的操作会使硬件设计变得复杂,所以RV32M需要两条乘法指令才能得到一个完整的64位积。

对许多微处理器来说,整数除法是相对较慢的操作。如前述,除数为2的幂次的无符号除法可以用右移来代替。事实证明,通过乘以近似倒数再修正积的高32位的方法,可以优化除数为其它数的除法。例如,图 4.3显示了 3为除数的无符号除法的代码。

图4.3

{% note danger %} lui t0, 0xaaaab 为什么t0=0xaaaaaaab而不是0xaaaab000 {% endnote %}