交叉编译工具链介绍《Building Embedded Linux Systems》
1.前言
配置和编译一个合适的GNU工具链是相对复杂的并且需要很精细的操作,包括你需要对不同软件库之间的依赖关系、它们的各自的任务,不同软件库版本情况都有比较好的了解,编译工具链是一个乏味的工作。
2.制作之前需要了解的一些术语与名称
1)build:你编译你的工具链时所使用的编译系统。
2)host:交叉编译工具链运行在的主机系统。
3)target:你的交叉编译工具链所生成的可执行文件所要运行的目标系统。
在一些通用非嵌入式的使用,以上三个必须是一样的。但是大部分嵌入式开发中,build跟host是使用相同的机器(开发者的工作空间)。而target则是你所开发应用软件所要运行的嵌入式目标板。
当我们需要使用GNU配置来为各种交叉工具链组件搭建软件和系统时,我们需要通过用GNU configuration files对build,host,target
系统进行命名,GNU configuration files的标准格式如下:
1)cpu-manufacturer-kernel-os(内核部分):这个是个可选选项,其中指明了3部分:
① cpu:系统的芯片架构。(习惯上在小端变体的架构名称额外加上el)。
② manufacturer:具体的制造商或者使用上述CPU的开发板家族。(这个对交叉编译工具链的影响很小,很多时候都直接指明为unknown 机器类型或者简单的省略机器描述)。
③ kernel:主要用于GNU/Linux 系统,甚至有时候在一些情况下为了简洁而忽略掉。
④ os :在系统上使用的操作系统(或ABI)名称。configuration将被用于描述所有种类的操作系统,包括没有运行任何操作系统的嵌入式系统,这种情况下这个区域用来指明工程文件格式,比如Elf或者COFF。
下面是两个简单的例子:
i386-pc-linux-gnu
APC-style x86 Linux system
powerpc-8540-linux-gnu
AFreescale 8540 PowerQuickIII Linux system
mips-unknown-linux
A big-endian MIPS Linux system from an unspecified manufacturer
mipsel-linux
A little-endian MIPS Linux system from an unspecified manufacturer
xscale-unknown-linux
An XScale (formely StrongARM) Linux system from an unspecifiedmanufacturer
在很多典型情况下,交叉编译工具链的工具都是以目标三组合作为前缀的。比如,一个用于arm,linux系统的交叉编译工具将被命名为arm-linux-gnu-gcc(其中gcc是GNU编译系列的可执行文件名)。但是如果连接器是用于little-endian MIPS Linux系统将被命名为mipsel-linux-ld(ld是GNU链接器的可执行文件名)。
3.编译toolchain的成分要求
1)Linux kernelheaders
C库是toolchain的一部分,封装了目前很多可用的API,编译这个库需要Linux kenel header files的一个子集用来描述内核API。
理论上来说,toolchain使用的Linuxkernel headers来自的Linux内核版本要与用在目标板上的linux版本相一致。但是实际上,因为Linux kernel的ABI很少改变,使用不同版本但是相似的headers是司空见惯的事。
在2.6版本发行之前,C库编译是根据逐字的拷贝在linux内核目录include/asm-architecture和include/linux 的头文件。而到了linux2.6发布了,就不再支持上述方式了,因为内核头文件包含了太多代码所以不适合包含在用户应用空间,而且包含C库会容易中断用户程序的编译。取代这种方式的是:使用一个sanitized的Linux内核头文件版本,适合代替C库用于用户代码空间。自从linux2.6.23内核之后,内核源配备了一耳光自动Make target用于建立类似“sanitized”linux kernelheaders版本。
从内核源目录,简单执行以下命令,把ppc改成你的体系架构,headers/改成你要把sanitized headers安装所在目录的路径。
$make ARCH=ppc headers_check
$make ARCH=ppc INSTALL_HDR_PATH=headers/ headers_instal;
2)binutilspackage
这个包包含了很多工具经常用于操作那些二进制对象文件(binary object files)。其中这个包里最重要的两个工具是GNU assembler(as),linker(ld)。以下表包含了在binutils包里所能找到的工具:
3)the C library
标准C库大部分经常用于当前日常Linux系统的是the GNU C library。而glibc作为精简版的C库。glibc是一个可移植,高效的C库,支持所有相关标准(ISO C99,POSIX.1c,POSIX.1j,POSIX.1d,Unix98,和单一的Unix规范)。
glibc整个开发工程(包含链接到开发源码树,bug数据库,和许多资源)主网站:
http://www.gnu.org/software/libc
这个库所支持的平台列表可以在以下网站查询:
http://www.gnu.org/software/libc/ports.html
库本身可以在以下网站下载到:
注意:那些glibc-后面没有加ports的包是不支持Arm和MIPS的,在支持了arm和MIPS的库glibc-后面都会加上ports后缀,可以通过上述相同路径下载到。假如项目要求更小的RAM,可以考虑使用一个有名的嵌入式替代物比如uClibc和dietlibc。
4)the threadinglibrary
线程是一种很流行的现代编程技术包含了几个独立,异步的任务但是属于同一个进程地址空间。Linux内核在2.6版本之前对线程提供了很少的支持,为了填补这样的缺陷,一些不同的线程库被开发,其中最通用的是LinuxThreads library。在Linux2.6之后就伴随着一个新的线程实现,称为the New POSIX Threading Library(NPTL)。NPTL依赖与linux内核对线程的支持。NPTL现在已经被Linux threading library所支持了,并且被分配作为最近glibc版本的一部分。
我们可以开始使用LinuxThreads和移植到NPTL上去,因为这两个都符合POSIX标准。
可以在运行期间通过调用confstr()函数来测试出哪种线程库被执行:
#define_XOPEN_SOURCE
#include<unistd.h>
#include<stdio.h>
intmain(void)
{
char name[128];
confstr (_CS_GNU_LIBPTHREAD_VERSION, name,sizeof(name));
printf ("Pthreads lib is: %s\n",name);
return 0;
}
5)componentversions
在构建工具链之前需要为你将要用到的每一个部件(GCC,glibc 和binutils)选择合适的版本。并不是每个部件的所有的版本和不同部件的所有版本都能结合构建出合适的工具链。可以使用每一个部件的最新版本但是也不能保证就一定能工作。
为了选择合适的版本,我们需要测试出一个组合来适合我们的host和target。可以使用别人以前测试通过了的组合,如果没有,那就需要自己来测试了,先为每一个package选择最新稳定的版本,然后进行测试,如果构建失败了就用上一个版本来代替继续测试,进行相同的操作直到测试出合适的组合。
注意:一般情况下,最高版本可能没有经过足够时间的测试就被发布出来,这样的版本其实不能认为是稳定的版本。所以例如:glibc2.3被发布出来,那么,我们最好选择glibc2.2.5直到glibc2.3.1开始有效。
这里举个测试的例子:假如binutils的最新版本是2.1.8,GCC的最新版本是4.2.2,glibc的最新版本是2.7。通常情况下binutils是可以build成功的并且我们不需要去改变它,然后当我们build GCC4.2.2时,假如失败了尽管所有合适的配置标识已经被提供了。在这种情况下,我们就需要选择GCC4.2.1来测试,重复着这样的方法直到成功为止。但是需要注意的是;有时候最新版本的package期望其他package提供某些功能,所以有时候是需要在你前一个package成功build而下一个package的所有版本build失败后,对上一个成功build的package版本进行回溯。比如上述例子中,我们必须回溯到glibc2.6.0或许就可以适合于GCC4.1和binutils2.17,尽管很多近期的GCC和binutils可以被完美结合。
另外,应用补丁到一些版本来让它们正确为我们的目标编译。
每当你发现一个新版本结合编译成功,测试你最终的工具链来确定这个工具链确实有效。因为有些版本结合虽然可以编译成功但是在使用的适合却依然失败。而且在某些情况下,一个版本组合被发现可以用于一个处理器家族的某个处理器,但是在同个处理器家族的其他某些处理器又工作不起来。
6)Additionalbuild requirements
为了build cross-platform development toolchain,我们需要一个本机工具链的功能。很多主流发行版提供了这个工具链作为它们packages的一部分。假如这个攻击力没有被安装在你的工作空间或者你选择不安装它来节省空间,你需要安装它。
4.build主要步骤涉及内容
1)Linux headers
2)binary utilities
3)the bootstrap compiler
4)the C library
5)the full compiler
其中你可能会注意到一点是,compiler好像被built了两次。这是正常的也是必须的,因为一些GCC支持的语言(比如C++)要求glibc支持。因此,a bootstrap compiler 被built只是用于支持C,而full compiler被built一旦C库有效了。
尽管我们把Linux headers作为第一步,但是headers在C库被设置之前是没有被使用的,所有我们可以修改这个步骤只要是在C库被设置之前就行了。
每一个步骤都涉及到许多它自己本身的迭代。但是,这些步骤在一些行为里是保持相似的。很多的工具链构建步骤包含了一下几个行为:
①解压包
②为交叉平台开发配置包
③build包
④安装包
一些工具链builds和上面序列有细微的不同。比如:the Linux headers没有要求我们去build或者安装thekernel。因为编译器已经卸载了the bootstrap compiler的设置,所以the full compiler 设置不需要再被要求卸载GCC包.
5.工作空间设置
1)根据早期工作空间目录层次建议,工具链将被built 在${PRJROOT}/tools 目录。之后我们还需要定义一些额外的环境变量。基于这些已经被定义的环境变量使build过程变得方便了很多。以下是关于一个新develdaq脚本用于设置新环境变量:
exportPROJECT=daq-module
exportPRJROOT=/home/gby/bels/control-project/${PROJECT}
export TARGET=powerpc-unknown-linux或者是arm-linux
exportHOST=i686-cross-linux-gnu
exportPREFIX=${PRJROOT}/tools
exportTARGET_PREFIX=${PREFIX}/${TARGET}
exportPATH=${PREFIX}/bin:${PATH}
cd$PRJROOT
PROJECT变量:是工程名,可以随便写
PRJROOT变量:这个工程的绝对路径
TARGET变量:定义了你的工具链将被编译用于的目标平台的体系架构。
HOST变量:定义工具链所运行的主机平台体系架构。
PREFIX变量:提供组件配置脚本指明了你想要把目标工具安装在的目录。
TARGET_PREFIX变量:用于目标依赖头文件和库的安装。
PATH变量:为了可以使用新安装的工具,我们需要修改PATH变量来指明这些二进制文件将要被安装到的目录。
6.资源
在进行时间的制作工具链之前,让我们来看看一些你或许可以发现有用的资源在你制作过程中碰到问题时。
1)最重要的,每个包都有自己的文档。在GCC里面,我们可以找到一个FAQ文件和一个install 目录包含了一些如何配置和安装GCC的说明书。这些包含了大量的编译配置选项的说明。相似的,glibc包也包含了FAQ和INSTALL文件。INSTALL文件涵盖了编译配阿紫选项和安装过程,并且它提供了使用编译工具版本的建议。
2)另外,我们也许想要尝试使用一般的搜索引擎比如Google来寻找其他开发者已经遇到并解决的跟你相似的问题的记录。通常,这是一个很有效的方法来解决用GNU工具链的构建问题。
3)一个非常有用的资源是Cross-Compiled Linux From Scratch(CLFS)(http://trac.cross-lfs.org)已经在一些例子中被使用的组件组合版本的工具链构建已经有很多被提供在这个资源里。
CLFS:CLFS项目提供指导你逐步构建你自己定制的Linux系统全部通过使用原始资料。优点有以下几个:
教人们如何构建一个交叉编译器
教人们如何利用multilib系统
教人们Linux系统内部是如何工作的
构建CLFS来产生一个非常紧凑的Linux系统
在很多Unix风格操作系统构建CLFS
CLFS是非常灵活的
CLFS提供额外的安全性