Chinaunix首页 | 论坛 | 博客
  • 博客访问: 542090
  • 博文数量: 150
  • 博客积分: 5010
  • 博客等级: 大校
  • 技术积分: 1861
  • 用 户 组: 普通用户
  • 注册时间: 2008-03-17 00:19
文章分类

全部博文(150)

文章存档

2011年(1)

2009年(14)

2008年(135)

我的朋友

分类: 项目管理

2008-04-07 23:31:30

嵌入式设备由于不具备一定的处理器能力和存储空间,程序开发一般用PC来完成,然后将可执行文件下载到嵌入式系统中运行。这是目前嵌入式程序开发的不二选 择——Host/target模式。但这引发了一个问题:由于Host和Target的处理器体系结构不同,我们不能直接用PC上既有的程序开发工具,必 须使用跨平台开发工具,即在Host上生成能在Target上运行格式的目标文件。

与在PC上进行程序开发类似,嵌入式系统开发也需要编译器、链接器、解释程序等。本文讨论GNU跨平台开发工具链的建立,包括: ld, gas, ar, gcc, glibc.

自己建立交叉编译环境是一件很头疼的事(处理版本的依赖性, 漫长的编译过程...),如果你不想经历这样的痛苦,可以选择网上编译好了的工具链进行安装.如果你用的是Debian/Ubuntu的发行版, 推荐使用. 如果使用uClinux, 也可安装arm-elf-tools.

关于Emdebian和arm-elf-tools的更多介绍, 情看本blog的这篇文章!

前提:
建立工作目录

在为目标板开发及定制软件的过程中, 最好将项目要用到的代码, 文档, 及生成的目标文件按一定的组织放在统一的, 结构化的目录中. 这里采用《构建嵌入式Linux系统》中推荐的项目目录安排:

 目录 内容
bootldr
目标板的引导加载程序
build-tools
建立跨平台开发工具链所需要用到的包和目录
debug 调试工具及相关包
doc
项目相关的文档
images
使用在目标板上的引导加载程序和内核的二进制映像, 以及根文件系统
kernel 将在目标板上进行评估的各个内核版本
rootfs
目标板的内核在运行时看到的根文件系统
sysapps
目标板需要用到的系统应用程序
tmp
进行实验时或临时文件会用到此目录
tools
 放置跨平台开发工具链以及C链接库

上表列出的是一个完备的目录结构, 使用于一个实际的项目. 本文关注的是建立跨平台工具链,  它使用下面的目录:
build-tools, tools, kernel

其中, 在build-tools建立下列目录:
$ mkdir build-binutils build-boot-gcc build-gcc build-glibc gcc-patch


设置环境变量

前面建立的项目空间目录众多, 我们可以设置一些环境变量以方便后面的工作. 你可以在命令行中设定这些环境变量. 当然, 更简单的方法是建立一个名为RunFirst.sh的bash脚本. 将该脚本放在项目根目录中, 每次登录时运行它既可进入你的工作环境: $ ./RunFirst.sh

#!/bin/bash

export PRJROOT=/home/zp/project/EmbeddedLinux
export TARGET=arm-linux
export PREFIX=${PRJROOT}/tools
export TARGET_PREFIX=${PREFIX}/${TARGET}
export PATH=${PREFIX}/bin:${PATH}
cd PRJROOT

PRJROOT  :指定了项目的根目录.
TARGET  :指定了目标般类型, 如果要更换目标板类型(比如换成PowerPC), 那么需要更改TARGET, 并重新编译工具链.
PREFIX  :为后面用到的建立工具链的命令提供了一个"指针", 它指向目标板工具程序将被安装的目录.
TARGET_PREFIX  :指定与目标板相关的头文件和链接库将被安装的位置.
PATH  :指定可执行文件的位置. 当交叉编译工具被建立后, 它告诉Bash首先在我们自己建立的工具链目录中寻找可执行文件.
执行最后一条命令就会移动到项目的根目录.

注意运行脚本的方式: 一般是有两种方法source file.sh或./file.sh. 如果直接调用脚本名, 当前shell会生成一个子shell去执行脚本, 子shell在脚本运行完后退出. 不会对当前shell产生影响. 也就是说当前设置的环境变量只对子shell起作用. 所以不用这种方法运行. 而使用source. source命令使得脚本在当前shell的上下文中被执行.


