1 引言 由于一般嵌入式开发系统存储容量有限,在裁减和定制Linux,运用于嵌入式系统前,通常需要在PC机上建立一个用于目标机的交叉编译环境,也就是将各种二进制工具程序集成为工具链,其中包括如GNU的链接器(ld)、GNU的汇编器(as)、ar(产生修改和解开一个存档文件)、C编译器(gcc)以及C链接库(glibc)。本文以在Linux系统上针对目标机arm为例,介绍了跨平台开发工具链的建立过程。 2 基本概念 2.1 什么是交叉编译? 简单地说,交叉编译就是在一个平台上生成在另一个平台上执行的代码。这里的平台包括体系结构(Architecture)和操作系统(OS)。同一个体系结构可以运行不同的操作系统,同样,同一个操作系统也可以在不同的体系结构上运行。举例来说,x86 Linux平台是Intel x86 体系结构和Linux for x86操作系统的统称。 2.2 为什么要用交叉编译? 原因有两个。一是目标平台所需要的bootloader以及OS核心还没有建立时,需要作交叉编译。二是目标机设备不具备一定的处理器能力和存储空间,即单独在目标板上无法完成程序开发,所以只好求助宿主机。这样可以在宿主机上对即将在目标机上运行的应用程序进行编译,生成可以在目标机上运行的代码格式,然后移植到目标板上,也就是目前嵌入式程序开发的Host/Target模式。 2.3 对于i386的理解 如果单纯说i386、i686,就是指平时所说的CPU类型。从Linux内核设计上讲,i386是架构,i486/586/686这些CPU的架构都是i386,所以很多linux方面的设计都是基于i386。简单地说,i386跟ppc,alpha,arm等放在一起时就是指架构,跟i586,i686放在一起指处理器型号,一个是横向的,一个是纵向的。 3 建立过程 3.1 选定软件版本 要想选用适当的版本,以保证建立的工具链可用,就必须找到适合主机和目标板的组合。这些可以自己测试,也可以从网上寻找已经测试过的版本组合,即binutils、gcc、glibc的版本组合。我用的宿主机为redhat-9.0,目标机arm,选择的版本如下: -------------------------------------------------------------------------------- binutils- gcc-core-2.95.3.tar.gz 包含GCC的主体部分。 gcc-g++2.95.3.tar.gz 可以使GCC编译C++程序。 glibc- glibc-linuxthreads- linux- -------------------------------------------------------------------------------- 你可以尝试选定更新的版本,编译无法通过时,依次使用较旧的版本。即时发现新版本组合能够编译成功,仍然需要测试建立的工具链是否可以使用。 你可以从FTP网站ftp://ftp.gnu.org/gnu/或者任何其他的镜像网站下载GNU工具链的各个组件:binutils包位于binutils目录,gcc包位于gcc目录,而glibc包与glibc-linuxthreads包放在glibc目录。下面给出上面选用的各个版本的下载路径。 -------------------------------------------------------------------------------- binutils- ftp://ftp.gnu.org/gnu/binutils/binutils- gcc-core-2.95.3.tar.gz ftp://ftp.gnu.org/gnu/gcc/gcc-2.95.3/gcc-core-2.95.3.tar.gz gcc-g++2.95.3.tar.gz ftp://ftp.gnu.org/gnu/gcc/gcc-2.95.3/gcc-g++-2.95.3.tar.gz glibc- ftp://ftp.gnu.org/gnu/glibc/glibc- glibc-linuxthreads- ftp://ftp.gnu.org/gnu/glibc/glibc-linuxthreads- linux- ftp://ftp.kernle.org/pub/linux/kernel/v2.4/linux- ftp://ftp.arm.linux.org.uk/pub/linux/arm/kernel/v2.4/patch- -------------------------------------------------------------------------------- 3.2 建立工作目录 我的用户名为lqm,所以所有的工作都在/home/lqm下面建立完成。 ************************************************************ $cd /home/lqm 进入工作目录 $pwd 查看当前目录 /home/lqm $mkdir embedded-system 创建工具链文件夹 $ls 查看/home/lqm建立的所有文件 embedded-system ************************************************************ 现在已经建立了顶层文件夹embedded-system,下面在此文件夹下建立如下几个目录: -------------------------------------------------------------------------------- setup-dir 存放下载的压缩包 src-dir 存放binutils、gcc、glibc解压之后的源文件 kernel 存放内核文件,对内核的配置和编译工作也在此完成 build-dir 编译src-dir下面的源文件。这是GNU推荐的源文件目录与编译目录分离的做法。 tool-chain 交叉编译工具链的安装位置 program 存放编写程序 doc 说明文档和脚本文件 -------------------------------------------------------------------------------- 下面建立目录,并且下载源文件。 ************************************************************ $pwd /home/lqm/ $cd embedded-system $mkdir setup-dir src-dir kernel build-dir tool-chain program doc $ls build-dir doc kernel program setup-dir src-dir tool-chain $cd setup-dir $wget ftp://ftp.gnu.org/gnu/binutils/binutils- $wget ftp://ftp.gnu.org/gnu/gcc/gcc-2.95.3/gcc-core-2.95.3.tar.gz $wget ftp://ftp.gnu.org/gnu/gcc/gcc-2.95.3/gcc-g++-2.95.3.tar.gz $wget ftp://ftp.gnu.org/gnu/glibc/glibc- $wget ftp://ftp.gnu.org/gnu/glibc/glibc-linuxthreads- $wget ftp://ftp.kernel.org/pub/linux/kernel/v2.4/linux- $wget ftp://ftp.arm.linux.org.uk/pub/linux/arm/kernel/v2.4/ patch- $ls binutils- patch- $cd ../build-dir $mkdir build-binutils build-gcc build-glibc 建立编译目录 ************************************************************ 3.3 输出环境变量 在建立与使用某些工具程序时,可能会用到这些目录的路径,如果设计一个简短的命令脚本,设定适当的环境变量,则可以简化操作过程。下面就建立命令脚本hjbl: ************************************************************ $pwd /home/lqm/embedded-system/build-dir $cd ../doc $mkdir scripts $cd scripts $emacs hjbl 用文本编辑器emacs编译环境变量脚本 -------------------------------------------------------------------------------- 在随后打开的emacs编辑窗口中输入下面内容(如果在命令行界面下,则必须要用到vi文本编辑器,emacs则不可以): export PRJROOT=/home/lqm/embedded-system export TARGET=arm-linux export PREFIX=$PRJROOT/tool-chain export TARGET_PREFIX=$PREFIX/$TARGET export PATH=$PREFIX/bin:$PATH 保存后关闭emacs窗口,如果要在目前的窗口中执行此脚本,即让环境变量生效,还需要执行下面的语句: -------------------------------------------------------------------------------- $. hjbl(注意:在点和hjbl之间有一个空格) $cd $PRJROOT 验证环境变量是否生效 $ls build-dir doc kernel program setup-dir src-dir tool-chain ************************************************************ 该环境变量的作用时间仅仅在Terminal当前窗口,如果将窗口关闭,开启一个新的窗口,则环境变量实效,需要重新执行下面的命令: $. /home/lqm/embedded-system/doc/scripts/hjbl 说明: TARGET变量用来定义目标板的类型,以后会根据此目标板的类型来建立工具链。参看表1。目标板的定义与主机的类型是没有关系的,但是如果更改TARGET的值,GNU工具链必须重新建立一次。 PREFIX变量提供了指针,指向目标板工具程序将被安装的目录。 TARGET_PREFIX变量指向与目标板相关的头文件和链接库将被安装的目录。 PATH变量指向二进制文件(可执行文件)将被安装的目录。 表1 TARGET变量值
3.4 内核头文件的配置 内核头文件的配置是建立工具链的第一步。它与后面将要执行的其他步骤有着类似性,大多需要执行下面几步操作: 1、 解压缩包 2、 为跨平台开发设定包的配置 3、 建立包 4、 安装包 ************************************************************ $pwd /home/lqm/embedded-system/ $cd kernel $tar xvzf ../setup-dir/ linux- $gunzip ../setup-dir/ patch- $cd linux- $patch –p1 < ../../setup-dir/patch- $make ARCH=arm CROSS_COMPILE=arm-linux- menuconfig 配置 $make dep -------------------------------------------------------------------------------- 变量ARCH和CROSS_COMPILE的值与目标板的架构类型有关。如果使用PPC目标板,则ARCH=ppc CROSS_COMPILE=ppc-linux-。如果使用i386目标板,则ARCH=i386 CROSS_COMPILE=i386-linux-。 make menuconfig是以文本菜单方式配置。 make xconfig是以图形界面方式配置。 make config是纯文本方式界面配置。 一般选择make menuconfig,注意在选项System Types中选择正确的硬件类型。配置完退出并保存,检查一下的内核目录中的 kernel/linux- 然后,建立工具链需要的include目录,并将内核头文件复制过去。 -------------------------------------------------------------------------------- $cd include $ln -s asm-arm asm #可以查看一下,经过编译可以自动生成。如果已经生成连接,则不必写 $cd asm $ln -s arch-epxa arch #同上说明 $ln -s proc-armv proc #同上说明 #这些是针对makefile文件作出的修改 $mkdir –p $TARGET_PREFIX/include $cp –r $PRJROOT/kernel/linux- $cp –r $PRJROOT/kernrl/linux- ************************************************************ 注意: 1、不必再每次重新设定内核配置之后重建工具链,除非你变更了处理器或系统的类型。工具链只需要一组可供目标板使用的有效头文件即可,这些头文件在前面的程序中早就已经提供了。 2、asm-linux文件夹放到目标文件夹$TARGET_PREFIX/include/时要更改名称为asm,因为配置文件的include包含都是<asm/*.h>方式。这也是交叉编译的不同之处。否则就会出现类似下面的错误提示: -------------------------------------------------------------------------------- ......... done _udivsi3 _divsi3 _umodsi3 _modsi3 _dwmd_lnx libgcc1.s:438:asm/unistd.h:No such file or directory make [1] *** [libgcc1-asm.a] error 1 -------------------------------------------------------------------------------- 3.5 binutils(二进制工具程序)的设置 binutils包中的工具常用来操作二进制目标文件。该包中最重要的两个工具就是GNU汇编器as和链接器ld。 ************************************************************ $cd $PRJROOT/src-dir $tar xvzf ../setup-dir/binutils- $cd $PRJROOT/build-dir/build-binutils $../../src-dir/binutils- $make $make install $ls $PREFIX/bin 验证安装的结果是否正确 arm-linux-addr2line arm-linux-ld arm-linux-readelf arm-linux-ar arm-linux-nm arm-linux-size arm-linux-as arm-linux-objcopy arm-linux-strings arm-linux-c++filt arm-linux-objdump arm-linux-strip arm-linux-gasp arm-linux-ranlib ************************************************************ 注意:每个工具的文件名的前缀都是前面为TARGET变量设定的值。如果目标板是i386-linux,那么这些工具的文件名前缀就会是i386-linux-。这样就可以根据目标板类型找到正确的工具程序。 3.6 初始编译器的建立 开始只能建立支持C语言的引导编译器,因为缺少C链接库(glibc)的支持。等到glibc编译好之后,可以重新编译gcc并提供完整的C++支持。 ************************************************************ $cd $PRJROOT/setup-dir $mv gcc-core-2.95.3.tar.gz gcc-2.95.3.tar.gz #重命名 $cd $PRJROOT/src-dir $tar xvzf ../setup-dir/gcc-2.95.3.tar.gz $cd $PRJROOT/build-dir/build-gcc $../../src-dir/gcc-2.95.3/configure --target=$TARGET --prefix=$PREFIX --without-headers -------------------------------------------------------------------------------- 因为是交叉编译器,还不需要目标板的系统头文件,所以需要使用 --without-headers这个选项。--enable-language=c用来告诉配置脚本,需要产生的编译器支持何种语言,现在只能支持C语言。--disable-threads是因为threads需要glibc的支持。 准备好了Makefile文件,进行编译之前,需要修改src-dir/gcc-2.95.3/gcc/config/arm/t-linux文件,在TARGET_LIBGCC2_CFLAGS中添加两个定义:-Dinhibit_libc -D__gthr_posix_h,否则会报错。 -------------------------------------------------------------------------------- $make $make install ************************************************************ 3.7 建立C库(glibc) 这一步是最为繁琐的过程。目标板必须靠它来执行或者是开发大部分的应用程序。glibc套件常被称为C链接库,但是glibc实际产生很多链接库,其中之一是C链接库libc。因为嵌入式系统的限制,标准GNU C链接库显得太大,不适合应用在目标板上。所以需要寻找C链接库的替代品,比如uClibc。在这里,现以标准GNU C为例建立工具链。 ************************************************************ $cd $PRJROOT/src-dir $tar xvzf ../setup-dir/glibc- $tar xvzf ../setup-dir/glibc-linuxthreads- $cd $PRJROOT/build-dir/build-glibc $CC=arm-linux-gcc ../../src-dir/glibc- $make $make install_root=$TARGET_PREFIX prefix=”” install -------------------------------------------------------------------------------- 在这里设定了install_root变量,指向链接库组件目前所要安装的目录。这样可以让链接库及其头文件安装到通过TARGET_PREFIX指定的与目标板有关的目录,而不是建立系统本身的/usr目录。因为之前使用--prefix选项来设定prefix变量的值,而且prefix的值会被附加到install_root的值之后,成为链接库组件的安装目录,所以需要重新设定prefix的值。这样所有的glibc组件将会安装到$TARGET_PREFIX指定的目录下。 -------------------------------------------------------------------------------- $cd $TARGET_PREFIX/lib $cp ./libc.so ./libc.so.orig -------------------------------------------------------------------------------- 编辑文件libc.so,更改如下: /* GNU ld script Use the shared library,but some functions are only in the static library,so try that secondarily.*/ GROUP(libc.so.6 libc_nonshared.a) -------------------------------------------------------------------------------- ************************************************************ 3.8 完整编译器的设置 现在可以为目标板安装支持C和C++的完整编译器了。这个步骤相对于前面来建立过程要简单一些。 ************************************************************ $cd $PRJROOT/build-dir/build-gcc $../../src-dir/gcc-2.95.3/configure --target=$TARGET --prefix=$PREFIX $make all $make install ************************************************************ 3.9 完成工具链的设置 ************************************************************ $cd $TARGET_PREFIX/bin $file as ar gcc ld nm ranlib strip 查看文件是否为二进制文件 $arm-linux-gcc -print-search-dirs 查看缺省的搜寻路径 $mv as ar gcc ld nm ranlib strip $PREFIX/lib/gcc-lib/arm-linux/2.95.3 转移文件 $for file in as ar gcc ld nm ranlib strip >do >ln -s $PREFIX/lib/gcc-lib/arm-linux/2.95.3/$file >done ************************************************************ 3.10 使用工具链 下面编写一个简单的C程序,使用建立的工具链。、 ************************************************************ $cd $PRJROOT/program $emacs hello.c -------------------------------------------------------------------------------- 在文本编辑器emacs中编写: #include <stdio.h> int main() { int i; for(i=1;i<9;i++) printf(“Hello World %d times!\n”,i); } 保存退出 -------------------------------------------------------------------------------- $gcc -g hello.c -o hello $gdb (gdb)file hello (gdb)l #include <stdio.h> int main() { int i; for(i=1;i<9;i++) printf(“Hello World %d times!\n”,i); } (gdb)r (gdb)q $arm-linux-gcc -g hello.c -o hello-linux $file hello-linux hello-linux:ELF 32-bit LSB executable,ARM,version 1(ARM),for GNU/Linux ************************************************************ 上面的输出说明你编译了一个能在 arm 体系结构下运行的 hello-linux,证明你的编译工具做成功了。 4 总结 通过上面的操作,已经能够建立全功能的跨平台开发工具链,在以后的嵌入式开发中将会经常用到。 说明:-----之间为说明文字 ***之间为源程序 参考: [1] http://www./arm/arm-2004.htm [2] 构建嵌入式LINUX系统,中国电力出版社 [3] http://www-128.ibm.com/developerworks/cn/linux/l-embcmpl/ 如何为嵌入式开发建立交叉编译环境,梁元恩 [4] http://www./oldsite/armlinux//docs/toolchain/toolchHOWTO/t2.html The GNU Toolchain for ARM targets HOWTO |
|