2.6  混合语言编程
目前,在嵌入式系统开发中,使用的主要编程语言是C语言和汇编语言。在稍大规模的嵌入式软件中,例如含有OS,大部分的代码都是用C编写的,主要是因为C语言的结构比较好,便于人的理解,而且有大量的支持库。尽管如此,很多地方还是要用到汇编语言,例如开机时硬件系统的初始化,包括CPU状态的设定、中断的使能、主频的设定、RAM的初始化等,一些中断处理也可能涉及汇编。另外,对性能非常敏感的代码,需要手工编写汇编语言程序。汇编语言是和CPU的指令集紧密相关的。作为涉及底层的嵌入式系统开发,必须熟练对汇编语言的使用。使用C语言和汇编语言进行混合编程,可以充分发挥各自的优势,取得显著效果。
在C中嵌入汇编的格式为:
asm("汇编语句"
:输出寄存器
:输入寄存器
:会被修改的寄存器);
其中:
汇编语句:是写汇编指令的地方;
输出寄存器:表示当这段嵌入汇编执行后,用于存放输出数据的寄存器。这些寄存器会分别对应一个C语言表达式或一个内存地址;
输入寄存器:表示在开始执行汇编指令时,指定一些寄存器中应存放输入值,它们也分别对应着一个C变量或常数值。
会被修改的寄存器:在gcc知道会被修改的寄存器后,能够对代码进行优化。
示例:
01    #define get_seg_byte(seg, addr) \
02    ({ \
03    register char __res;
04    __asm__("push %%fs; \
05            mov %%ax, %%fs; \
06            movb %%fs:%2, %%al; \
07            pop %%fs" \
08            :"=a"(__res) \
09            :""(seg), "m"(*(addr))); \
10    __res;})
这段10行的代码定义了一个嵌入汇编语言宏函数。用圆括号括住的组合语句(花括号中的语句)可以作为表达式使用,第10行变量__res是该表达式的输出值。
宏语句要在一行上定义,因此使用“\”将这些语句连成一行。
宏的名字是get_seg_byte(seg, addr)。
第3行定义寄存器变量__res。
第4行的__asm__表示嵌入汇编语句的开始。4-7行是AT&T格式的汇编语句。
第8行是输出寄存器,其含义是此段代码结束后将eax所代表的寄存器的值放入__res变量中,作为本函数的输出值,"=a"中的"a"称为加载代码,"="表示这是输出寄存器。
第9行表示此段代码开始运行时将seg放到eax寄存器中,""表示使用与上面同个位置的输出相同的寄存器。"m"表示使用一个内存偏移地址值。为了在上面的汇编语句中使用该地址值,嵌入汇编程序规定把输出和输入寄存器统一按顺序编号,从输出寄存器序列开始,从左到右,从上到下,以"%0"开始,分别记为%0、%1、…、%9。因此,输出寄存器的编号是%
0(这里只有一个输出寄存器),输入寄存器前一部分(""(seg))的编号是%1,而后部的编号是%2。上面第6行上的%2即代表(*(addr))这个内存偏移量。
第4行代码的作用是将fs段寄存器的内容入栈;
第5行将eax中的段值赋给fs段寄存器;
第6行是把fs:(*(addr))所指定的字节放入al寄存器中。当执行完汇编语句后,输出寄存器eax的值将被放入__res,作为该宏函数的返回值。
这段程序中,seg代表一个指定的内存段值,而addr表示一个内存偏移地址量。该宏函数的功能是从指定段和偏移值的内存地址处取一个字节。
一些可能会用到的寄存器加载代码及其具体的含义见表2.19。
表2.19  寄存器加载代码及其具体的含义
汇编语言转c语言的软件
代码
含义
代码
含义
a
使用寄存器eax
m
使用内存地址
b
使用寄存器ebx
o
使用内存地址并可以加偏移量
c
使用寄存器ecx
I
使用常数0~31
d
使用寄存器edx
J
使用常数0~63
S
使用esi
K
使用常数0~255
D
使用edi
L
使用常数0~65535
q
使用动态字节可寻址寄存器(eaxebxecxedx) 
M
使用常数0~3
r
使用任意动态分配的寄存器
N
使用1字节常数(0~255) 
g
使用通用有效的地址即可(eaxebxecxedx或内存变量
O
使用常数0~31
A
使用eaxedx联合(64
下面主要介绍基于ARM的C语言与汇编语言的混合编程。
1.C语言与汇编语言混合编程应遵守的规则
ARM编程中使用的C语言是标准C语言。在使用C语言时,要用到和汇编语言的混合编程。若汇编代码较为简洁,则可使用直接内嵌汇编的方法;否则要将汇编程序以文件的形式加入到项目中,按照ATPCS(ARM/Thumb Procedure Call Standard,ARM/Thumb过程调用标准)的规定与C程序相互调用与访问。
在C和ARM汇编语言程序之间相互调用时,必须遵守ATPCS规则。ATPCS规定了一些子程序间调用的基本规则、寄存器的使用规则、堆栈的使用规则以及参数的传递规则等。
(1)寄存器的使用规则
子程序之间通过寄存器R0~R3来传递参数,当参数个数多于4个时,使用堆栈来传递参数。此时R0~R3可记作A1~A4。
在子程序中,使用寄存器R4~R11保存局部变量。因此当进行子程序调用时要注意对这些寄存器的保存和恢复。此时R4~R11可记作V1~V8。
寄存器R12用于保存堆栈指针SP,当子程序返回时使用该寄存器出栈,记作IP。
寄存器R13用作堆栈指针,记作SP。
寄存器R14称为链接寄存器,记作LR,该寄存器用于保存子程序的返回地址。
寄存器R15称为程序计数器,记作PC。
(2)堆栈的使用规则
ATPCS规定堆栈采用满递减类型(FD,Full Descending),且对堆栈的操作是8字节对齐。使用STMFD/LDMFD指令。
(3)参数的传递规则
整数参数的前4个使用R0~R3传递,其它参数使用堆栈传递;浮点参数使用编号最小且能够满足需要的一组连续的FP寄存器传递参数。
子程序的返回结果为一个32位整数时,通过R0返回;返回结果为一个64位整数时,通过R0和R1返回;依此类推。结果为浮点数时,通过浮点运算部件的寄存器F0、D0或者S0返回。
2.在汇编语言程序中调用C函数的方法
汇编语言程序的书写要遵循ATPCS规则,以保证程序调用时参数能够正确传递。在汇编语言程序中调用C函数的方法是:
首先,将C代码在一个独立的C源程序文件中译;
然后,在汇编程序中使用IMPORT伪指令声明将要调用的C语言函数;
接着,通过BL指令调用C函数。
示例:在一个C源文件中定义了如下求和函数
int add(int x,int y){
return(x+y);
}
调用add()函数的汇编语言程序结构如下:
IMPORT add        ;声明要调用的C函数
……
MOV R0,1
MOV R1,2
BL add        ;调用C函数add
……
进行函数调用时,使用R0和R1实现参数的传递,返回结果由R0带回。函数调用结束后,R0的值为3。
示例:
;the details of parameters transfer comes from ATPCS        //汇编语言程序文件
;if there are more than 4 args, stack will be used
      EXPORT asmfile
      AREA asmfile, CODE, READONLY
      IMPORT  cFun
      ENTRY
      mov  R0, #11
      mov  R1, #22
      mov  R2, #33
      BL  sum
      END
/*C file, called by asmfile */                    //C源文件
int sum(int a, int b, int c)
{
return a+b+c;
}
3.C语言程序调用汇编子程序(函数)的方法
C语言程序调用汇编子程序时,汇编子程序的书写也要遵循ATPCS规则,以保证程序调用时参数正确的传递。在C语言程序中调用汇编子程序的方法为:
首先,在汇编语言程序中使用EXPORT伪指令声明被调用的子程序,表示该子程序将在其它文件中被调用;
然后,在C语言程序中使用extern关键字,声明要调用的汇编子程序为外部函数;
接着,就可以在C语言程序中调用该子程序了。
注意:从C的角度来看,其并不知道该函数是用C还是汇编实现的。因为C的函数名用来表示函数代码的起始地址,这个和汇编中的label是一样的。
示例:在一个汇编语言源文件中定义了求和函数
EXPORT add        ;声明add子程序将被外部函数调用
……
add                ;求和子程序add
ADD R0,R0,R1
MOV pc,lr
……
在一个C语言程序的main()函数中,对add汇编子程序进行了调用。
extern int add (int x,int y);        //声明add为外部函数
int main(){
int a=1,b=2,c;
c=add(a,b);            //调用add子程序
……
}
当main()函数调用add汇编子程序时,变量a、b的值分别赋给R0和R1,返回结果由R0带回,并赋值给变量c。函数调用结束后,变量c的值为3。
示例:
/* cfile.c                                //C源文件
* in C,call an asm function, asm_strcpy
*      Sep 9, 2004
*/
#include <stdio.h>
extern void asm_strcpy(const char *src, char *dest);        //声明asm_strcpy为外部函数
int main()
{
      const        char *s = "seasons in the sun";
      char        d[32];
      asm_strcpy(s, d);                        //调用asm_strcpy子程序
      printf("source: %s", s);
      printf("      destination: %s",d);
      return 0;
}
;asm function implementation                    //汇编语言程序文件
      AREA asmfile, CODE, READONLY
      EXPORT asm_strcpy
asm_strcpy
loop
      ldrb  r4, [r0], #1      ;address increment after read
      cmp  r4, #0
      beq  over
      strb  r4, [r1], #1
      b  loop
over
      mov  pc, lr
      END
4.C语言程序中内嵌汇编语句

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。