本工具链的各组件的版本为:
gcc : 2.95.3
binutils : 2.10.1
glibc : 2.2.3 + libc-linuxthreads-2.2.3
kernel : 2.4.21 + patch-2.4.21-rmk2

建立工具链的步骤:
  1. 设置内核头文件
  2. 设置binutils
  3. 设置引导编译器boot-gcc
  4. 设置C链接库glibc
  5. 设置完整的编译起gcc
尽管将设置内核头文件放在第一步, 实际上要等到设置glibc时才会用到头文件. 只需保证设置内核头文件在设置glibc之前即可. 如果考虑到设置工作目录, 将设置内核头文件排在第一步比较合适.


(一)设置内核头文件

首先进入kernel目录,下载内核源码,以及

patch-2.4.21-rmk2是针对ARM的补丁, 关于它的介绍可以参考

这里使用的2.4.21版的内核, 如果使用2.4.18版之前(含2.4.18)的内核, 解压之后建立的目录名为linux, 不带版本号. 如果使用多个版本的内核, 最好把它linux目录改名,添加版本号. 否则如果解压另一版本(低于2.4.19)的内核, 会覆盖linux目录. 2.4.19版之后的目录名会加上版本号.

解压内核, 并打补丁:

$ cd linux-2.4.21
$ patch -p1 < ../patch-2.4.21-rmk2

在内核源码数根目录配置内核:
$ make ARCH=arm CROSS_COMPILE=arm-linux- menuconfig

ARCH=arm指定目标系统的体系结构,这里是针对ARM,如果你不指定 ARCH=arm,那么会针对你的本地系统(X86)配置内核.

由于你没有建立交叉编译器, 你无法编译内核. 在这里只是对内核进行基本的配置: 在这里只需选定 目标系统的处理器类型.

推荐用menuconfig进行配置, 关于配置内核的工具, 参考本blog这篇文章的前半部分.

配置结束, 退出menuconfig时, 它提示你运行 make dep, 其实不需要运行它. 因为此处只是简单的配置, 不编译内核.

配置完退出并保存, 检查一下的内核目录中的 include/linux/version.h 和 include/linux/autoconf.h 文件是不是生成了, 这是编译 glibc 是要用到的, version.h 和 autoconf.h 文件的存在,也说明了你生成了正确的头文件.

设置内核头文件

此后编译完整的gcc过程中需要用到内核头文件. 编译过程中系统默认在sys-linux目录中搜索头文件. 我们可以在配置gcc编译时增加 --with-headers选项来设定包含头文件目录, 但这会使得头文件被拷贝到sys-linux目录中. 比较好的方法是创建符号连接以避免拷贝冗余文件:

1, 将内核源码术的asm-arm目录和linux目录拷贝到$TARGET_PREFIX/include目录:
$ cd $PRJROOT/kernel/linux-2.4.21
$ cp -dR include/asm-arm $TARGET_PREFIX/include/asm
$ cp -dR include/linux $TARGET_PREFIX/include/linux

2, 在内核源码数建立下列符号连接:

$ cd include
$ ln -s asm-arm asm
$ cd asm
$ ln -s arch-epxa arch
$ ln -s proc-armv proc

3, 在$TARGET_PREFIX/目录中建立下面的符号连接:

$ cd $TARGET_PREFIX
$ ln -s include sys-linux

4,在$TARGET_PREFIX/目录中建立下面的符号连接:
$ cd $TARGET_PREFIX/include/asm
$ ln -s arch-epxa arch
$ ln -s proc-armv proc


(二)设置binutils

此步骤不需要设置内核头文件!

进入build-tools目录, 下载

(1)进入build-binutils目录配置和编译binutils

$../binutils-2.10.1/configure --target=$TARGET --prefix=$PREFIX

--target 选项是指出我们生成的是 arm-linux 的工具,--prefix 是指出我们可执行文件安装的位置。

注意: 配置binutils需要用到flex, 若没有安装flex, 配置过程中会提示:


../../binutils-2.10.1/binutils/configure: line 1945: flex: command not found
checking for flex... lex
checking for yywrap in -ll... no
checking lex output file root... ../../binutils-2.10.1/binutils/configure: line 2033: lex: command not found
configure: error: cannot find output from lex; giving up

需要安装flex, 在Debian/Ubuntu中,直接运行:
$ sudo apt-get install flex

下面是从源代码安装的方法:
------------------------------------------------------------------------------
先安装lex,到GNU网站:下载的源码安装。
在编译flex的过程中,make又提示错误:
make: yacc: Command not found
make: *** [parse.c] Error 127
需要yacc,于是下载Berkeley的源码。

so,安装binutils时,若需要flex, yacc,执行下列步骤
1,获得yacc, flex源码
2,安装yacc: ./configure --> make --> make install
3,安装flex: ./confugre --> make --> make install
    完成后,再继续安装binutils:
1,./configure --target=..., --prefix=...
2,make
3,make install
------------------------------------------------------------------------------

(2) 前面的配置产生Makefile之后, 只需运行:
$ make
$ make install

OK, 前面已经安装好了binutils, 下面我们看看安装了哪些程序:
$ ls $PREFIX/bin

arm-linux-addr2line  arm-linux-c++filt  arm-linux-nm       arm-linux-ranlib   arm-linux-strings
arm-linux-ar         arm-linux-gasp     arm-linux-objcopy  arm-linux-readelf  arm-linux-strip
arm-linux-as         arm-linux-ld       arm-linux-objdump  arm-linux-size

关于上面的命令, 可以参看本人blog的GNU binutils笔记


(三)设置初始编译器(boot-gcc)


此步骤不需要设置内核头文件!

进入build-tools目录, 下载并到网站下载这三个补丁:

   

解压GCC,并打补丁:
$cd gcc-2.95.3
$patch -p1< ../gcc-patch/gcc-2.95.3-2.patch
$patch -p1< ../gcc-patch/gcc-2.95.3-no_fixinc-1.patch
$patch -p1< ../gcc-patch/gcc-2.95.3-returntype_fix-1.patch
echo timestamp > gcc/cstamp-h.in

如果你用的是Ubuntu默认的gcc(既安装build-essential中的gcc, 该gcc的版本目前是4.0.2). 在编译时候会提示"invalid lvalue in increment"错误. 最好使用gcc-3.3.
Ubuntu中安装gcc-3.3: $ sudo apt-get install gcc-3.3
只有调用不同版本的gcc, 只需:
$ export CC=gcc-3.3

对t-linux文件作一些修改:
-----------------------------
修改$PRJROOT/gcc-2.95.3/gcc/config/arm/t-linux,把
TARGET_LIBGCC2-CFLAGS = -fomit-frame-pointer -fPIC
这一行改为
TARGET_LIBGCC2-CFLAGS = -fomit-frame-pointer -fPIC -Dinhibit_libc -D__gthr_posix_h
注意是arm目录下的!在/gcc/config目录下也有一个t-linux文件,别弄混淆了。
-----------------------------
上面的修改方案来自IBM developerworks上的“如何为嵌入式开发建立交叉编译环境”. 但我在编译过程中老是碰见"libgcc1.S:438: asm/unistd.h: No such file or directory"这样的错误. 原先以为是本地GCC的问题, 用gcc-2.95.3, gcc-3.3等编译, 还是如此. 又以为需要在这里设置内核头文件, 拷贝, 建立符号连接... 什么方法都试过, 还是不行. 我KAO...

实际上, 应该这样修改t-linux:
TARGET_LIBGCC2_CFLAGS = -fomit-frame-pointer -fPIC语句之前添加"T_CFLAGS = -Dinhibit_libc -D__gthr_posix_h"
成为:
T_CFLAGS = -Dinhibit_libc -D__gthr_posix_h
TARGET_LIBGCC2_CFLAGS = -fomit-frame-pointer -fPIC


-Dinhibit_libc等同于在命令中加上"--with-newlib", 它告诉配置工具不要使用glibc, 因为当前的glibc并不是针对目标板的. 从字面上看, 它是告诉配置工具"使用一个新的c库来作为目标板的C链接库. 然而当前我们并没有可用的C链接库, 所以这个选项只是让GCC能够正确编译, 生成一个初始编译器而已. 随后可以选择C链接库.

-D__gthr_posix_h我不知道是什么意思, 反正如果不更改上面的语句, 就会有如下的错误:
../../gcc-2.95.3/gcc/gthr-posix.h:37: pthread.h: No such file or directory
可能是去掉posix thread支持吧.

配置
$ cd $PRJROOT/build-tools/build-boot-gcc/
$ ../gcc-2.95.3/configure --target=$TARGET --prefix=$PREFIX --without-headers --enable-language=c --disable-threads

