RISC-V嵌入式开发入门篇2:RISC-V汇编语言程序设计(下)

原文出处:https://mp.weixin.qq.com/s/Ln4qBYvSsgRvdiK1IJqI6Q

随着国内第一本RISC-V中文书籍《手把手教你设计CPU——RISC-V处理器篇》正式上市,越来越多的爱好者开始使用开源的蜂鸟E203 RISC-V处理核,很多初学者留言询问有关RISC-V工具链使用的问题,因此本公众号将开始陆续发表若干篇有关RISC-V软件工具链使用的文章,包括:

在本号之前发表的文章《编译过程简介》中介绍了C/C++语言如何被编译成为汇编语言,而本文将介绍如何直接使用RISC-V架构的汇编语言进行程序设计。

《RISC-V汇编语言程序设计》——分成上中下三篇,本篇是下篇。继续关注公众号可查询上中两篇的内容。

上篇:RISC-V嵌入式开发入门篇2:RISC-V汇编语言程序设计(上)

中篇:RISC-V嵌入式开发入门篇2:RISC-V汇编语言程序设计(中)

注:本文节选自《RISC-V架构与嵌入式开发快速入门》(即将出版)。

1.6.1 GCC内联汇编简述

由于本文介绍的是GCC的RISC-V工具链,因此在C/C++程序中嵌入汇编程序遵循GCC内联汇编(inline asm )语法规则,其格式有如下部分组成:

在这里插入图片描述

分别简述如下:

  • “关键字asm”,为GCC的关键字,表示进行内联汇编操作。

注意:也可以使用前后各带两个下划线的asm__,_asm_是GCC 关键字asm 的宏定义。

  • “关键字volatile”,或者_volatile_。_volatile_或volatile 是可选的,如果添加了该关键字,则要求编译器对后续括号内添加汇编程序不进行任何优化以保持其原状;如果没有添加此关键字,则编译器可能会将某些汇编指令优化掉。

注意:也可以使用_volatile_,__volatile是GCC 关键字volatile 的宏定义。

  • “汇编指令列表”,即需要嵌入的汇编指令,每条指令必须被双引号括起来(作为字符串),两条指令之前必须以“\n”或者“;”作为分隔符,如果没有添加分隔符的两个字符串将会被合并成为一个字符串。

注意:“汇编指令列表”中的编写语法和普通的汇编程序编写一样,可以在其中定义标签(Label)、定义对齐(.align n )、定义段(.section name )等。

  • “输出操作数”,用来指定当前内联汇编程序的输出操作符列表。

有关“输出操作数”部分的详细介绍,请参见第1.6.2节。

  • “输入操作数”,用来指定当前内联汇编语句的输入操作符列表。

有关“输入操作数”部分的详细介绍,请参见第1.6.2节。

  • “可能影响的寄存器或存储器”,用于告知编译器当前内联汇编语句可能会对某些寄存器或内存进行修改,使得编译器在优化时将其因素考虑进去。

有关“可能影响的寄存器或存储器”部分的详细介绍,请参见第1.6.3节。

综上,一个典型的完整内联汇编程序格式如下:

在这里插入图片描述

后续章节将进行进一步详述。

1.6.2 GCC内联汇编“输出操作数”和“输入操作数”部分

由于C/C++中使用的是抽象层次较高的变量或者表达式,如下所示:

在这里插入图片描述

而汇编指令中直接操作的是寄存器,以RISC-V指令集为例,一个加法指令的汇编指令如下:

在这里插入图片描述

那么,当在C/C++程序中添加了汇编程序之时,程序员如何将其所需要操作的C/C++变量与汇编指令的操作数对应起来呢?那就需要使用到GCC内联汇编的“输出操作数”和“输入操作数”部分来指定。

GCC内联汇编语法的“输入操作数”和“输出操作数”部分用来指定当前内联汇编程序的输入和输出操作符列表。其遵循如下语法:

  • 每一个输入或者输出操作符都由3部分组成,分别为:

(1)方括号[]中的符号名,用于将内联汇编程序中使用的操作数(由%[字符]指定)和此操作符(由[字符]指定)通过同名“字符”绑定起来。

— 除了使用“%[字符]”中明确的符号命名指定之外,还可以使用“%数字”的方式进行隐含指定。“数字”从0开始,依次表示输出操作数和输入操作数。譬如:假设包含“输出操作数”列表中有2个操作数,“输入操作数”列表中有2个操作数,则汇编程序中%0表示第一个输出操作数,%1表示第二个输出操作数,%2表示第一个输入操作数,%3表示第二个输入操作数。

(2)引号中的限制字符串,用于约束此操作数变量的属性,常用的约束如:

— 字母“r”代表使用编译器自动分配的寄存器来存储该操作数变量;字母“m”代表使用内存地址来存储该操作数变量。如果同时指明“rm”则编译器自动选择最优方案。

— 对于“输出操作数”而言,等号“=”代表输出变量用作输出,原来的值会被新值替换;加号“+”代表输出变量不仅作为输出,还作为输入。注意:此约束对不适用于“输入操作数”。

(3)圆括号()中的C/C++变量或者表达式。

  • 输出操作符之间需使用逗号分割。

为了便于读者理解上述语法,请读者参见第1.6.4节和1.6.5中的实例。

1.6.3 GCC内联汇编“可能影响的寄存器或存储器”部分

