有人预言,RISC-V或将是继Intel和Arm之后的第三大主流处理器体系。欢迎访问全球首家只专注于RISC-V单片机行业应用的中文网站
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
本帖最后由 小飞飞 于 2020-8-21 02:46 编辑
随着国内第一本RISC-V中文书籍《手把手教你设计CPU——RISC-V处理器篇》正式上市,越来越多的爱好者开始使用开源的蜂鸟E203 RISC-V处理核,很多初学者留言询问有关RISC-V工具链使用的问题,因此本公众号将开始陆续发表若干篇有关RISC-V软件工具链使用的文章,包括: 本文为RISC-V嵌入式开发准备篇1:编译过程简介。本文的目的是对编译过程进行简单的科普与回顾,为后续详细介绍“RISC-V GCC工具链”和“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内联汇编语法的“输入操作数”和“输出操作数”部分用来指定当前内联汇编程序的输入和输出操作符列表。其遵循如下语法:
(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等等),编译器会根据引号中指定的操作数约束按照编译优化的原则来分配合理的寄存器索引号。
因此,程序员仅仅需要关心操作数和变量的映射,无需关心操作数会映射到处理器具体的哪个通用寄存器,使得软件程序员能够从底层硬件的细节中被解放出来。
|