`
buliedian
  • 浏览: 1193324 次
  • 性别: Icon_minigender_2
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

构建交叉编译工具链

阅读更多

构建交叉编译工具链

部分摘自《Building Embedded Linux Systems 》作者: Karim Yaghmour
刘建文略译并整理(http://blog.csdn.net/keminlau

KEY: 交叉编译 嵌入式 Linux C库 glibc

Buildroot自动构建交叉编译工具链

在过去很长的一段时间里,构建一套交叉编译工具链对于嵌入式开发者来说简直是一场恶梦,因为他们得手动跟踪各种源码包(及其更新包)之间的依赖关系。buildroot,和有名的微型C库——uclibc一起发布的小工具改变这一事实。

为啥复杂

Configuring and building an appropriate GNU toolchain is a complex and delicate operation that requires a good understanding of the dependencies between the different software packages and their respective roles. This knowledge is required, because the GNU toolchain components are developed and released independently from one another.

Buildroot是像Linux内核构建系统 类似的基于GNU make的软件构建系统。不过,Buildroot只包含构建所需的Makefiles和一些patches,没有待构建软件的源码,源码必须从网上动态下载。Buildroot主要是就用来构建[使用uClibc的交叉编译工具链 ]和根文件系统。

什么叫使用uClibc的交叉编译工具链?

首先要理解什么是编译工具链。编译工具链可简单理解为编译工具集,包括编译器、汇编器、链接器和C标准库。编译器负责将源代码转换为二进制机器码(或汇编代码),像gcc;汇编器和链接器等则负责【可执行文件】的构建,像binutils,中文为二进制工具集;C标准库是通用的机器码库,供链接器用,像 glibc。 从参与编译构建任务的角色看,前三者是【器具】,有操作的;最后者C库是【数据材料】。

接着理解什么是交叉编译。交叉的前提主机(HOST)与目标机(TARGET)使用不同的CPU体系,编译在主机上进行,生成目标机的机器码 。交叉编译工具链与本地编译工具链的区别,第一,交叉编译工具链的【编译器具】具有生成目标机机器码的功能;第二,交叉编译工具链的【C库】是目标机机器码库。

最后理解何为【使用uClibc的交叉编译工具链】变得很显然了。

此步引申出几个问题,第一,【编译器具】具有交叉编译性质需要做些什么?第二,目标机机器码C库如何生成?第三,uClibc与glibc的不同体现在哪里?

使用Buildroot

  • 创建你自己的载板支持(board support)
  • 离线构建
  • 源码树外(out of tree)构建
  • 环境变量
  • 目标机的根文件系统的定制
  • Busybox定制
  • uClibc定制
  • 创建基于单一buildroot源码树的多个项目
  • 于buildroot外使用uClicb工具链
  • 源码包的下载位置
  • 使用外部工具链
  • 扩展buildroot(工具软件部分)

使用详细请查看buildroot文档

手动构建交叉编译工具链

既然有了buildroot,还有必要学习如何构建工具链吗?答案当然有必要。我们是不建议重复发明轮子的,但如果我们不自己发明一次,我们永远不知道轮子是怎么来了。从职业的角度看,应该说不建议对外行的事物重新发明。

[嵌入式Linux开发 ]的第一步是构建(能编译运行在目标机平台的内核和应用程序的)交叉编译工具链,而[嵌入式Linux开发]第一步的第一步,是已经有一个生成这个交叉编译工具链的[本地工具链 ](native toolchain);第二步,是组织项目的工作目录。

本地工具链一般随发行版安装,如果没有选择安装,或者不小心损坏,可以从发行版的CD或网上取得安装包进行重要安装即可。

工作目录

在为你的嵌入式系统开发或定制各种软件的工作过程中,你需要用到很多的源码包和工具集。为这些源码包、工具集和其它[项目相关组件]定立一个清晰易理解的目录结构——项目工作空间(project workspace)是工作的第一步。下表是一个示范,你可以按需求适当调整,调整以直观为原则。另外,把你写的代码与从网上下载的源码分隔开,这样既可以减少源代码归属的迷惑,又可划清授权问题。

Table 4-1. Suggested project directory layout
Directory Content
bootldr 存放为嵌入式系统构建的不同版本的bootloader
build-tools 存放构建跨平台开发工具链的软件包
debug 调试工具及相关软件包
doc 嵌入式系统项目文档
images 存放待用的bootloader镜像、内核镜像和根文件系统镜像
kernel 存放为嵌入式系统构建的不同版本的内核
project 你编写的项目代码
rootfs 存放嵌入式系统内核运行时的根文件系统
sysapps 存放为嵌入式系统构建的不同版本的系统工具
tmp 临时文件
tools 完整的跨平台开发工具链和C库

以上的目录均隶属于你的project workspace内的子目录(当然以上目录还会有子目录),而project workspace的位置随你自己定。不过我建议你不要使用全局性质(system-wide )的目录,像/usr或/usr/local。可以使用/home或/home内的目录,这些目录可共享给用户组内所有用户。如果一定要用全局目录,你可以用/opt目录。

以下是~/control-project目录情况:

$ ls -l ~/control-project
total 4
drwxr-xr-x 13 karim karim 1024 Mar 28 22:38 control-module
drwxr-xr-x 13 karim karim 1024 Mar 28 22:38 daq-module
drwxr-xr-x 13 karim karim 1024 Mar 28 22:38 sysmgnt-module
drwxr-xr-x 13 karim karim 1024 Mar 28 22:38 user-interface

以上的情况特别一点,有四个嵌入式项目,每个项目都有一个工作目录,而每个工作目录都有上表的功能不同的子目录,比如:

$ ls -l ~/control-project/daq-module
total 11
drwxr-xr-x 2 karim karim 1024 Mar 28 22:38 bootldr
drwxr-xr-x 2 karim karim 1024 Mar 28 22:38 build-tools
drwxr-xr-x 2 karim karim 1024 Mar 28 22:38 debug
drwxr-xr-x 2 karim karim 1024 Mar 28 22:38 doc
drwxr-xr-x 2 karim karim 1024 Mar 28 22:38 images
drwxr-xr-x 2 karim karim 1024 Mar 28 22:38 kernel
drwxr-xr-x 2 karim karim 1024 Mar 28 22:38 project
drwxr-xr-x 2 karim karim 1024 Mar 28 22:38 rootfs
drwxr-xr-x 2 karim karim 1024 Mar 28 22:38 sysapps
drwxr-xr-x 2 karim karim 1024 Mar 28 22:38 tmp
drwxr-xr-x 2 karim karim 1024 Mar 28 22:38 tools

由于一些构建工具需要路径信息,你可以编写一小段的脚本完成这个任务,比如以下是daq-module项目的脚本develdaq :

export PROJECT=daq-module

export PRJROOT=/home/karim/control-project/${PROJECT}

cd $PRJROOT

执行它:$ . develdaq

构建工具链步骤

  1. 确定目标(Target)的名字
  2. 确定可用的Kernel/GCC/Glibc/Binutils版本号组合
  3. 确定工具目录
  4. 准备内核头文件
  5. 编译binutils
  6. 编译最小的自举式(Bootstrap)GCC
  7. 编译Glibc
  8. 编译全功能的GCC

确定目标(Target)的名字

target不是工具链的名字,而是工具链的性质,类型标识,决定工具链输出何种平台机器码。target的定义与本地HOST无关,只与目标机TARGET相关,由目标机的硬件平台(处理器体系)与软件平台(操作系统)组合定义。
以下是四常见的target:

  • arm-linux: Support for ARM processors such as armV4, armv5t, and so on.
  • mips-linux: Support for various MIPS core such as r3000, r4000, and so on.
  • ppc-linux: Linux/PowerPC combination with support for various PPC chips.
  • m68k-linux: This targets Linux running on the Motorola 68k processor.

完整的列表在: http://www.gnu.org

确定可用的Kernel/GCC/Glibc/Binutils版本号组合

这是构建工具链最麻烦的一步。安装文档、邮件列表是获取信息的地方。以下是对arm-linux可用的已知版本号组合:

  • ARM/Kernel 2.6/GCC 2.95.1,GCC 3.3/BINUTILS 2.10.x or
  • ARM/Kernel 2.4/GCC 2.95.1/BINUTILS 2.9.5

确定是否有更新包可用

确定好版本号后,确保该版本的Kernel/GCC/Glibc/Binutils是否有相应的更新包。

确定工具目录

如前我们定义的工作目录布局所得,工具链会在${PRJROOT}/build-tools 内编译构建,安装到{PRJROOT}/tools。为了方便我们编译,我们定义一些环境变量,并将其输出。以下是脚本develdaq 的内容:

export PROJECT=projectXX

export PRJROOT=/home/nakeman/${PROJECT}

export TARGET=i386-linux

export PREFIX=${PRJROOT}/tools

export TARGET_PREFIX=${PREFIX}/${TARGET}

export PATH=${PREFIX}/bin:${PATH}

cd $PRJROOT

TARGET:目标名字

PREFIX: prefix是前缀,语义不足,指路径前缀,而且这里默认指[工具链的安装目录] 的路径前缀。比如,本地工具链的prefix一般都是/usr,意思是说,你可以在BINDIR=$PREFIX/bin找到gcc,在 INCLUDEDIR=$PREFIX/include找到头文件。为了避免与本地工具链混淆,交叉工具链使用非/usr作为prefix。

安装工具链到/usr/local的一个问题

Some people prefer to set PREFIX to /usr/local. This results in the tools and libraries being installed within the /usr/local directory where they can be accessed by any user. I find this approach not to be useful for most situations, however, because even projects using the same target architecture may require different toolchain configurations.

为整个开发团队建置工具链

If you need to set up a toolchain for an entire development team, instead of sharing tools and libraries via the /usr/local directory, I suggest that a developer build the toolchain within an entry shared by all project members in the /home directory, as I said earlier. In a case in which no entry in the /home directory is shared among group members, a developer may build the toolchain within an entry in her workstation's /opt directory and then share her resulting ${PRJROOT}/tools directory with her colleagues. This may be done using any of the traditional sharing mechanisms available, such as NFS, or using a tar-gzipped archive available on an FTP server. Each developer using the package will have to place it in a filesystem hierarchy identical to the one used to build the toolchain for the tools to operate adequately. In a case in which the toolchain was built within the /opt directory, this means placing the toolchain in the /opt directory.

环境变量也能定义在.bashrc文件中,这样当你logout或换了控制台时,就不用老是export这些变量了。

有了以上的准备,现在可以开始构建工具链了。

编译 Binutils

1. 下载源码包(ftp://ftp.gnu.org/gnu/binutils/)并解包:

$ cd $PRJROOT/build-tools/src

$ tar -xzf binutils-2.10.1.tar.gz

另:如果有更新包,最好更新一下。

2. Configure :

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

配置脚本一般会检查编译环境,比如主机的一些资源,然后根据options(比如这里的目标target和安装位置prefix) 生成编译所需要makefile。

3. 编译:

$ make

4. 安装:

$ make install

binutils的组成及其使用

Utility Use
as gnu 的汇编器
ld The GNU linker
gasp gnu 汇编器预处理器
ar 产生、修改和解开一个archive 文件
nm 列出目标文件的符号和对应的地址
objcopy 将某种格式的目标文件转化成另外格式的目标文件
objdump 显示目标文件内容的信息
ranlib 为一个archive文件产生索引,并将这个索引存入archive文件中
readelf 显示 elf 格式的目标文件的信息
size 列出目标文件各个节的大小和目标文件的大小
strings 打印出目标文件中能打印的字符串,有个默认的长度,为4
strip 剥掉目标文件的所有的符号信息
c++filt C++ 和 java 中有一种重载函数,所用的重载函数最后会被编译转化成汇编的标号,c++filt 就是实现这种反向的转化,根据标号得到函数名
addr2line 将你要找的地址转成文件和行号,他要使用 debug 信息

准备内核头文件

编译GCC的第一步是准备好内核头文件(KEMIN:听起来怪怪的,用GCC编译GCC,编译器本身也一支程序,关键是理解编译一个编译需要些什么),交叉编译一工具链需要内核头文件。

1. 下载并解包到${PRJROOT}/kernel;

2. 如果有更新包,更新它;

3. 更改内核的ARCH属性,通过更改源码树顶层的makefie里的ARCH变量;

4. 配置内核:

make menuconfig

配置两个选项: System and processor type, and select a system consistent with the tools you’re building.

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

5. 拷贝

$ mkdir -p ${TARGET_PREFIX}/include

$ cp -r include/linux/ ${TARGET_PREFIX}/include

$ cp -r include/asm-i386/ ${TARGET_PREFIX}/include/asm

$ cp -r include/asm-generic/ ${TARGET_PREFIX}/include

注意,除非你的工具链更改了System OR processor type,否则内核配置是一次性的。

编译最小的自举式(Bootstrap)GCC

我们先编译一个最小的交叉C编译器,然后用它编译目标平台的glibc,最后编译全功能的GCC。这个过程前者的编译条件依赖后者,比如编译C++编译器需要glibc,有点像计算机自举启动。

1. 下载并解包,更新包如有必要:

$ cd ${PRJROOT}/build-tools

$ tar xvzf gcc-2.95.3.tar.gz

2. Con?gure:

$ cd build-boot-gcc

$ ../gcc-2.95.3/configure --target=$TARGET --prefix=${PREFIX} \

> --without-headers --with-newlib --enable-languages=c

配置选项除了target和prefix外,有三个与编译binuilts不同的选项:

  • --without-headers:因为这是一个交叉编译器,而目标机的系统库还没编译,glibc还没有编译;
  • --with-newlib:这个选项只指为了让编译顺利通过,暂时不指定任何库,因为glibc还没有编译;
  • --enable-languages=c :这个选项指定最小的GCC使用C语言。

3. Build:

$ make

4. Install:

$ make install

编译Glibc

了解glibc的人应该知道,glibc远不只是一个简单的通用语言代码库,它一个现代操作系统代码库;它除了实现标准C库(其中的libc)外,还封装了操作系统功能接口(POSIX标准),网络接口和线程操作接口等;绝大部分的应用程序都是通过glibc(链接glibc代码)使用操作系统功能的。另外,要区别glibc与内核的C库,内核开发是不用glibc的,内核内有一个小的C库实现。

1.下载(ftp.gnu.org/gnu/glibc)并解包:

$ cd ${PRJROOT}/build-tools

$ tar xvzf glibc-2.2.3.tar.gz

由于种种原因,glibc被分割成以glibc核心与add-ons的形式发布。在我们配置glibc时,如果打开add-ons,那么这个add-ons必需已经就绪(下载并解包在适当位置)。对嵌入式系统一般需要Linux threads add-on。

$ tar -xvzf glibc-linuxthreads-2.2.3.tar.gz --directory=glibc-2.2.3

2. Con?gure:

$ cd build-glibc

$ CC=$TARGET-linux-gcc ../glibc-2.2.3/configure --host=$TARGET \

> --prefix="/usr" --enable-add-ons=linuxthreads \

> --with-headers=${TARGET_PREFIX}/include

  • CC:与之前配置不同,编译glibc前修改了CC变量,这是因为这个glibc是编译给目标机用的,所以要用刚编译好的$TARGET-linux-gcc进行交叉编译。
  • --host:配置项使用--host而不是--target也说明了,这个库是运行在目标机,而不是本机。
  • --prefix:同样这个也是glibc在目标机的安装位置,而不是本机,本地不安装这个交叉编译出来的代码库。这个值用来提供给编译器把glibc的安装位置硬编码入 glibc,然后在本机编译安装时我们修改安装位置(请看下面安装一步),安装入目标机只需文件拷贝生成的文件。
  • --enable-add-ons:详细请查看配置文档;
  • --with-headers:glibc对内核头文件是有依赖的,如果这个选项不提供,那编译会先用/usr/include ,本机内核的头文件,这是与编译目标机的glibc是不符的。

一些其它可能用到配置的选项:

  • --disable-profile\--disable-shared :默认的编译会生成三组库文件:共享库、静态库和分析版(profiling information)的静态库。如果你的目标机没有很多的应用程序,可以选择只生成静态库。
  • --enable-static-nss:略;
  • --without-fp:略。

3. Build:

$ make

注意,由于glibc比较庞大,编译冗长耗时,如果编译过程发生错误,最先用make clean 清理掉中间结果再make。

4. Install:

$ make install_root=${TARGET_PREFIX} prefix="" install

如前所说,这个glibc这是安装给本机的应用程序执行用的,所安装位置特定,注意变量prefix先清空。

5. 最后一步是修改 libc.so 文件

将 GROUP ( /lib/libc.so.6 /lib/libc_nonshared.a)

改为

GROUP ( libc.so.6 libc_nonshared.a)

这样连接器 ld 就会在 libc.so 所在的目录查找他需要的库,因为你的机子的/lib目录可能已装了一个相同名字的库,这个是为编译本地应用程序的库,而不是用于交叉编译的库。

编译全功能的GCC

在建立boot-gcc 的时候,我们只支持了C。到这里,我们就要建立全套编译器,来支持C和C++。

$cd $PRJROOT/build-tools/build-gcc

$../gcc-2.95.3/configure --target=$TARGET --prefix=$PREFIX --enable-languages=c,c++

$make all

$make install

参考

  • 怎么为linux嵌入式研发建立交叉编译环境(2.4内核):http://www.sudu.cn/info/html/edu/20070101/294486.html
  • original:http://blog.csdn.net/keminlau/archive/2009/12/05/4945951.aspx
  • ARM 体系结构的《The GNU Toolchain for ARM Target HOWTO》
  • PowerPC 体系结构的《Linux for PowerPC Embedded Systems HOWTO》
  • The Scott Howard CrossGCC FAQ is available at http://www.sthoward.com/CrossGCC/.
  • The Bill Gatliff CrossGCC FAQ is available at http://crossgcc.billgatliff.com/.
  • crosgcc mailing list hosted by Red Hat at http://sources.redhat.com/ml/crossgcc/.
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics