newlib的编译和使用

要熟悉一个新的处理器平台,第一件事是写一些裸机程序。这时我们最想要的,是实现一个printf打印函数,以便及时输出各种信息。

除去下层的字节输出驱动不说,printf本身的实现就有够麻烦,如果平时有保存相关的代码还好,不然就很浪费时间。除此之外,还有

一些诸如strlen、strcpy之类的函数,我们不愿意自己写,既麻烦而且效率不高,如果能借助已有的代码或库就好了。newlib就满足了

这点需求。

newlib c库是一个开源的c函数库,包括libc和libm两部分。它支持ANSI C库标准,针对不同处理器架构进行优化,轻量级,适用于嵌入式系统。

在我看来,newlib库有如下好处:

1. 支持printf和优化的字符串操作

2. 支持malloc和free等内存操作

3. 支持函数可重入功能(不过这种支持对内存有压力,总之是感觉弊大于利)

4. 支持libm数学库(不过一般嵌入式用不到浮点数,而且用模拟的开销略大)

        5. newlib的函数是分文件实现的,如果用不到,绝不加入链接,一般不会造成目标文件猛增的情况。


newlib虽有诸多好处,但要用起来还真有点坡度。它需要先编译成库再使用,而且要用特定的功能还需要写一些底层支持函数。

我就不献丑了,网上已有大牛专业的教程:Howto: porting newlib a simple guide,

http://www.embecosm.com/appnotes/ean9/ean9-howto-newlib-1.0.html


这篇文章是介绍如何将newlib移植到一个开源软核OpenRISC 1000处理器上去。但我们一般用不到这种高难度动作,只是简单地在已有平台上

进行使用即可,下面就介绍我在stm32平台上使用newlib-1.20.0的经验。


下载newlib-1.20.0.tar.gz,解压缩,生成newlib-1.20.0。再创建newlib-1.20.0-build目录用于生成库,创建newlib-1.20.0-install用于安装(当然装完了随便拷贝)。

$cd newlib-1.20.0-build

$../newlib-1.20.0/configure --target=arm-none-eabi --with-newlib --prefix=`pwd`/../newlib-1.20.0-install

因为这里我用的事arm-none-eabi-gcc工具链,如果是arm-elf-gcc什么的要相应地修改--target选项。

$make all

$make install


从理论上来说,这样做之后,就可以生成newlib库到newlib-1.20.0-install目录中。但在此过程中确实出现了问题。newlib支持arm三个架构armv6m、thumb、thumb2,它试图编译对每个架构生成一个库,可惜部分汇编文件中包含swi指令,thumb和thumb2架构不支持,这就糟了,编译过程中就卡住了。当然,这些汇编文件都是系统启动或者进行系统调用时用的,我们用不到(我也不会用)。要想办法删掉它们,修改相应的Makefile.am。

   1. 在newlib-1.20.0/newlib/libc/sys/Makefile.am中:

## Process this file with automake to generate Makefile.in

AUTOMAKE_OPTIONS = cygnus

INCLUDES = $(NEWLIB_CFLAGS) $(CROSS_CFLAGS) $(TARGET_CFLAGS)

AM_CCASFLAGS = $(INCLUDES)

noinst_LIBRARIES = lib.a

if MAY_SUPPLY_SYSCALLS
extra_objs = $(lpfx)libcfunc.o $(lpfx)trap.o $(lpfx)syscalls.o
else
extra_objs =
endif

lib_a_SOURCES = aeabi_atexit.c
#lib_a_LIBADD = $(extra_objs)                                 #不生成syscalls.o
#EXTRA_lib_a_SOURCES = trap.S syscalls.c libcfunc.c           #不使用syscalls.c
#lib_a_DEPENDENCIES = $(extra_objs)                           #依赖也不要,因为都不需要,所以干脆全注释了
lib_a_CCASFLAGS = $(AM_CCASFLAGS)
lib_a_CFLAGS = $(AM_CFLAGS)

if MAY_SUPPLY_SYSCALLS
#all-local: crt0.o                                            #这里的crt0.o也是不需要生成的
all-local:
endif

ACLOCAL_AMFLAGS = -I ../../.. -I ../../../..
CONFIG_STATUS_DEPENDENCIES = $(newlib_basedir)/configure.host

      改为makefile之后,要在相应目录下执行autoreconf,重新生成Makefile.in。

    2. 刚才修改Makefile.am,使其不生成crt0.o,但实际上上层目录还是要用它的。所以就又改了其上层目录的Makefile.am,让其凭空造一个crt0.o出来。修改文件newlib-1.20.0/newlib/libc/sys/Makefile.am:

$(CRT0): $(sys_dir)/$(CRT0)
	rm -f $@
	ln $(sys_dir)/$(CRT0) $@ >/dev/null 2>/dev/null \
	 || cp $(sys_dir)/$(CRT0) $@ || touch $@              #就是在这里最后加上 || touch $@,就凭空造出一个crt0.o,不至于在编译时出错
   

      改完后同样不要忘了执行autoreconf。
    

   3. 编译时除了编译newlib目录,还编译了libgloss目录,所以还要修改其中的Makefile.in(这回改Makefile.in了)。修改文件newlib-1.20.0/libgloss/arm/Makefile.in:

LINUX_CRT0    = linux-crt0.o
LINUX_BSP     = libgloss-linux.a
#LINUX_OBJS    = linux-syscalls0.o linux-syscalls1.o                       #这个有syscalls,去掉
LINUX_SCRIPTS = linux.specs
LINUX_INSTALL = install-linux

