3.2 ARM 编程技巧
3.2.1 ATPCS 介绍
为了½单独编译的
C
语言和汇编语言之间½够相互调用,必须为子程序间的调用规定一定
的规则。ATPCS 就是
ARM
程序和
Thumb
程序中子程序调用的基本规则。
ATPCS 概述
ATPCS
规定了一些子程序间调用的基本规则。这些基本规则包括子程序调用过程中寄
存器的½用规则,数据栈的½用规则,参数的传递规则。
有调用关系的所有子程序必须遵守同一种
ATPCS。编译器或汇编器在 ELF
格式的目标
文件中设½相应的属性,标识用户选定的
ATPCS
类型。对应于不同类型的
ATPCS
规则,有
相应的
C
语言库,链接器根据用户指定的
ATPCS
类型连接相应的
C
语言库。
½用
ADS
的
C
语言编译器编译的
C
语言子程序满足用户指定的
ATPCS
类型。而对于
汇编语言程序来说,完全要依赖用户来保证各子程序满足选定的
ATPCS
类型。具½来说,
汇编语言子程序必须满足下面的
3
个条件:
在子程序编写时必须遵守相应的
ATPCS
规则。
数据栈的½用要遵守相应的
ATPCS
规则。
在汇编编译器中½用-apcs 选项。
下面介绍基本
ATPCS。基本 ATPCS
规定了在子程序调用时的一些基本规则,包括下面
3
方面的内容:
各寄存器的½用规则及其相应的名称。
数据栈的½用规则。
参数传递的规则。
寄存器的½用规则
寄存器的½用必须满足下面的规则。
子程序间通过寄存器
R0~R3
来传递参数。这时,寄存器
R0~R3
可以记½
A0~A3。被调
用的子程序在返回前无需恢复寄存器
R0~R3
的内容。
在子程序中,½用寄存器
R4~R11
来保存局部变量。这时,寄存器
R4~R11
可以记½
V1~V8。如果在子程序中½用到了寄存器 V1~V8
中的某些寄存器,子程序进入时必须
保存这些寄存器的值,
在返回前必须恢复这些寄存器的值;
对于子程序中没有用到的寄
存器则不必进行这些操½。在
Thumb
程序中,通常只½½用寄存器
R4~R7
来保存局部
变量。
寄存器
R12
用½子程序间的
scratch
寄存器,记½
ip。在子程序间的连接代码段中常有
- 87 -
这种½用规则。
寄存器
R13
用½数据栈指针,记½
sp。在子程序中寄存器 R13
不½用½其他用途,寄
存器
sp
在进入子程序的值和退出子程序的值必须相等。
寄存器
R14
称为连接寄存器,记½
lr。它用½保存子程序的返回地址。如果在子程序中
保存了返回地址,寄存器
R14
则可以用½其他用途。
寄存器]R15 是程序计数器,记½
PC。它不½用½其他的用途。
下表
3-2-1
总结了在
ATPCS
中各寄存器的½用规则及其名称。
这些名称在
ARM
编译器和汇
编器中½是预定义的。
表
3-2-1
寄存器的½用规则
寄存器
R15
R14
R13
R12
R11
R10
R9
R8
R7
R6
R5
R4
R3
R2
R1
R0
V8
V7
V6
V5
V4
V3
V2
V1
A4
A3
A2
A1
Wr
Sl
Sb
别名
特殊名称
pc
Lr
Sp
Ip
½用规则
程序计数器
连接寄存器
数据栈指针
子程序内部调用的
scratch
寄存器
ARM
状态局部变量寄存器
8
ARM
状态局部变量寄存器
7
在支持数据检查的
ATPCS
中为数据栈限制指针
ARM
状态局部变量寄存器
6
在支持
RWPI
的
ATPCS
中为静态基址寄存器
ARM
状态局部变量寄存器
5
ARM
状态局部变量寄存器
4
Thumb
状态工½寄存器
局部变量寄存器
3
局部变量寄存器
2
局部变量寄存器
1
参数/结果/scratch 寄存器
4
参数/结果/scratch 寄存器
3
参数/结果/scratch 寄存器
2
参数/结果/scratch 寄存器
1
数据栈½用规则
栈指针通常可以指向不同的½½。
½栈指针指向栈顶元素
(即最后一个入栈的数据元素)
时,称为
FULL
栈;½栈指针指向与栈顶元素(即最后一个入栈的数据元素)相邻的一个可
用数据单元时,称为
EMPTY
栈。
数据栈的增长方向也可以不同。½数据栈向内存地址减小的方向增长时,称为
DESCENDING
栈;½数据栈向内存地址增加的方向增长时,称为
ASCENDING
栈。
综合这两种特点可以有以下
4
种数据栈。
FD
FULL Descending
ED
Empty Descending
FA
Full Ascending
EA
EmptyAscending
ATPCS
规定数据栈为
FD
类型,
并且对数据栈的操½是
8
字节对½的。
下面是一个数据
- 88 -
栈的示例(如图
3-2-1
所示)及其相关的名词。
1) 数据栈指针(stack
pointer)
是指最后一个写入栈的数据的内存地址。
2)
数据栈的基地址(stack
base)是指数据栈的最高地址。由于 ATPCS
中数据栈是
FD
类型的,实际上数据栈中最早入栈的数据占据的内存单元是基地址的下一个内存单元。
3)
数据栈界限(stack
limit)是指数据栈中可以½用的最½的内存单元的地址。
4)
已占用的数据栈(used
stack)是指数据栈的基地址和数据栈栈指针之间的区域。其
中包括数据栈栈指针对应的内存单元,½不包括数据栈的基地址对应的内存单元。
5)
未占用的数据栈(unused
stack)是指数据栈栈指针和数据界限之间的区域。其中包
括数据栈界限对应的内存单元,½不包括数据栈栈指针对应的内存单元。
6)
数据栈中的数据帧(stack
frames)是指在数据栈中,为子程序分配的用来保存寄存
器和局部变量的区域。
异常的处理程序可以½用被中断程序的数据栈,
这时用户要保证中断的程序的数据栈足
够大。
½用
ADS
中的编译器产生的目标代码中包含了
DRFT2
格式的数据帧。在调试过程中,
调试器可以½用这些数据帧来查看数据栈中的相关信息。
而对于汇编语言来说,
用户必须½
用
FRAME
为操½来描述数据栈中的数据帧。ARM 汇编器根据伪操½在目标文件中产生相
应的
DRAFT2
格式的数据帧。
图
3-2-1
一个数据栈的示意图
在
ARMv5TE
中,批量传送指令
LDRD/STRD
要求数据栈是
8
字节对½的,以提高数
据传送的速度。用
ADS
编译器产生的目标文件中,外部接口的数据栈½是
8
字节对½的,
并且编译器将告诉链接器:
本目标文件中的数据栈是
8
字节对½的。
而对于汇编程序来说如
果目标文件中包含了外部调用,则必须满足下列条件:
外部接口的数据栈必须是
8
字节对½的。
也就是要保证在进入该汇编代码后,
直到该汇
编代码调用外部程序之间,数据栈的栈指针变化偶数个数(如栈指针加
2
个字,而不½加
3
个字)
。
在汇编程序中½用
PRESERVE8
伪操½告诉链接器,
本汇编程序数据栈是
8
字节对½的。
- 89 -
参数传递规则
根据参数个数是否固定可以将子程序分为参数个数固定的(nonvariadic)子程序和参数
个数可变的(variadic)子程序。这两个子程序的参数传递规则是不同的。
参数个数可变的子程序参数传递规则
对于参数个数可变的子程序,½参数不超过
4
个时,可以½用寄存器
R0~R3
来传
递参数;½参数超过
4
个时,还可以½用数据栈来传递参数。
在参数传递时,将所有参数看½是存放在连续的内存单元中的字数据。然后,依次
将各字数据传送到寄存器
R0、R1、R2、R3
中,如果参数多于
4
个,将剩½的字数据
传送到数据栈中,入栈的顺序与参数顺序相反,即最后一个字数据先入栈。
按照上面的规则,一个浮点参数可以通过寄存器传递,也可以通过数据栈传递。
参数个数固定的子程序参数传递规则
对于参数个数固定的子程序,
参数传递与参数个数可变的子程序参数传递的规则不
同,如果系统包含浮点运算的硬件部件,浮点参数将按下面的规则传递:
各个浮点参数按顺序处理。
为每个浮点参数分配
FP
寄存器。
分配的方法是,满足该浮点参数需要的切编号最小的一组连续的
FP
寄存器。
第一个整数参数,通过寄存器
R0~R3
来传递。其他参数通过数据栈传递。
子程序结果返回规则
子程序中结果返回的规则如下:
结果为一个
32
½的整数时,可以通过寄存器返回。
结果为一个
64
½整数时,可以通过寄存器
R0
和
R1
返回,依次类推。
结果为一个浮点数时,可以通过浮点运算的寄存器
f0、d0
或则
s0
来返回。
结果为复合型的浮点数(如复数)时,可以通过寄存器
f0~fN
或者
d0~dN
来返回。
对于为数更多的结果,需要通过内存来传递。
3.2.2 C 与汇编的混合编程
在嵌入式系统开发中,
目前½用的主要编程语言是 C 和汇编。
在稍大规模的嵌入式½件
中,例如含有嵌入式操½系统,大部分的代码½是用 C 编写的,主要是因为 C 语言的结构比
较½,便于人的理解,而且有大量的支持库。½管如此,很多地方还是要用到汇编语言,例
如开机时硬件系统的初始化,包括 CPU 状态的设定、中断的½½、主频的设定以及 RAM 的控
制参数及初始化。
一些中断处理方面也可½涉及汇编。
另外一个½用汇编的地方就是一些对
性½非常敏感的代码块,这是不½依靠 C 编译器来生成代码的,而要手工编写汇编,达到优
化的目的。
而且,
汇编语言是和 CPU 的指令集紧密相连的,
½为涉及底层的嵌入式系统开发,
熟练掌握汇编语言的½用也是必须的。
在需要 C 与汇编混和编程时,
若汇编代码较简单,
则可以直接½用内嵌汇编的方法混和
编程;
否则,
可以将汇编文件以文件的½式加入项目中,
通过 ATPCS 规定与 C 程序相互调用、
访问。
- 90 -
这里主要讨论 C 和汇编的混合编程,
包括相互之间的½数调用。
下面分四种情况来进行
讨论。
在
C
语言中加入汇编程序
这里介绍在
C/C++里加入汇编程序的两种方法:内联汇编(Inline Assemble)和嵌入式
汇编(Embedded
Assemble)
。
内联汇编是指在
C/C++½数定义中插入汇编语句的方法,如下面的例子:
void enable_IRQ(void)
{
int tmp;
_asm
//内联汇编定义
{
MRS tmp,CPSR
//可以引用外部的 C
变量定义
BIC tmp,tmp,#0x08
MSR CPSR_c,tmp
}
}
内联汇编的用法跟真实汇编之间有很大的区别,并且不支持
Thumb,在内联汇编之中
不½直接访问物理寄存器(CPSR 除外)
,即½½用寄存器名进行编程,也会被编译器进行
重新分配。
与内联汇编不同,嵌入式汇编具有真实汇编的所有特性,同时支持
ARM
和
Thumb,½
是不½直接引用
C/C++的变量定义,数据交换必须通过 ATPCS
进行。嵌入式汇编在½式上
表现为独立定义的½数½,如下所示:
_asm int add(int i,int j)
//定义嵌入式汇编
{
ADD R0,R0,R1
//Value of i in R0 and j in R1,result in R0
MOV PC,LR
}
void main()
{
printf(“12345+67890=%d\n”,add(12345,67890));
}
灵活½用内联汇编和嵌入式汇编,可以帮助提高程序效率。
在汇编中½用
C
定义的全局变量
内嵌汇编不用单独编辑汇编语言文件,因此比较简洁,½是有诸多限制,所以½汇编的
代码较多时一般放在单独的汇编文件中。这时就需要在汇编和 C 之间进行一些数据的传递,
最简便的办法就是½用全局变量。
可以在 C 语言½数½外申明一个全局变量,
并在需要用到该变量的汇编语言文件中用关
键字 IMPORT 申明该变量即可。
- 91 -
评论