方永、南天紫云

道亦有道

lfs简要解析
2011年08月06日

出处:http://www.vinoca.org/2011/08/06/lfs简要解析

最近编译了几遍lfs,对构造工具链时binutils和gcc编译两遍印象颇深,

也很不理解,在网上查阅,也没有得到什么结果,反而被一些观点弄得更加迷惑了。靠人不如靠自己,遂仔细阅读lfs提供的文档。品读之后才发现原文档是自注释的,一切都解释得很清楚,一切原来很简单,只是我们自己把它弄复杂了。

lfs就是从源码完完全全地编译出来一个系统,既然是一个系统,存在于分区中最好,所以第二章介绍了怎么分区;既然需要源码,第三章列出了编译这个系统所需要的必要的程序源码及下载地址;既然是编译源代码,那么初始的二进制的编译器是必需的,所以得准备一个包含一套编译工具的系统,把这个系统称之为宿主系统,这是编译的起点和母体。对这个母体,那是既爱又恨呀,没有她,lfs无从生起,有了她,就有了她的血(glibc提供的库)和肉(那套编译工具)。怎么办呢,必须得弄个血肉工厂为lfs提供与母体没有联系的生命材料,这就是/tools里的那些东西。所以从开始,就需要与宿主系统有适当地隔离,所以第四章说明怎么新建一个lfs用户及为这个用户配置环境变量。.bashrc中PATH=/tools/bin:/bin:/usr/bin这句很妙,它既保证我们能够利用到宿主系统的工具,又隔离了它们,因为一开始/tools/bin并没有东西,所以会在/bin及/usr/bin里面找到所需的工具提供给程序,等我们编译出了bintuils及gcc并放在了/tools里时,PATH变量就会指示:“用/tools/bin里的!”。每五章,构造用来整个lfs系统的工具链,这是至关重要的一步。在这里,我们把binutils和gcc编译了两遍。为什么要这么做呢?书中有说:

Slightly adjusting the name of the working platform, by changing the “vendor” field target triplet by way of the LFS_TGT variable, ensures that the first build of Binutils and GCC produces a compatible cross-linker and cross-compiler. Instead of producing binaries for another architecture, the cross-linker and cross-compiler will produce binaries compatible with the current hardware.

The temporary libraries are cross-compiled. Because a cross-compiler by its nature cannot rely on anything from its host system, this method removes potential contamination of the target system by lessening the chance of headers or libraries from the host being incorporated into the new tools. Cross-compilation also allows for the possibility of building both 32-bit and 64-bit libraries on 64-bit capable hardware.

Careful manipulation of gcc’s specs file tells the compiler which target dynamic linker will be used

第一遍编译binutils和gcc用LFS_TGT这个变量确保binutils和gcc是和当前硬件兼容的交叉链接器和编译器。

第一遍编译glibc的目标是确保lfs系统不受到宿主系统的影响。

注意调整gcc的specs文件那一步,确保之后编译出来的二进制文件能正确地使用动态链接器(即ld-linux.so.2,gcc将把动态链接器的位置编译进二进制文件中)。

而binutils和gcc编译两遍中第一遍的目的,一是为了交叉编译出glibc,另一个目的是编译出它们自身。嗯,还是glibc、binutils、gcc这三个家伙,真正的目的就是隔离。这里需要详细说明一下:

第一遍编译binutils和gcc时,用LFS_TGT指示生成的binutils和gcc放在哪儿,名字是什么样的,在x86的机器上,对于gcc名字就是i686-lfs-linux-gnu-gcc,而ld(binutils中最重要的一个程序)就是i686-lfs-linux-gnu-ld,这是在$LFS/tools/bin中,在$LFS/tools/i686-lfs-linux-gnu/bin中也有gcc和ld,这是configure确保生成的文件能够在使用时被找到的一种机制。在随后编译glibc binutils gcc这三件套configure时明确指明使用i686-lfs-linux-gnu-这种名字的链接器和编译器。

换一种角度叙述一下:首先,我们用宿主系统的编译器和链接器编译了一遍binutils和gcc,这时生成的ld会加载宿主系统的库,而生成的gcc会编译一个文件时会写入/lib/ld-linux.so.2,这也是宿主系统动态链接器的位置,然后我们用生成的gcc和binutils生成了glibc,glibc中既有库文件也有执行文件,不管是库文件还是执行文件都是自我依赖的,也就是先生成没有任何依赖的那些库文件,再生成需要依赖的库和执行文件依赖到先生成的库文件上。好了,有了自己的血了,虽然还没有和母体撇清楚,再编译binutils和gcc,用我们自己的血!这时,我们修改了i686-lfs-linux-gnu-gcc的specs文件,指明以后再编译出的文件写入的是/tools/lib/ld-linux.so.2,也就是使用新生成的glibc中的动态链接器,这样以后的程序用的就是新生成的库了。然后编译binutils,需要注意的是把ld子目录中的源码重新编译了一遍,为什么要重新编译呢?因为在configure时不能指定LIB_PATH这个变量,在configure时是交叉编译,指定这个变量就会在编译时用到宿主系统的库文件。这样,工具链中的binutils便制作完成了,它用上了/tools中的库,并且ld也指向了这个库。然后依次编译gcc tcl及DejaGNU等程序,使它们都用上/tools中的库,并且安装后存在于/tools目录中,这些程序都在随后编译lfs系统所必需的。

这样,我们的工具链便制作完成了,它所有的程序和库都依赖它自己库文件,不再与宿主系统有依赖关系了。这正是我们需要的。

在第六章,我们创建了一些目录、文件和链接,然后chroot,用生成的工具链生成整个系统。这个应该没什么疑问,不这样做,很多程序会运行失败。

整个编译过程完成后加个启动脚本,安个内核,再配置一下,一个可启动的系统就制作完成了。lfs,多么神奇的一本书啊!试想一下,如果没有lfs,即使我们知道这些,会卡住多少次,而有些步骤相互依赖性之强,最终会做出一个系统吗?再次感谢lfs的项目成员。