如果内联汇编中的某个指令会更新某些寄存器的值,则必须在asm中第三个冒号后的“可能影响的寄存器或存储器”中显示的指定出这些寄存器,从而通知GCC编译器让其不再假定之前存入这些寄存器中的值依然合法。指定出这些寄存器由逗号分隔开,每个寄存器由引号包含住,如下所示:

在这里插入图片描述

注意:对于那些已经由“输入操作数”和“输出操作数”部分约束指定了的变量,由于编译器自动分配寄存器,因此编译器知道哪些寄存器会被更新,所有程序员无需担心这部分寄存器,不用在“可能影响的寄存器或存储器”进行显示的指定。

如果内联汇编中的某个指令会以无法预料的形式修改了存储器中的值,则必须在asm中第三个冒号后的“可能影响的寄存器或存储器”中显示的加上“memory”,从而通知GCC编译器不要将存储器中的值暂存在处理器的通用寄存器中。

为了便于读者理解上述语法,请读者参见第1.6.4节和1.6.5节中的实例。

1.6.4 GCC内联汇编参考实例一

以下结合第1.6.2节中描述的“add”汇编示例给出一个完整的实例,代码如下:

在这里插入图片描述

从上述示例可以看出,通过使用“输出操作数”和“输入操作数”部分的指定,可以将C/C++中的变量或者表达式映射到汇编指令中充当操作数进行操作。在此过程中,程序员无需关心真正执行的汇编指令具体使用的寄存器索引是什么(譬如到底是x1,还是x2等等),编译器会根据引号中指定的操作数约束按照编译优化的原则来分配合理的寄存器索引号。因此,程序员仅仅需要关心操作数和变量的映射,无需关心操作数会映射到处理器具体的哪个通用寄存器,使得软件程序员能够从底层硬件的细节中被解放出来。

1.6.5 GCC内联汇编参考实例二

RISC-V架构中定义的CSR寄存器由于需要使用特殊的CSR指令进行访问,如果在C/C++程序中需要使用CSR寄存器,只能够采用内嵌汇编(CSR指令)的方式才能够对CSR寄存器进行操作。以下是在C语言中调用RISC-V的CSR读或者写汇编指令访问CSR寄存器的一个实例,代码如下:

在这里插入图片描述

1.6.6 小结

GCC内联汇编语法的规则比较复杂,信息量很大。本文由于限于篇幅,仅对其最基本的语法和示例进行介绍,以帮助读者能够看懂并且编写简单的C/C++内联汇编程序。感兴趣的读者可以自行查阅完整的GNU C/C++内联汇编语法手册了解更多详情。

1.7 在汇编中调用C/C++函数

除了在C/C++程序中内嵌汇编程序之外,还可以在汇编程序中调用C/C++函数。这种情形在实际的工程中使用也很常见,由于C/C++语言构造的函数非常普遍,在某些以汇编程序为主体的程序中也会调用C/C++的函数。

在介绍C/C++函数调用之前,需要先介绍应用程序二进制接口(Abstract Binary Interface,ABI),ABI描述了应用程序和操作系统之间,应用和它的库之间,或者应用的组成部分之间的接口。ABI涵盖了各种细节,如:

  • 数据类型的大小、布局和对齐;

  • 函数调用约定(控制着函数的参数如何传送以及如何接受返回值),例如,是所有的参数都通过栈传递,还是部分参数通过寄存器传递;哪个寄存器用于哪个函数参数;通过栈传递的第一个函数参数是最先push到栈上还是最后;

  • 系统调用的编码和一个应用如何向操作系统进行系统调用;

  • 以及在一个完整的操作系统ABI中,目标文件的二进制格式、程序库等等。

其中,函数调用约定决定了函数调用时参数传递和函数返回结果的规则,有关RISC-V架构ABI的函数调用约定,请参见附录A中的图A-1。

对于RISC-V汇编程序而言,在汇编程序中调用C/C++语言函数,必须遵照ABI所定义的函数调用规则,即,函数参数由寄存器a0-a7所传递,函数返回由寄存器a0-a1所指定,一个具体的示例代码如下:

在这里插入图片描述

1.8 总结

汇编语言由于是一种低级语言,因此抽象层次较低,程序编写难度较大,在实际的工作中,更多的情形是能够阅读理解某些现有的汇编代码,或者编写比较简单的汇编程序。

由于本文介绍的RISC-V工具链基于的是GCC工具链,因此RISC-V汇编程序也遵循GNU汇编语法规则,完整的GNU汇编语法手册长达数百页,介绍了大量的伪操作和语法,但是大多数的语法并不常用。本文由于限于篇幅,仅对RISC-V汇编常用的语法进行简要介绍,以帮助读者初步认识RISC-V汇编语言程序、能够看懂并且编写简单而基本的汇编程序。如果对于RISC-V汇编编程进阶感兴趣的读者可以自行查阅RISC-V汇编语言的完整的GNU汇编语法手册了解更多详情。

《RISC-V汇编语言程序设计》——分成上中下三篇,本篇是下篇。继续关注公众号可查询上中两篇的内容。

上篇:RISC-V嵌入式开发入门篇2:RISC-V汇编语言程序设计(上)

中篇:RISC-V嵌入式开发入门篇2:RISC-V汇编语言程序设计(中)

更多信息

感兴趣的读者可以通过下面二维码关注公众号“硅农亚历山大”,了解Verilog、IC设计、CPU、RISC-V和人工智能AI相关的更多设计技巧和经验分享,注意:由于干货太多,请自备茶水。

在这里插入图片描述