在进行Android开发时,一般考虑加速某些算法处理速率时,需要使用NDK进行开发,
为了进一步加速程序执行速率,还可以进行汇编级别的优化。
比如采用 NEON 技术进行代码的优化,以实现模块处理效率的成倍增长。
在C/C++中使用内联汇编的用法如下:
asm(
"ADD R0,R0,#1 \n\t" // 使R0寄存器值增加1,后面加换行符和制表符是为了汇编代码的美观,其中有多条指令时 换行符是必须的
"SUB R0,R0,#1 " // 使R0寄存器的值减少1
);
参考的文章有如下几篇,可能跟我的平台有所不同,所以我都不能完全照搬使用:
http://blog.csdn.net/wuzhong325/article/details/8277703
读了一些网上找的文章,所讲述的内容大体比较零散,我在此做一个整理,方便后来者的学习和使用。
开发Arm程序的时候,大多数时候使用C/C++语言就可以了,但汇编语言在某些情况下能够实现一些C语言无法实现的功能,这时候就要调用一些汇编语言的程序.我们需要大概了解一下在C语言中如何嵌入汇编语言.
1.内嵌汇编语言的语法:
__asm
{
指令[;指令]
......
[指令]
}
2.举例:使能/禁止IRQ中断
__inline void enable_IRQ(void)
{
int tmp;
__asm //嵌入汇编代码
{
MRS tmp,CPSR //读取CPSR的值
BIC tmp,tmp,#0x80 //将IRQ中断禁止位I清零,即允许IRQ中断
MSR CPSR_c,tmp //设置CPSR的值
}
}
__inline void disable_IRQ(void)
{
int tmp;
__asm
{
MRS tmp,CPSR
ORR tmp,tmp,#Ox80
MSR CPSR_c,tmp
}
}
3.举例:字符串复制
void my-strcpy(const char *src,char *dst)
{
int ch;
__asm
{
loop:
#ifndef __thumb
LDRB ch,[src],#1
STRB ch,[dst],#1
#else
LDRB ch,[src]
ADD src,#1
STRB ch,[dst]
ADD dst,#1
#endif
CMP ch,#0
BNE loop
}
}
int main(void)
{
const char *a="Hello world!";
char b[20];
__asm
{
MOV R0,a
MOV R1,b
BL my_strcpy,{R0,R1}
}
return(0);
}
4.内嵌汇编的指令用法:
.操作书: 内嵌的汇编指令中作为操作数的寄存器和常量可以是C表达式.这些表达式可以是char,short或int等类型,而且这些表达式都是作为无符号数进行操作的.若要有符号数,用户需要自己处理与符号有关的操作.编译器将会计算这些表达式的值,并为其分配寄存器.
.物理寄存器:内嵌汇编中使用物理寄存器是有限制的:
_ 不能直接向PC(程序计数器)寄存器中赋值,程序跳转只能通过B或BL指令来实现.
_ 使用物理寄存器的指令中,不要使用过于复杂的C表达式
_ 尽可能少的使用物理寄存器
.常量: 在内嵌汇编指令中,常量前面的"#"可以省略
.标号: C程序中的标号可以被内嵌的汇编指令使用.但是只有指令B可以使用C程序中的标号,而指令BL则不能使用.
.内存单元的分配:所有内存分配均由C编译器完成,分配的内存单元通过变量供内嵌汇编器使用.内嵌汇编器不支持内嵌汇编程序中用于内存分配的伪指令.
5.内嵌汇编注意事项:
.必须小心使用物理寄存器,如R0~R3,IP,LR,CPSR中的标志位,避免发生冲突.
例如:
__asm
{
MOV R0,x
ADD y,R0,x/y
}
改成下面代码会比较妥当:
__asm
{
MOV var,x
ADD y,var,x/y
}
.不要使用寄存器代替变量.
.使用内嵌汇编无需保存和恢复寄存器.事实上,除了CPSR,SPSR寄存器,对物理寄存器先读后写都会引起汇编报错.
.汇编语言中","号作为操作数分隔符.如果有C表达式作为操作数,若表达式中包含有",",则必须使用()将其规约为一个汇编操作数,例如:
__asm
{
ADD x,y,(f(),z) //"f(),z"为带有","的C表达式.
}
6.不同数据类型对应的汇编指令:
unsigned char LDRB/STRB
unsigned short LDRH/STRH
unsigned int LDR/STR
char LDRSB/STRSB
short LDRSH/STRSH
7.访问C程序的变量:
AREA globals,CODE,READONLY
EXPORT asmsubroutine
IMPORT globalvar ;声明的外部变量
asmsubroutine
LDR R1,=blobalval
LDR R0,[R1]
ADD R0,R0,#1
STR R0,[R1]
MOV PC,LR
END
http://blog.csdn.net/wuzhong325/article/details/8277718
一、格式
asm volatile (“asm code”:output:input:changed); //必须以‘;’结尾,不管有多长对C都只是一条语句
asm 内嵌汇编关键字
volatile 告诉编译器不要优化内嵌汇编,如果想优化可以不加
ANSI C规范的关键字: (ANSI C把asm用于其它用途,不能用于内嵌汇编语句,GCC可以)
__asm__
__volatile__ //前面和后面都有两个下划线,它们之间没有空格
如果后面部分没有内容,‘:’可以省略,前面或中间的不能省略‘:’
没有asm code也不可以省略‘“”’,没有changed必须省略‘:’
二、asm code
asm code必须放在一个字符串内,但是字符串中间是不能直接按回车键换行的
可以写成多个字符串,只要字符串之间不加任何符号编译完后就会变成一个字符串
"mov r0,r0\n\t" //指令之间必须要换行,/t可以不加,只是为了在汇编文件中的指令格式对齐
"mov r1,r1\n\t"
"mov r2,r2"
字符串内不是只能放指令,可以放一些标签、变量、循环、宏等等
还可以把内嵌汇编放在C函数外面,用内嵌汇编定义函数、变量、段等汇编有的东东,总之就跟直接在写汇编文件一样
在C函数外面定义内嵌汇编时不能加volatile:output:input:changed
注意:编译器不检查asm code的内容是否合法,直接交给汇编器
三、output(ASM --> C)和input(C --> ASM)
1、 指定输出值
__asm__ __volatile__ (
"asm code"
:“constraint”(variable)
);
// constraint定义variable的存放位置:
r 使用任何可用的通用寄存器
m 使用变量的内存地址
// output修饰符:
+ 可读可写
= 只写
& 该输出操作数不能使用输入部分使用过的寄存器,只能 +& 或 =& 方式使用
2、 指定输入值
__asm__ __volatile__ (
"asm code"
:
:"constraint"(variable / immediate)
);
constraint定义variable / immediate的存放位置:
r 使用任何可用的通用寄存器(变量和立即数都可以)
m 使用变量的内存地址(不能用立即数)
i 使用立即数(不能用变量)
3、 使用占位符
int a = 100,b = 200;
int result;
// 注意:原文中引号是中文的,换行符也打印成了 /n/t,我在此作了替换。
// 下面的代码同样有类似的错误,我也进行了替换
__asm__ __volatile__ (
"mov %0,%3 \n\t" // mov r3,#123 %0代表result,%3代表123(编译器会自动加 # 号)
"ldr r0,%1 \n\t" // ldr r0,[fp, #-12] %1代表 a 的地址
"ldr r1,%2 \n\t" // ldr r1,[fp, #-16] %2代表 b 的地址
"str r0,%2 \n\t" // str r0,[fp, #-16] 因为%1和%2是地址所以只能用ldr或str指令
"str r1,%1 \n\t' // str r1,[fp, #-12] 如果用错指令编译时不会报错,要到汇编时才会
:“=r”(result),“+m”(a),“+m”(b) // out1是%0,out2是%1,...,outN是%N-1
:“i”(123) // in1是%N,in2是%N+1,...
);
4、引用占位符
int num = 100;
__asm__ __volatile__ (
"add %0,%1,#100\n\t"
: "=r"(a)
: "0"(a) //"0"是零,即%0,引用时不可以加 %,只能input引用output,
); //引用是为了更能分清输出输入部分
5、 & 修饰符
int num;
__asm__ __volatile__ ( //mov r3, #123 //编译器自动加的指令
"mov %0,%1\n\t" //mov r3,r3 //输入和输出使用相同的寄存器
: "=r"(num)
: "r"(123)
);
int num;
__asm__ __volatile__ ( //mov r3, #123
"mov %0,%1\n\t" //mov r2,r3 //加了&后输入和输出的寄存器不一样了
: "=&r"(num) //mov r3, r2 //编译器自动加的指令
: "r"(123)
);
四、changed
告诉编译器你修改过的寄存器,编译器会自动把保存这些寄存器值的指令加在内嵌汇编之前,再把恢复寄存器值的指令加在内嵌汇编之后
void test() test:
{ str fp, [sp, #-4]!
__asm__ __volatile__ ( add fp, sp, #0
"mov r4,#123" mov r4,#123
); add sp, fp, #0
} ldmfd sp!, {fp}
bx lr
void test() test:
{ stmfd sp!, {r4, fp}
__asm__ __volatile__ ( add fp, sp, #0
"mov r4,#123" mov r4,#123
: add sp, fp, #0
: ldmfd sp!, {r4, fp}
:"r4" bx lr
);
}
汇编的第 2 行与第 6 行没有保存和恢复 R4(R4是通用寄存器变量必须保护,见APCS),第 10 行与第 14 行有保存和恢复 R4
如果修改了没有在输入或输出中定义的任何内存位置,必须在changed列表里加上“memory”
http://blog.chinaunix.net/uid-20715001-id-1234726.html
在嵌入式系统开发中,目前使用的主要编程语言是C 和汇编,虽然C++已经有相应的编译器,但是现在使用还是比较少的。
在稍大规模的嵌入式程序设计中,大部分的代码都是用C来编写的,主要是因为C语言具有较强的结构性,便于人的理解,并且具有大量的库支持。但对于一写硬件上的操作,很多地方还是要用到汇编语言,例如硬件系统的初始化中的CPU 状态的设定,中断的使能,主频的设定,RAM控制参数等。另外在一些对性能非常敏感的代码块,基于汇编与机器码一一对应的关系,这时不能依靠C编译器的生成代码,而要手工编写汇编,从而达到优化的目的。汇编语言是和CPU的指令集紧密相连的,作为涉及底层的嵌入式系统开发,熟练对应汇编语言的使用也是必须的。
单纯的C或者汇编编程请参考相关的书籍或者手册,这里主要讨论C和汇编的混合编程,包括相互之间的函数调用。下面分四种情况来进行讨论,不涉及C++语言。
一、在C语言中内嵌汇编
在C中内嵌的汇编指令包含大部分的ARM和Thumb指令,不过使用与单纯的汇编程序使用的指令略有不同,存在一些限制,主要有下面几个方面:
a 不能直接向PC 寄存器赋值,程序跳转要使用B或者BL指令;
b 在使用物理寄存器时,不要使用过于复杂的C表达式,避免物理寄存器冲突;
c R12和R13可能被编译器用来存放中间编译结果,计算表达式值时可能把R0-R3、R12及R14用于子程序调用,因此避免直接使用这些物理寄存器;
d 一般不要直接指定物理寄存器;
e 让编译器进行分配内嵌汇编使用的标记是__asm或asm关键字,用法如下:
__asm{instruction [; instruction]}或 asm("instruction[; instruction]")。
下面是一个例子来说明如何在C中内嵌汇编语言:
//C语言文件*.c http://hi.baidu.com/procatlaw/
#include
void my_strcpy(const char *src, char *dest){
char ch;
__asm{
loop:
ldrb ch, [src], #1
strb ch, [dest], #1
cmp ch, #0
bne loop
}
}
int main(){
char *a="forget it and move on!";
char b[64];
my_strcpy(a, b);
printf("original: %s", a);
printf("copyed: %s", b);
return 0;
}
在此例子中C语言和汇编之间的值传递是用C语言的指针来实现的,因为指针对应的是地址,所以汇编中也可以访问。
二、在汇编中使用C定义的全局变量
内嵌汇编不用单独编辑汇编语言文件,比较简洁,但是有很多的限制。当汇编的代码较多时一般放在单独的汇编文件中,这时就需要在汇
编文件和C文件之间进行一些数据的传递,最简便的办法就是使用全局变量。
//C语言文件*.c http://hi.baidu.com/procatlaw/
#include
int gVar=12;
extern asmDouble(void);
int main(){
printf("original value of gVar is: %d", gVar_1);
asmDouble();
printf(" modified value of gVar is: %d", gVar_1);
return 0;
}
下面是一个C语言和汇编语言共享全局变量的例子:
;汇编语言文件*.S http://hi.baidu.com/procatlaw/
AREA asmfile, CODE, READONLY EXPORT asmDouble
IMPORT gVar
asmDouble
ldr r0, =gVar
ldr r1, [r0]
mov r2, #2
mul r3, r1, r2
str r3, [r0]
mov pc, lr
END
在此例中,汇编文件与C文件之间相互传递了全局变量gVar和函数asmDouble,留意声明的关键字extern和IMPORT
三、在C中调用汇编的函数
有一些对机器要求高的敏感函数,通过C语言编写再通过C编译器翻译有时会出现误差,因此这样的函数一般采用汇编语言来编写,然后供C
语言调用。在C文件中调用汇编文件中的函数,要注意的有两点,一是要在C文件中声明所调用的汇编函数原型,并加入extern关键字作为引入
函数的声明;二是在汇编文件中对对应的汇编代码段标识用EXPORT关键字作为导出函数的声明,函数通过mov pc, lr指令返回。这样,就可以
在C文件中使用该函数了。从C语言的角度的角度,并不知道调用的函数的实现是用C语言还是汇编汇编语言,原因C语言的函数名起到表明函数
代码起始地址的作用,而这个作用和汇编语言的代码段标识符是一致的。
下面是一个C语言调用汇编函数例子:
//C语言文件*.c http://hi.baidu.com/procatlaw/
#include
extern void asm_strcpy(const char *src, char *dest);
int main(){
const char *s="seasons in the sun"; char d[32];
asm_strcpy(s, d);
printf("source: %s", s);
printf(" destination: %s",d);
return 0;
}
;汇编语言文件*.S http://hi.baidu.com/procatlaw/
AREA asmfile, CODE, READONLY
EXPORT asm_strcpy
asm_strcpy
loop
ldrb r4, [r0], #1
cmp r4, #0
beq over
strb r4, [r1], #1
b loop
over
mov pc, lr
END
在此例中,C语言和汇编语言之间的参数传递是通过对应的用R0-R3来进行传递,即R0传递第一个参数,R1传递第二个参数,多于4个时借助栈完成,函数的返回值通过R0来传递。这个规定叫作ATPCS(ARM Thumb Procedure Call Standard),具体见ATPCS规范。
四、在汇编中调用C的函数
在汇编语言中调用C语言的函数,需要在汇编中IMPORT对应的C函数名,然后将C的代码放在一个独立的C文件中进行编译,剩下的工作由连接器来处理。
下面是一个汇编语言调用C语言函数例子:
//C语言文件*.c http://hi.baidu.com/procatlaw/
int cFun(int a, int b, int c){
return a+b+c;
}
;汇编语言文件*.S http://hi.baidu.com/procatlaw/
AREA asmfile, CODE, READONLY
EXPORT cFun
start
mov r0, #0x1
mov r1, #0x2
mov r2, #0x3
bl cFun
nop
nop
b start
END
在汇编语言中调用C语言的函数,参数的传递也是按照ATPCS规范来实现的。
在这里简单介绍一下部分ATPCS规范:
子程序间通过寄存器R0~R3来传递参数。
A.在子程序中,使用寄存器R4~R11来保存局部变量。
B.寄存器R12用于子程序间scratch寄存器(用于保存SP,在函数返回时使用该寄存器出桟),记作IP。
C.寄存器R13用于数据栈指针,记作SP。寄存器SP在进入子程序时的值和退出子程序时的值必须相等。
D.寄存器R14称为链接寄存器,记作LR。它用于保存子程序的返回地址。
E.寄存器R15是程序计数器,记作PC
F.参数不超过4个时,可以使用寄存器R0~R3来传递参数,当参数超过4个时,还可以使用数据栈来传递参数。
G.结果为一个32位整数时,可以通过寄存器R0返回
H.结果为一个64位整数时,可以通过寄存器R0和R1返回,依次类推。
以上通过几个简单的例子演示了嵌入式开发中常用的C 和汇编混合编程的一些方法和基本的思路,其实最核心的问题就是如何在C 和汇编之间传值,剩下的问题就是各自用自己的方式来进行处理。以上只是抛砖引玉,更详细和复杂的使用方法要结合实际应用并参考相关的资料。
---------------------
作者:luofl_
来源:CSDN
原文:https://blog.csdn.net/luofl1992/article/details/8756423
版权声明:本文为博主原创文章,转载请附上博文链接!