网站首页 > 文章精选 正文
本期内容如下:
- GCC内联汇编简述
- GCC内联汇编“输出操作数”和“输入操作数”部分
- GCC内联汇编“可能影响的寄存器或存储器”部分
- GCC内联汇编参考实例一
- GCC内联汇编参考实例二
- 在汇编中调用C/C++函数
一、GCC内联汇编简述
由于本文介绍的是GCC的RISC-V工具链,因此在C/C++程序中嵌入汇编程序遵循GCC内联汇编(inline asm )语法规则,其格式由如下部分组成:
asm volatile {
汇编指令列表
:输出操作 //非必要
:输入操作 //非必要
:可能影响的寄存器或存储器 //非必要
};
各组成部分简述如下:
注意:也可以使用前后各带两个下划线的asm__,_asm_是GCC 关键字asm 的宏定义。
注意:也可以使用_volatile_,__volatile是GCC 关键字volatile 的宏定义。
注意:“汇编指令列表”中的编写语法和普通的汇编程序编写一样,可以在其中定义标签(Label)、定义对齐(.align n )、定义段(.section name )等。
有关“输出操作数”部分的详细介绍,本文后续内容详细介绍。
有关“输入操作数”部分的详细介绍,本文后续内容详细介绍。
有关“可能影响的寄存器或存储器”部分的详细介绍,本文后续内容详细介绍。
综上,一个典型的完整内联RISC-V汇编程序格式如下:
int add(int val1,int val2)
{
int sum=0;
_asm_ _volatile_ {
"lui %[para1], 0x33331111\n"
"lui %[para2],0x22223333\n"
"add %[para3],%[para2],%[para1]\n"
:[para3]"=r"(sum)
:[para1]"=r"(val1),[para2]"=r"(val2)
:"a0","a1","a2"
}
return sum;
}
int main(int argc,char **argv)
{
int sum=0,add1=22,add2=100;
sum = add(add1,add2);
return 0;
}
二、 GCC内联汇编“输出操作数”和“输入操作数”部分
由于C/C++中使用的是抽象层次较高的变量或者表达式,如下所示:
sum = add1+add2;
而汇编指令中直接操作的是寄存器,以RISC-V指令集为例,一个加法指令的汇编指令如下:
add a0,a1,a2
那么,当在C/C++程序中添加了汇编程序之时,程序员如何将其所需要操作的C/C++变量与汇编指令的操作数对应起来呢?那就需要使用到GCC内联汇编的“输出操作数”和“输入操作数”部分来指定。
GCC内联汇编语法的“输入操作数”和“输出操作数”部分用来指定当前内联汇编程序的输入和输出操作符列表。其遵循如下语法格式:
每一个输入或者输出操作符都由3部分组成,分别为:
:[ 汇编操作数字符名 ]"=rm"(C变量或表达式),[ 汇编操作数字符名 ]"=rm"(C变量或表达式)
(1)方括号[]中的符号名,用于将内联汇编指令列表中使用的操作数(由%[字符]指定)和此操作符(由[字符]指定)通过同名“字符”绑定起来。
在汇编指令列表中除了使用“%[字符]”中明确的符号命名指定之外,还可以使用“%数字”的方式进行隐含指定。“数字”从0开始,依次表示输出操作数和输入操作数。譬如:假设包含“输出操作数”列表中有2个操作数,“输入操作数”列表中有2个操作数,则汇编程序中%0表示第一个输出操作数,%1表示第二个输出操作数,%2表示第一个输入操作数,%3表示第二个输入操作数。
(2)引号中的限制字符串,用于约束此操作数变量的属性,常用的约束如:
字母“r”代表使用编译器自动分配的寄存器来存储该操作数变量;字母“m”代表使用内存地址来存储该操作数变量。如果同时指明“rm”则编译器自动选择最优方案。
对于“输出操作数”而言,等号“=”代表输出变量用作输出,原来的值会被新值替换;加号“+”代表输出变量不仅作为输出,还作为输入。
注意:此约束对不适用于“输入操作数”。
(3)圆括号()中的内容为C/C++变量或者表达式。
输出操作符之间需要使用逗号分割。
三、GCC内联汇编“可能影响的寄存器或存储器”部分
如果内联汇编中的某个指令会更新某些寄存器的值,则必须在asm中第三个冒号后的“可能影响的寄存器或存储器”中显示的指定出这些寄存器,从而通知GCC编译器让其不再假定之前存入这些寄存器中的值依然合法。指定出这些寄存器由逗号分隔开,每个寄存器由引号包含住,如下所示:
:"a0","a1","a2"
注意:对于那些已经由“输入操作数”和“输出操作数”部分约束指定了的变量,由于编译器自动分配寄存器,因此编译器知道哪些寄存器会被更新,所有程序员无需担心这部分寄存器,不用在“可能影响的寄存器或存储器”进行显示的指定。
如果内联汇编中的某个指令会以无法预料的形式修改了存储器中的值,则必须在asm中第三个冒号后的“可能影响的寄存器或存储器”中显示的加上“memory”,从而通知GCC编译器不要将存储器中的值暂存在处理器的通用寄存器中。
四、 GCC内联汇编参考实例一
以下描述的“add”汇编和c程序给出了一个完整的实例,代码如下:
int main(int argc,char **argv)
{
int sum=0,add1=22,add2=100;
_asm_ _volatile_ {
"lui %[para1], 0x33331111\n"
"lui %[para2],0x22223333\n"
"add %[para3],%[para2],%[para1]\n"
:[para3]"=r"(sum)
:[para1]"=r"(add1),[para2]"=r"(add2)
:"t0","t1","t2"
}
return 0;
}
从上述示例可以看出,通过使用“输出操作数”和“输入操作数”部分的指定,可以将C/C++中的变量或者表达式映射到汇编指令中充当操作数进行操作。在此过程中,程序员无需关心真正执行的汇编指令具体使用的寄存器索引是什么(譬如到底是t1,还是t2等等),编译器会根据引号中指定的操作数约束按照编译优化的原则来分配合理的寄存器索引号。因此,程序员仅仅需要关心操作数和变量的映射,无需关心操作数会映射到处理器具体的哪个通用寄存器,使得软件程序员能够从底层硬件的细节中被解放出来。
五、GCC内联汇编参考实例二
RISC-V架构中定义的CSR寄存器由于需要使用特殊的CSR指令进行访问,如果在C/C++程序中需要使用CSR寄存器,只能够采用内嵌汇编(CSR指令)的方式才能够对CSR寄存器进行操作。以下是在C语言中调用RISC-V的CSR读或者写汇编指令访问CSR寄存器的一个实例,代码如下:
unsigned int Read_CSR_MSTATUS()
{
unsigned int res=0;
_asm_ volatile{
"csrr %[re_val],mstatus\n"
:[re_val]"=r"(res)
:"a0"
}
return res;
}
GCC内联汇编语法的规则比较复杂,信息量很大。本文由于限于篇幅,仅对其最基本的语法和示例进行介绍,以帮助读者能够看懂并且编写简单的C/C++内联汇编程序。感兴趣的读者可以自行查阅完整的GNU C/C++内联汇编语法手册了解更多详情。
六、在汇编中调用C/C++函数
除了在C/C++程序中内嵌汇编程序之外,还可以在汇编程序中调用C/C++函数。这种情形在实际的工程中使用也很常见,由于C/C++语言构造的函数非常普遍,在某些以汇编程序为主体的程序中也会调用C/C++的函数。
在介绍C/C++函数调用之前,需要先介绍应用程序二进制接口(Abstract Binary Interface,ABI),ABI描述了应用程序和操作系统之间,应用和它的库之间,或者应用的组成部分之间的接口。ABI涵盖了各种细节,如:
其中,函数调用约定决定了函数调用时参数传递和函数返回结果的规则,有关RISC-V架构ABI的函数调用约定。
对于RISC-V汇编程序而言,在汇编程序中调用C/C++语言函数,必须遵照ABI所定义的函数调用规则,即,函数参数由寄存器a0-a7所传递,函数返回由寄存器a0-a1所指定,一个具体的示例代码如下:
//C语言代码
//-----------------------------------------
int add(int val1,int val2) //ABI 传参标准,val1存入a0,val2存入a1
{
int sum=0;
sum=val1+val2;
return sum; //ABI返回值被存入a0
}
//汇编代码
//-----------------------------------------
.section main_loop .text
main_loop:
li a0,9990 //加数1
li a1,1234 //加数2
//传递参数给函数接口遵循ABI规范
call add
mv s0,a0 //将计算结果也就是add函数的返回值保存到s0中
汇编语言由于是一种低级语言,因此抽象层次较低,程序编写难度较大,在实际的工作中,更多的情形是能够阅读理解某些现有的汇编代码,或者编写比较简单的汇编程序。
由于本文介绍的RISC-V工具链基于的是GCC工具链,因此RISC-V汇编程序也遵循GNU汇编语法规则,完整的GNU汇编语法手册长达数百页,介绍了大量的伪操作和语法,但是大多数的语法并不常用。本文由于限于篇幅,仅对RISC-V汇编常用的语法进行简要介绍,以帮助读者初步认识RISC-V汇编语言程序、能够看懂并且编写简单而基本的汇编程序。如果对于RISC-V汇编编程进阶感兴趣的读者可以自行查阅RISC-V汇编语言的完整的GNU汇编语法手册了解更多详情。
- 上一篇: 世界上最著名的操作系统是用什么语言编写的?
- 下一篇: 什么是C语言?
猜你喜欢
- 2025-01-02 什么是C语言?
- 2025-01-02 世界上最著名的操作系统是用什么语言编写的?
- 2025-01-02 支持PLC编程的5大电气语言
- 2025-01-02 支持PLC编程的5大电气语言,全部会用的才是大神!
- 2025-01-02 知识地图—编程语言相互影响的关联图
- 2025-01-02 简单说说什么是MLIR
- 2025-01-02 学指令集(汇编语言)的重点是寻址方式(一)
- 2025-01-02 零基础五步掌握,PLC学习方法
- 2025-01-02 CPU眼里的:汇编语言
- 2025-01-02 编译和解释的区别是什么
- 最近发表
- 标签列表
-
- newcoder (56)
- 字符串的长度是指 (45)
- drawcontours()参数说明 (60)
- unsignedshortint (59)
- postman并发请求 (47)
- python列表删除 (50)
- 左程云什么水平 (56)
- 计算机网络的拓扑结构是指() (45)
- 稳压管的稳压区是工作在什么区 (45)
- 编程题 (64)
- postgresql默认端口 (66)
- 数据库的概念模型独立于 (48)
- 产生系统死锁的原因可能是由于 (51)
- 数据库中只存放视图的 (62)
- 在vi中退出不保存的命令是 (53)
- 哪个命令可以将普通用户转换成超级用户 (49)
- noscript标签的作用 (48)
- 联合利华网申 (49)
- swagger和postman (46)
- 结构化程序设计主要强调 (53)
- 172.1 (57)
- apipostwebsocket (47)
- 唯品会后台 (61)
- 简历助手 (56)
- offshow (61)