REDBOOT_CRT0	= redboot-crt0.o 
#REDBOOT_OBJS	= redboot-syscalls.o                                      #这个有syscalls,去掉
REDBOOT_SCRIPTS	= redboot.ld redboot.specs
REDBOOT_INSTALL	= install-redboot

RDPMON_CRT0	= rdpmon-crt0.o
RDPMON_BSP	= librdpmon.a
#RDPMON_OBJS	= syscalls.o libcfunc.o trap.o _exit.o _kill.o             #这个有syscalls,去掉
RDPMON_SCRIPTS	= rdpmon.specs
RDPMON_INSTALL	= install-rdpmon

RDIMON_CRT0	= rdimon-crt0.o
RDIMON_BSP	= librdimon.a
RDIMON_OBJS	= $(patsubst %,rdimon-%,$(RDPMON_OBJS))
RDIMON_SCRIPTS	= rdimon.specs
RDIMON_INSTALL	= install-rdimon

CFLAGS		= -g

# Here is all of the eval board stuff
PID_SCRIPTS	= pid.specs
PID_INSTALL	= install-pid

IQ80310_SCRIPTS	= iq80310.specs
IQ80310_INSTALL	= install-iq80310


# Host specific makefile fragmen

    如此修改之后,应该就能编译安装成功。arm之外的架构,一般不会遇上这种问题。


    如此成功之后,可以再newlib-1.20.0-install/arm-none-eabi中看到一个include头文件目录,一个lib目录。我的stm32使用的事lib/thumb2/libc.a。


   接下来,还要再补齐一些底层支持函数。这个不需要libgloss生成的,而是要看newlib-1.20.0/newlib/configure.host文件。newlib根据$host不同预设了底层接口的支持方式,我们需要看$syscall_dir的值,以及MISSING_SYSCALL_NAMES、REENTRANT_SYSCALLS_PROVIDED两个宏是否添加进$newlib_cflags。下面是不同设定对应的情况:

     

-DMISSING_SYSCALL_NAME和-DREENTRANT_SYSCALL_PROVIDED

-DMISSING_SYSCALL_NAME

         当使用-DMISSING_SYSCALL_NAME宏进行编译时,在_syslist.h中将原本的_close映射为close(等等),_syslist.h在ligloss/libnosys和libc/reent的c文件中被包含。并且,如果使用了-DMISSING_SYSCALL_NAME,则编译时不包括libc/syscall中的文件。而libc/syscall中文件正是有close等函数的定义。

         所以,如果定义了_DMISSING_SYSCALL_NAME,看起来就是bsp提供close等函数的实现,而libc/reent中的_close_r等函数能提供简单的可重入支持。

 

         此时,bsp提供一套close等函数的实现,而libc/syscall不被使用。系统流程虽然经过_close_r,但从意思上是不支持可重入性的。

-DREENTRANT_SYSCALLS_PROVIDED

         当使用-DREENTRANT_SYSCALLS_PROVIDED宏定义进行编译时,在libc/reent中的c文件全部没有函数体,即不定义_close_r等函数。所以-DREENTRANT_SYSCALLS_PROVIDED的意思,是指bsp提供了可重入的_close_r等函数版本。上层的库如stdio仍调用_close_r等可重入函数进行操作。同时,libc/syscall中的函数被使用。libc/syscall中定义bsp需提供实现的close等函数,这里close由_close_r实现。bsp不需要再提供close的实现,只需提供_close_r的实现。

 

         此时,bsp提供一套_close_r等函数的实现,libc/syscall被使用。整个系统的可重入性,压在bsp的_close_r等函数的可重入性上。从意思上说是支持可重入性的。

 

两者同时存在

         当两者都存在时,libc/syscall和libc/reent都未起作用。bsp提供了一套close等函数的实现。整个newlib的上层,从调用_close_r转而调用close。系统的可重入性,直接压在bsp的close等函数之上。但从意思上来说不支持可重入性。

两者都不存在

         两者都不存在的时候,使用libc/syscall中的函数。这个流程就简单了,先是libc/syscall中的close函数,调用libc/reent中的_close_r函数,然后_close_r函数又调用bsp应该实现的_close函数。

 

         此时bsp提供一套_close等函数的实现,libc/syscall被使用。从意思上来说是不支持可重入性的。


       对于arm来说,定义如下:

  arm-*-*)
	syscall_dir=syscalls

      符合两者都不存在的情况,我们只需提供一套_close等函数的实现即可。参照前面的专业教程实现即可。

      要实现printf,需要实现好_write函数,要实现malloc,需要实现好_sbrk函数,__malloc_lock函数,__malloc_unlock函数。这里就不多说了。

     printf的打印缓冲区是从_reent结构中获取的,因为我们没有实现可重入功能,是同一缓冲区,所以在打印过程中任务切换可能导致打印混乱。我希望它在打印时使用栈空间,有两个方法:一是自己实现printf,在其中调用sprintf打印到缓冲区,然后用puts输出;二是完全自己实现printf,也就是用使用newlib之前的printf函数。

     另外,在malloc调用时要做好互斥工作,但也不适合用关中断,最好用互斥信号量或普通的信号量。



PS: 在编译newlib过程中,重复好多遍,还要改Makefile.am,还必须编译libgloss。我尝试过在configure中添加选项的方式避免修改,但没有成功。更改地很暴力,如果大家有好的编译使用newlib的方式,还请不吝指教,多谢了。


  • 4
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值