Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1635256
  • 博文数量: 511
  • 博客积分: 967
  • 博客等级: 准尉
  • 技术积分: 2560
  • 用 户 组: 普通用户
  • 注册时间: 2012-07-06 14:19
文章分类

全部博文(511)

文章存档

2016年(11)

2015年(61)

2014年(257)

2013年(63)

2012年(119)

分类: Android平台

2015-05-28 16:58:35

本文介绍了ARM代码编译时的软浮点(soft-float)和硬浮点(hard-float)的编译以及链接实现时的不同。从VFP浮点单元的引入到软浮点(soft-float)和硬浮点(hard-float)的概念,然后是在GCC和ARMCC RVCT工具链下的具体编译参数。

VFP (vector floating-point)

ARMv5开始,就有可选的 Vector Floating Point (VFP模块,当然最新的如 Cortex-A8Cortex-A9  Cortex-A5 可以配置成不带VFP的模式供芯片厂商选择。VFP经过若干年的发展,有VFPv2 (一些 ARM9 / ARM11) VFPv3-D16(只使用16个浮点寄存器,默认为32个)和VFPv3+NEON (如大多数的Cortex-A8芯片。对于包含NEONARM芯片,NEON一般和VFP公用寄存器。

硬浮点Hard-float

编译器将代码直接编译成发射给硬件浮点协处理器(浮点运算单元FPU)去执行。FPU通常有一套额外的寄存器来完成浮点参数传递和运算。使用实际的硬件浮点运算单元FPU当然会带来性能的提升。因为往往一个浮点的函数调用需要几个或者几十个时钟周期。

软浮点 Soft-float

编译器把浮点运算转换成浮点运算的函数调用和库函数调用,没有FPU的指令调用,也没有浮点寄存器的参数传递。浮点参数的传递也是通过ARM寄存器或者堆栈完成。 现在的Linux系统默认编译选择使用hard-float,即使系统没有任何浮点处理器单元,这就会产生非法指令和异常。因而一般的系统镜像都采用软浮点以兼容没有VFP的处理器。

armel和armhf ABI
armel中,关于浮点数计算的约定有三种。以gcc为例,对应的-mfloat-abi参数值有三个:soft,softfp,hardsoft是指所有浮点运算全部在软件层实现,效率当然不高,会存在不必要的浮点到整数、整数到浮点的转换,只适合于早期没有浮点计算单元的ARM处理器;softfp是目前armel的默认设置,它将浮点计算交给FPU处理,但函数参数的传递使用通用的整型寄存器而不是FPU寄存器;hard则使用FPU浮点寄存器将函数参数传递给FPU处理。需要注意的是,在兼容性上,soft与后两者是兼容的,但softfphard两种模式不兼容。默认情况下,armel使用softfp,因此将hard模式的armel单独作为一个abi,称之为armhf。而使用hard模式,在每次浮点相关函数调用时,平均能节省20CPU周期。对ARM这样每个周期都很重要的体系结构来说,这样的提升无疑是巨大的。在完全不改变源码和配置的情况下,在一些应用程序上,使用armhf能得到20%——25%的性能提升。对一些严重依赖于浮点运算的程序,更是可以达到300%的性能提升。

Soft-float和hard-float的编译选项

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 :非规格数flush0、舍入到最接近的实现的IEEE标准,不带异常;

--fpmode fast  更积极的优化,可能会有一点精度损失。

 

一个浮点软链接实现的汇编例子

IMPORT __softfp_cos

BL __softfp_cos

ARMCC fplib浮点运算库

__aeabi_dadd 浮点double类型数据的加法,__aeabi_fdiv 单精度浮点除法。

附录:常见的芯片和VFP配置

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

 

Texas Instruments

OMAP3xxx

armv7

Texas Instruments

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工具链下的具体编译参数。

 352人阅读 评论(0)  举报
目前大多数CPU都支持浮点运算单元FPU,FPU作为一个单独的协处理器放置在处理器核外,但是对于嵌入式处理器,浮点运算本来就少用,有些嵌入式处理器就会去掉浮点协处理器。
X86处理器一般都是有FPU的。而ARM PPC MIPS处理器就会出现没有FPU的现象。

linux kernel如何处理浮点运算,我们就分为带FPU的处理器和不带FPU的处理器来讨论。

(以下为个人知识总结,研究不深,错误之处希望大家指正,共同学习)


一 对于带FPU的处理器

1 对于linux kernel来说,kernel本身编译默认使用了-msoft-float选项,默认编译为软浮点程序,软浮点含义是有gcc编译器模拟浮点运算(glibc库提供),将浮点运算代码替换为定点运算。 

对于带FPU的处理器,我们可以将编译选项-msoft-float去掉,一般是在arch/xxx/Makefile中。将kernel编译为硬浮点,也就是让处理器的浮点指令计算浮点,
硬浮点运算肯定要比模拟的定点运算效率高。(kernel代码中一般不会有浮点运算,所以效率影响不大)

2 对于运行在kernel上的app来说,特别是对于图形程序,如QT,浮点运算较多,我们直接编译即可,因为处理器支持浮点运算,支持浮点运算指令。

二 对于不带FPU处理器 

1 对于linux kernel来说,编译默认使用了-msoft-float选项,默认编译为软浮点程序,linux kernel编译不依赖链接任何库,kernel中来实现对应的模拟浮点ABI。


2 对于运行在kernel之上的app来说,如何处理浮点运算,这里就有2种方法了:

(1)由kernel来模拟软浮点.

应用程序使用硬浮点直接编译(编译器默认就是编译成硬浮点程序)。 
而对于kernel,我所了解的PPC MIPS处理器都有专门的浮点运算异常处理,程序运行碰到浮点指令,无法运行浮点指令时,硬件会产生相应的中断异常,kernel浮点异常处理程序根据指令内容进行软浮点模拟操作,将运算结果返回之后再恢复到用户空间执行。
对于ARM我在其异常介绍中没有找到对于浮点计算的异常入口,但是kernel中也有对于其软浮点的支持,
在配置ARM Linux内核时,应该都会看到这样的配置:
    menu "Floating point emulation"
    comment "At least one emulation must be selected"
    config FPE_NWFPE
        ...
    这个是用来配置在内核里面模拟浮点处理器。
具体ARM如何实现支持异常模拟软浮点,具体实现有时间还需要仔细看代码,在arch/arm/nwfpe中。
这样的方式好处在于应用程序不需要重新编译,需要在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

阅读(4156) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~