--target, --prefix 和配置 binutils 的含义是相同的. --without-headers 就是指不需要头文件, 因为是交叉编译工具, 不需要本机上的头文件. --with-newlib在前面已经介绍了, 即便更改了t-linux文件, 也可以在这里设置--with-newlib. -enable-languages=c是指我们的 boot-gcc 只支持 c 语言. --disable-threads 是去掉 thread 功能, 这个功能需要 glibc 的支持.

如果用的比较老版本gcc (比如gcc-2.95.3) 在配置过程中会提示这样的错误:
Config.guess failed to determine the host type.  You need to specify one.
这是由于config.guess, config.sub版本太老. config.guess是用来检测host类型的, 运行它就能得知host类型.
解决方法: 到ftp://ftp.gnu.org/pub/pub/gnu/config/下载新版的config.guess和config.sub:
 *checkout*/config/config/config.guess
*checkout*/config/config/config.sub
用它们替换gcc-2.95.3目录下的老文件.
现在运行 $./config.guess:
i686-pc-linux-gnu

编译, 安装
$ make all-gcc
$ make install-gcc

注意:为什么不用 all-in-one 的 gcc-$VGCC.tar.gz 呢?
all-in-one 的 gcc 包里面有 chill, fortran, java 等语言的编译器,虽然在下面 configure 时指定 -enable-languages=c,但编译时还是把所有的都编译一便,这不是我们需要的,而且它也总会有错误。因此我们只编译 C 语言的编译器。后面第二次编译的时候也是这个问题,我们只编译 C 和 C++ 的编译器。


(四) 设置C链接库glibc

进行到这一步的时候, 需要保证正确地设置了内核头文件, 因为glibc要用到内核头文件!

进入build-tools目录, 下载
以及
解压glibc, 并打补丁

$ cd $PRJROOT/build-tools
$ tar -xvzf glibc-2.2.3.tar.gz
$ tar -xzvf glibc-linuxthreads-2.2.3.tar.gz --directory=glibc-2.2.3
配置glibc

$ cd build-glibc
$ CC=arm-linux-gcc ../glibc-2.2.3/configure --host=$TARGET  --prefix="/usr"--enable-add-ons --with-headers=$TARGET_PREFIX/include

CC=arm-linux-gcc 把 CC 变量设成你刚编译完的boostrap gcc, 用它来编译你的glibc.

--host=$TARGET 告诉该链接库在目标系统上执行, 而非在本地主机.

--prefix="/usr" 告诉配置脚本在目标板的根文件系统中glibc的位置.

--enable-add-ons 告诉配置脚本使用我们下载的附加包. 已经将glibc-linuxthreads-2.2.3放入了 glibc 源码目录中. 由于我们只添加了一个附加包, 这里--enable-add-ons等价于 --enable-add-ons=linuxthreads. (如果使用glibc-2.1.x, 需要使用glibc-crypt附加包, 就得使用: --enable-add-ons=linuxthreads, crypt选项).

--with-headers 告诉 glibc 我们的linux 内核头文件的目录位置.

编译并安装glibc
$ make
$ make install_root=$TARGET_PREFIX prefix="" install
install_root 指定了安装链接库组件的目录, 将glibc安装到与我们项目相关的目录, 而非/usr目录.

如果不指定prefix="", 那么glibc会被安装到$TARGET_PREFIX/usr/lib目录中. 指定prefix使glibc被安装到 $TARGET_PREFIX/lib目录.

修改$TARGET_PREFIX/lib目录中的libc.so

$ cd $TARGET_PREFIX/lib
$ cat libc.so

libc.so的内容:
/* GNU ld script
   Use the shared library, but some functions are only in
   the static library, so try that secondarily.  */
GROUP ( /lib/libc.so.6 /lib/libc_nonshared.a )

将/lib/绝对目录去掉, 既将"GROUP ( /lib/libc.so.6 /lib/libc_nonshared.a )"改为:
GROUP ( libc.so.6 libc_nonshared.a )

由于嵌入式系统存储功能的限制, 使用glibc生成的可执行文件比较大, 可以使用uClibc. 文末介绍了使用uClibc.


(五) 建立完整的GCC

前面建立的boot-gcc只支持c, 而且不能使用glibc运行时库, 现在我们已经可以建立完整的gcc, 支持c, c++, 运行时库.

