分类: Android平台
2015-05-28 16:58:35
本文介绍了ARM代码编译时的软浮点(soft-float)和硬浮点(hard-float)的编译以及链接实现时的不同。从VFP浮点单元的引入到软浮点(soft-float)和硬浮点(hard-float)的概念,然后是在GCC和ARMCC RVCT工具链下的具体编译参数。
从ARMv5开始,就有可选的 Vector Floating Point (VFP) 模块,当然最新的如 Cortex-A8, Cortex-A9 和 Cortex-A5 可以配置成不带VFP的模式供芯片厂商选择。VFP经过若干年的发展,有VFPv2 (一些 ARM9 / ARM11)、 VFPv3-D16(只使用16个浮点寄存器,默认为32个)和VFPv3+NEON (如大多数的Cortex-A8芯片) 。对于包含NEON的ARM芯片,NEON一般和VFP公用寄存器。
编译器将代码直接编译成发射给硬件浮点协处理器(浮点运算单元FPU)去执行。FPU通常有一套额外的寄存器来完成浮点参数传递和运算。使用实际的硬件浮点运算单元FPU当然会带来性能的提升。因为往往一个浮点的函数调用需要几个或者几十个时钟周期。
编译器把浮点运算转换成浮点运算的函数调用和库函数调用,没有FPU的指令调用,也没有浮点寄存器的参数传递。浮点参数的传递也是通过ARM寄存器或者堆栈完成。 现在的Linux系统默认编译选择使用hard-float,即使系统没有任何浮点处理器单元,这就会产生非法指令和异常。因而一般的系统镜像都采用软浮点以兼容没有VFP的处理器。
armel和armhf ABI
在armel中,关于浮点数计算的约定有三种。以gcc为例,对应的-mfloat-abi参数值有三个:soft,softfp,hard。soft是指所有浮点运算全部在软件层实现,效率当然不高,会存在不必要的浮点到整数、整数到浮点的转换,只适合于早期没有浮点计算单元的ARM处理器;softfp是目前armel的默认设置,它将浮点计算交给FPU处理,但函数参数的传递使用通用的整型寄存器而不是FPU寄存器;hard则使用FPU浮点寄存器将函数参数传递给FPU处理。需要注意的是,在兼容性上,soft与后两者是兼容的,但softfp和hard两种模式不兼容。默认情况下,armel使用softfp,因此将hard模式的armel单独作为一个abi,称之为armhf。而使用hard模式,在每次浮点相关函数调用时,平均能节省20个CPU周期。对ARM这样每个周期都很重要的体系结构来说,这样的提升无疑是巨大的。在完全不改变源码和配置的情况下,在一些应用程序上,使用armhf能得到20%——25%的性能提升。对一些严重依赖于浮点运算的程序,更是可以达到300%的性能提升。
在CodeSourcery gcc的编译参数上,使用-mfloat-abi=name来指定浮点运算处理方式。-mfpu=name来指定浮点协处理的类型。可选类型如fpa,fpe2,fpe3,maverick,vfp,vfpv3,vfpv3-fp16,vfpv3-d16,vfpv3-d16-fp16,vfpv3xd,vfpv3xd-fp16,neon,neon-fp16,vfpv4,vfpv4-d16,fpv4-sp-d16,neon-vfpv4等。使用-mfloat-abi=hard (等价于-mhard-float) -mfpu=vfp来选择编译成硬浮点。使用-mfloat-abi=softfp就能兼容带VFP的硬件以及soft-float的软件实现,运行时的连接器ld.so会在执行浮点运算时对于运算单元的选择,是直接的硬件调用还是库函数调用,是执行/lib还是/lib/vfp下的libm。-mfloat-abi=soft (等价于-msoft-float)直接调用软浮点实现库。
在ARM RVCT工具链下,定义fpu模式:
? --fpu softvfp
? --fpu softvfp+vfpv2
? --fpu softvfp+vfpv3
? --fpu softvfp+vfpv_fp16
? --fpu softvfp+vfpv_d16
? --fpu softvfp+vfpv_d16_fp16.
定义浮点运算类型
--fpmode ieee_full : 所有单精度float和双精度double的精度都要和IEEE标准一致,具体的模式可以在运行时动态指定;
--fpmode ieee_fixed : 舍入到最接近的实现的IEEE标准,不带不精确的异常;
--fpmode ieee_no_fenv :舍入到最接近的实现的IEEE标准,不带异常;
--fpmode std :非规格数flush到0、舍入到最接近的实现的IEEE标准,不带异常;
--fpmode fast : 更积极的优化,可能会有一点精度损失。
一个浮点软链接实现的汇编例子
IMPORT __softfp_cos
BL __softfp_cos
__aeabi_dadd 浮点double类型数据的加法,__aeabi_fdiv 单精度浮点除法。
Partial reference of SoC and supported ISAs
Manufacturerhttp://houh-1984.blog.163.com/ |
SoC |
architecture |
VFP |
SIMD |
Notes |
Freescale |
iMX5x |
armv7 |
|
|
; only reliable in Tape-Out 3 or above |
Nvidia |
Tegra2 |
armv7 |
|
none |
|
Marvell |
Dove |
armv7 |
|
iwMMXt |
|
OMAP3xxx |
armv7 |
|
|
|
|
OMAP4xxx |
armv7 |
|
|
|
|
|
OMAP5xxx |
armv7 |
VFPv4 |
|
(ARMv7-A) + (ARMv7-ME) |
Qualcomm |
Snapdragon |
armv7 |
|
[1] |
Qualcomm "Scorpion" core |
|
S5PC100 |
armv7 |
|
|
|
|
A1x |
armv7 |
|
|
|
Reference
http://houh-1984.blog.163.com/
本文介绍了ARM代码编译时的软浮点(soft-float)和硬浮点(hard-float)的编译以及链接实现时的不同。从VFP浮点单元的引入到软浮点(soft-float)和硬浮点(hard-float)的概念,然后是在GCC和ARMCC RVCT工具链下的具体编译参数。
linux kernel如何处理浮点运算,我们就分为带FPU的处理器和不带FPU的处理器来讨论。
(以下为个人知识总结,研究不深,错误之处希望大家指正,共同学习)
1 对于linux kernel来说,kernel本身编译默认使用了-msoft-float选项,默认编译为软浮点程序,软浮点含义是有gcc编译器模拟浮点运算(glibc库提供),将浮点运算代码替换为定点运算。
对于带FPU的处理器,我们可以将编译选项-msoft-float去掉,一般是在arch/xxx/Makefile中。将kernel编译为硬浮点,也就是让处理器的浮点指令计算浮点,二 对于不带FPU处理器
1 对于linux kernel来说,编译默认使用了-msoft-float选项,默认编译为软浮点程序,linux kernel编译不依赖链接任何库,kernel中来实现对应的模拟浮点ABI。
2 对于运行在kernel之上的app来说,如何处理浮点运算,这里就有2种方法了:
(1)由kernel来模拟软浮点.
应用程序使用硬浮点直接编译(编译器默认就是编译成硬浮点程序)。但是缺点也很明显,每次浮点操作都要触发中断异常,用户空间和内核空间切换,执行效率太低。
(2)使用软浮点重新编译app
这样可以避免上述问题,app编译时需要连接glibc库的,使用--msoft-float,使用glibc的模拟浮点,替换为定点运算,这样的好处是运行性能上会好一些。
但缺点是因为使用了不同的编译选项,使用的ABI可能就发生了变化,如果某个库或者应用没有使用同样的编译选项(ABI不同),
系统运行时会出现意想不到的情况,甚至造成崩溃。
根据最近对PPC一款处理器的调试记录,kernel正常启动进入console后死在某一地址,用户空间浮点运算多,询问IC后得知FPU去掉,而处理器浮点异常没有使能。
这样遇到浮点指令,处理器不会触发异常,也不知道该如何运行该指令。
所以进行kernel移植时对于处理器有无FPU也要搞清楚,如果处理器去掉了FPU,而核没有做相应的处理(使能浮点异常),那么APP的浮点指令运行结果就是无法预测的,这时可以采用软浮点工具链来编译APP。
这里有一点思考:
对于一款处理器,处理器设计中有浮点异常(MIPS PPC都是),其外也可以接FPU。
在接FPU后,处理器核内就要屏蔽掉浮点异常,不然浮点运算还是产生浮点异常,FPU就没有实用意义了。
无FPU,则处理器核内要使能浮点异常,不然就跟我上面遇到的问题一样,处理器不知道该如何运行该浮点指令,结果就无法预测了。
1、首先是待make的程序:
#include#include #include #include int main(void) { printf("Hello Makefile!\n"); return 0; }
2、其次就是其makefile了:
CC = $(CROSS_COMPILE)gcc LD = $(CROSS_COMPILE)ld STRIP = $(CROSS_COMPILE)strip OBJCOPY = $(CROSS_COMPILE)objcopy OBJDUMP = $(CROSS_COMPILE)objdump AR = $(CROSS_COMPILE)ar RANLIB = $(CROSS_COMPILE)ranlib CURTDIR = $(shell pwd) TARGET = my_make %.o:%.c $(CC)-c $(EXTRAFLAGS) $< -o $@ %.o:%.S $(CC)-c $(EXTRAFLAGS) $< -o $@ .PHONY: all clean $(TARGET): $(TARGET).o $(CC) -o $@ $^ clean: rm-rf $(TARGET) $(TARGET).o
如果是要交叉编译的话,那么就如下:
CROSS_COMPILE =/usr/local/arm/4.2.2-eabi/usr/bin/arm-linux- CC = $(CROSS_COMPILE)gcc LD = $(CROSS_COMPILE)ld STRIP = $(CROSS_COMPILE)strip OBJCOPY = $(CROSS_COMPILE)objcopy OBJDUMP = $(CROSS_COMPILE)objdump AR = $(CROSS_COMPILE)ar RANLIB = $(CROSS_COMPILE)ranlib CURTDIR = $(shell pwd) ROOTDIR = $(CURDIR)/../.. TARGET = my_make #CFLAGS = -I$(ROOTDIR)/src/inc -EL #LDFLAGS = -EL -L$(ROOTDIR)/lib -lled CFLAGS = -I$(ROOTDIR)/src/inc LDFLAGS = -L$(ROOTDIR)/lib -lled %.o:%.c $(CC)-c $(CFLAGS) $(EXTRAFLAGS) $< -o $@ %.o:%.S $(CC)-c $(CFLAGS) $(EXTRAFLAGS) $< -o $@ .PHONY: all clean $(TARGET): $(TARGET).o $(CC)$(LDFLAGS) -o $@ $^ clean: rm-rf $(TARGET) $(TARGET).o
以上是编译成可执行文件的,如果要编译成.so文件呢?那么其makefile要怎么实现呢?
1、 首先是最外面的makefile了
CROSS_COMPILE=/usr/local/arm/4.2.2-eabi/usr/bin/arm-linux- CC =$(CROSS_COMPILE)gcc LD =$(CROSS_COMPILE)ld STRIP =$(CROSS_COMPILE)strip OBJCOPY =$(CROSS_COMPILE)objcopy OBJDUMP =$(CROSS_COMPILE)objdump AR =$(CROSS_COMPILE)ar RANLIB =$(CROSS_COMPILE)ranlib ROOTDIR =$(shell pwd) INCLUDEDIRS =-I$(ROOTDIR)/inc #LIB_DIR =/opt/mips-linux-gnu-4.3.-51/mips-linux-gnu/libc/el/usr/lib #CFLAGS = -O0-g3 -EL -Wall $(INCLUDEDIRS) -mhard-float -fPIC CFLAGS = -O0 -g3-EL -Wall $(INCLUDEDIRS) -fPIC AFLAGS = -O0 -g3-EL -Wall $(INCLUDEDIRS) -mmt -fPIC LDFLAGS = -EL-fPIC -shared -LLIB_DIR -lm -lpthread -lc -ldl export CC LDSTRIP OBJCOPY OBJDUMP AR RANLIB CFLAGS LDFLAGS AFLAGS INCLUDEDIRS ROOTDIR .PHONY : all all : childdir childdir: @for dir in $(SUB_DIRS); do \ $(MAKE) -C $$dir || exit 1;\ done SUB_DIRS = led install: mkdir -p ../lib mkdir -p ../module dep: @for dir in $(SUB_DIRS); do \ $(MAKE) -C $$dir dep; \ done clean: @for dir in $(SUB_DIRS); do\ $(MAKE) -C $$dir clean; \ done
然后是其Rules.make
$(MODULE) : $(OBJFILE) $(LLIBS) @echo[LD] $@ $(CC)$(LDFLAGS) -o $(MODULE) $(OOBJFILE) mv$(MODULE) $(ROOTDIR)/../lib %.o:%.c $(CC)-c $(CFLAGS) $(EXTRAFLAGS) $< -o $@ %.o:%.S $(CC)-c $(CFLAGS) $(EXTRAFLAGS) $< -o $@ dep: $(CC)$(INCLUDEDIRS) -M $(SRC) > ./depend.mk clean: rm-rf $(OBJFILE) rm-rf $(MODULE) rm-rf depend.mk -include ./depend.mk
接着你要编译的程序,比如上面的那个程序。
MODULE = libmy_make.so SRS = $(wildcard *.S) SRC = my_make.c OBJFILE = $(SRC:%.c=%.o) $(SRS:%.S=%.o) include $(ROOTDIR)/Rules.make