http://blog.csdn.net/ce123/article/details/8209702 这篇文章比较不错,排版看起来很舒服。
在进行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 { 指令[;指令] ...... [指令] }
__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 } }
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); }
.标号: C程序中的标号可以被内嵌的汇编指令使用.但是只有指令B可以使用C程序中的标号,而指令BL则不能使用.
.内存单元的分配:所有内存分配均由C编译器完成,分配的内存单元通过变量供内嵌汇编器使用.内嵌汇编器不支持内嵌汇编程序中用于内存分配的伪指令.
5.内嵌汇编注意事项:
__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/STRSH7.访问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
"mov r0,r0\n\t" //指令之间必须要换行,/t可以不加,只是为了在汇编文件中的指令格式对齐 "mov r1,r1\n\t" "mov r2,r2"
__asm__ __volatile__ ( "asm code" :“constraint”(variable) ); // constraint定义variable的存放位置: r 使用任何可用的通用寄存器 m 使用变量的内存地址 // output修饰符: + 可读可写 = 只写 & 该输出操作数不能使用输入部分使用过的寄存器,只能 +& 或 =& 方式使用
__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) );
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
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 <stdio.h> 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 <stdio.h> 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中调用汇编的函数
有一些对机器要求高的敏感函数,通过C语言编写再通过C编译器翻译有时会出现误差,因此这样的函数一般采用汇编语言来编写,然后供C
语言调用。在C文件中调用汇编文件中的函数,要注意的有两点,一是要在C文件中声明所调用的汇编函数原型,并加入extern关键字作为引入
函数的声明;二是在汇编文件中对对应的汇编代码段标识用EXPORT关键字作为导出函数的声明,函数通过mov pc, lr指令返回。这样,就可以
在C文件中使用该函数了。从C语言的角度的角度,并不知道调用的函数的实现是用C语言还是汇编汇编语言,原因C语言的函数名起到表明函数
代码起始地址的作用,而这个作用和汇编语言的代码段标识符是一致的。
下面是一个C语言调用汇编函数例子:
//C语言文件*.c http://hi.baidu.com/procatlaw/ #include <stdio.h> 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的函数
在汇编语言中调用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
在这里简单介绍一下部分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返回,依次类推。