配置GCC
$ cd $PRJROOT/build-tools/build-gcc
$ ../gcc-2.95.3/configure --target=$TARGET --prefix=$PREFIX --enable-languages=c,c++

编译并安装完整的GCC
$ make all
$ make install

这里安装的gcc将覆盖原来的gcc.


(六) 安装uClibc

到下载uClibc库. 当前最新版是. 下载uClibc包, 保存到build-tools目录. 使用uClibc可以不考虑gnu工具版本依赖问题, 找最新的uClibc用.

将它解压, 进入uClibc-0.2.98目录, 对uClibc进行配置. 注意: uClibc不支持在自身目录之外操作. 所以这里的配置, 安装不要新建安装目录, 就在uClibc本身的目录中进行.

配置uClibc
$ cd $PRJROOT/build-tools/uClibc-0.2.98
$ make CROSS=arm-linux- menuconfig

使用menuconfig工具进行配置, 这和linux内核配置很相. 实际上uClibc的配置系统来自Roman Zippel的内核配置系统.

CROSS设置为"arm-linux-".

进入熟悉的menuconfig界面后, 需要进行一些设置. 我有一块ARM9的开发板, 所以这里的配置都针对ARM920T. 这里只列出了部分设置. 具体可参考menuconfig中的帮助信息.

Target Architecture (arm)

Target Architecture Features and Options  --->
Target Processor Type (Arm 920T)
Target Processor Endianness (Little Endian)
[*] Target CPU has a memory management unit (MMU)
[*] Enable floating point number support
[*] Target CPU has a floating point unit (FPU)
[*]   Enable full C99 math library support
($(PRJROOT)/kernel/linux-2.4.21) Linux kernel header location
这里的内核头文件位置设置为先前包含内核头文件的路径.

General Library Settings  --->
[*] Generate Position Independent Code (PIC)
要想使用动态库, 必须选上PIC.
[*] Enable support for shared libraries

如果你想将所有的应用程序静态连接, 那么你可以关闭动态库支持. 但如果以后要用动态库时, 又得重新编译uClibc了(glibc也是如此). 最好选上对动态库的支持. 你可以在gcc选项中加上-static来使用静态库.
 
Library Installation Options  --->
(/lib) Shared library loader path
指定共享库的目录: 一般为/lib. 这样uClibc安装后将把共享库安装到/lib目录.

(/) uClibc runtime library directory

($(PRJROOT)/tools/uclibc) uClibc development environment directory

...

如同用menuconfig配置内核, 所有的配置都位于.config文件中. 将它删除就能恢复到缺省配置.

编译, 安装uClibc
$ make CROSS=arm-linux-
$ make CROSS=arm-linux- PREFIX="" install

所有的uClibc组件将被安装到$PRJROOT/tools/uclibc目录.

运行 $ ls -l /lib/*uC*看看:
-rwxr-xr-x  1 root root  21504 2006-05-28 21:28 ld-uClibc-0.9.28.so
lrwxrwxrwx  1 root root     19 2006-05-28 21:28 ld-uClibc.so.0 -> ld-uClibc-0.9.28.so
-rw-r--r--  1 root root 228420 2006-05-28 21:28 libuClibc-0.9.28.so

(七)最后的检查工作及总结

到目前为止, 工具链已经可以使用了. 下面的内容是为了熟悉工具链以及项目目录.

$ ls $PRJROOT/tools
有这些目录: arm-linux  bin  include  info  lib  man  share

arm-linux : 目标板专用文件.
bin          :   交叉开发工具
include   :    供交叉开发工具使用的头文件
info         :   gcc的info文件
lib           :    供交叉开发工具使用的链接库
man        :   交叉开发工具的在线说明手册
share      :  交叉开发工具与链接库共享的文件. 目前为空.

这里面最重要的就是bin和arm-linux目录
bin目录包含了交叉开发工具链所有的工具程序, 我们将会在主机上使用它们来为目标板开发正许.

arm-linux包含了用在目标板上的所有组件. 主要是目标板的头文件和运行时链接库. arm-linux包含下列目录:
...


参考资料
(1)《构建嵌入式Linux系统,o'reilly.
(2) IBM developerworks上的这篇如何为嵌入式开发建立交叉编译环境
(3)  "ARM cross-compiling howto"
(4)
(5)
阅读(2012) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~