构造基于x86_64的LFS(2)
继续构造LFS,从构建交叉工具链和临时工具开始
编译交叉工具链
确认环境变量$LFS无误,随后切换到$LFS/sources目录下
1 | echo $LFS |
Binutils
第一个要构建的软件包是Binutils,因为在编译GCC前,GCC需要根据Binutils来决定启用哪些功能
先解压Binutils
1 | tar -xf binutils-2.43.1.tar.xz |
创建一个新目录来构建Binutils,这个目录在binutils-2.43.1下
1 | mkdir -v build |
准备编译Binutils
1 | ../configure --prefix=$LFS/tools \ |
开始编译,在编译完成后安装该软件包
1 | make && make install |
GCC
现在可以构建GCC了,但是是Freestanding GCC,因为我们没有Glibc,更多内容可以看FAQ
这个GCC是一个交叉编译器
解压GCC
1 | cd $LFS/sources/ |
GCC依赖GMP、MPFR 和 MPC 这三个包,需要解压它们并重命名解压出的目录,这里依然在gcc-14.2.0目录下继续操作 ,完成后,gcc-14.2.0目录下应该多出了mpfr,gmp,mpc目录
1 | tar -xf ../mpfr-4.2.1.tar.xz |
对于 x86_64 平台,还要设置存放 64 位库的默认目录为 “lib”
1 | case $(uname -m) in |
创建一个用于构建GCC的新目录
1 | mkdir -v build |
准备编译GCC
1 | ../configure \ |
开始编译GCC,编译完成后安装GCC
1 | make && make install |
刚刚构建的 GCC 安装了若干内部系统头文件。其中的 limits.h 一般来说,应该包含对应的系统头文件 limits.h,在我们的 LFS 环境中,就是 $LFS/usr/include/limits.h。然而,在构建 GCC 的时候,$LFS/usr/include/limits.h 还不存在,因此 GCC 安装的内部头文件是一个不完整的、自给自足的文件,不包含系统头文件提供的扩展特性。这对于构建临时的 Glibc 已经足够了,但后续工作将需要完整的内部头文件。使用以下命令创建一个完整版本的内部头文件,该命令与 GCC 构建系统在一般情况下生成该头文件的命令是一致的
1 | cd .. |
Linux API
安装Linux API头文件,解压源码后进入目录,这边虽然解压了整个内核,但目前只需要其中的API头文件供GLibc使用
1 | cd $LFS/sources/ |
确保软件包中没有遗留陈旧的文件
1 | make mrproper |
从源代码中提取用户可见的头文件
1 | make headers |
Glibc
接下来安装Glibc,先创建一个 LSB 兼容性符号链接
1 | cd $LFS/sources/ |
解压Glibc,并打patch
1 | tar -xvf glibc-2.40.tar.xz |
创建一个用于构建Glibc的文件夹
1 | mkdir -v build |
确保将 ldconfig 和 sln 工具安装到 /usr/sbin 目录中
1 | echo "rootsbindir=/usr/sbin" > configparms |
准备编译Glibc
1 | ../configure \ |
开始编译,然后安装。这里需要指定安装路径,否则默认会安装到宿主机的根目录,这里需要指定安装到$LFS,因为之后$LFS将成为根目录
1 | make && make DESTDIR=$LFS install |
改正 ldd 脚本中硬编码的可执行文件加载器路径
1 | sed '/RTLDLIST=/s@/usr@@g' -i $LFS/usr/bin/ldd |
现在需要检查新工具链是否能够正确编译和链接,也就是进行完整性检查
1 | echo 'int main(){}' | $LFS_TGT-gcc -xc - |
如果没有问题应该会出现下面的log,否则应当检查之前是否有步骤出现了问题
1 | [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2] |
完成检查后删除测试文件
1 | rm -v a.out |
Libstdc++
现在要构建Libstdc++,Libstdc++是GCC的一部分,所以需要解压GCC的源码包,但之前已经解压过一次了,可以再用
1 | cd $LFS/sources/gcc-14.2.0 |
删除之前用过的构建目录并重新创建
1 | rm -rf build |
准备编译Libstdc++
1 | ../libstdc++-v3/configure \ |
开始编译,编译完成后安装这个库,然后移除对交叉编译有害的 libtool 档案文件
1 | make && make DESTDIR=$LFS install |
交叉编译临时工具
临时交叉工具链的编译暂时告一段落,现在要开始用刚刚构建的临时交叉工具链来交叉编译临时工具,这些临时工具将会使用刚刚编译安装的库
M4
第一个软件包是M4
1 | cd $LFS/sources/ |
M4
准备编译m4
1 | ./configure --prefix=/usr \ |
开始编译,完成后安装软件包
1 | make && make DESTDIR=$LFS install |
Ncurses
解压Ncurses
1 | cd $LFS/sources/ |
首先,保证在配置时优先查找 gawk 命令:
1 | sed -i s/mawk// configure |
在宿主系统构建“tic”程序
1 | mkdir build |
准备编译 Ncurses
1 | ./configure --prefix=/usr \ |
开始编译,完成后安装软件包
1 | make |
Bash
解压Bash
1 | cd $LFS/sources/ |
准备编译Bash
1 | ./configure --prefix=/usr \ |
开始编译,完成后安装软件包,然后创建一个指向Bash的sh软连接,这样调用sh就会调用Bash
1 | make && make DESTDIR=$LFS install |
Coreutils
解压Coreutils
1 | cd $LFS/sources/ |
准备编译 Coreutils
1 | ./configure --prefix=/usr \ |
开始编译,完成后安装软件包,最后将程序移动到它们最终安装时的正确位置
1 | make |
Diffutils
解压Diffutils
1 | cd $LFS/sources/ |
准备编译Diffutils
1 | ./configure --prefix=/usr \ |
开始编译,完成后安装软件包
1 | make && make DESTDIR=$LFS install |
File
解压File
1 | cd $LFS/sources/ |
宿主系统 file 命令的版本必须和正在构建的软件包相同,才能在构建过程中创建必要的特征数据文件。运行以下命令,构建 file 命令的一个临时副本:
1 | mkdir build |
准备编译File
1 | ./configure --prefix=/usr --host=$LFS_TGT --build=$(./config.guess) |
开始编译,完成后安装软件包,然后移除对交叉编译有害的 libtool 档案文件
1 | make FILE_COMPILE=$(pwd)/build/src/file |
Findutils
解压Findutils
1 | cd $LFS/sources/ |
准备编译 Findutils
1 | ./configure --prefix=/usr \ |
开始编译,完成后安装软件包
1 | make && make DESTDIR=$LFS install |
Gawk
解压Gawk
1 | cd $LFS/sources/ |
首先,确保不安装某些不需要的文件:
1 | sed -i 's/extras//' Makefile.in |
准备编译 Gawk
1 | ./configure --prefix=/usr \ |
开始编译,完成后安装软件包
1 | make && make DESTDIR=$LFS install |
Grep
解压Grep
1 | cd $LFS/sources/ |
准备编译Grep
1 | ./configure --prefix=/usr \ |
开始编译,完成后安装软件包
1 | make && make DESTDIR=$LFS install |
Gzip
解压Gzip
1 | cd $LFS/sources/ |
准备编译Gzip
1 | ./configure --prefix=/usr --host=$LFS_TGT |
开始编译,完成后安装软件包
1 | make && make DESTDIR=$LFS install |
Make
解压Make
1 | cd $LFS/sources/ |
准备编译Make
1 | ./configure --prefix=/usr \ |
开始编译,完成后安装软件包
1 | make && make DESTDIR=$LFS install |
Patch
解压Patch
1 | cd $LFS/sources/ |
准备编译Patch
1 | ./configure --prefix=/usr \ |
开始编译,完成后安装软件包
1 | make && make DESTDIR=$LFS install |
Sed
解压Patch
1 | cd $LFS/sources/ |
准备编译Sed
1 | ./configure --prefix=/usr \ |
开始编译,完成后安装软件包
1 | make && make DESTDIR=$LFS install |
Tar
解压Tar
1 | cd $LFS/sources/ |
准备编译Tar
1 | ./configure --prefix=/usr \ |
开始编译,完成后安装软件包
1 | make && make DESTDIR=$LFS install |
Xz
解压Xz
1 | cd $LFS/sources/ |
准备编译Xz
1 | ./configure --prefix=/usr \ |
开始编译,完成后安装软件包,然后移除对交叉编译有害的 libtool 档案文件
1 | make && make DESTDIR=$LFS install |
Binutils 第二遍
之前已经解压过一遍源码了,所以只需删除构建目录
Binutils 构建系统依赖附带的 libtool 拷贝链接内部静态库,但源码包内附带的 libiberty 和 zlib 不使用 libtool。这个区别可能导致构建得到的二进制程序和库错误地链接到宿主发行版的库。绕过这个问题:
1 | cd $LFS/sources/binutils-2.43.1 |
再次创建构建目录
1 | mkdir -v build |
准备编译 Binutils
1 | ../configure \ |
开始编译,完成后安装软件包,然后移除对交叉编译有害的 libtool 档案文件,同时移除不必要的静态库
1 | make && make DESTDIR=$LFS install |
GCC 第二遍
因为前面已经解压过了,所以直接进入目录开始配置
1 | cd $LFS/sources/gcc-14.2.0 |
覆盖 libgcc 和 libstdc++ 头文件的构建规则,以允许在构建它们时启用 POSIX 线程支持:
1 | sed '/thread_header =/s/@.*@/gthr-posix.h/' \ |
创建一个单独的目录用于构建GCC
1 | mkdir build |
准备编译GCC
1 | ../configure \ |
开始编译,完成后安装软件包,然后创建一个符号链接
许多程序和脚本运行 cc 而不是 gcc,因为前者能够保证程序的通用性,使它可以在所有 UNIX 系统上使用,无论是否安装了 GNU C 编译器。运行 cc 可以将选择 C 编译器的权力留给系统管理员
1 | make && make DESTDIR=$LFS install |
进入 Chroot 并构建其他临时工具
现在已经解决了所有循环依赖的问题,可以开始使用chroot环境构建剩下的临时工具了。在这之前仍有一些准备工作需要完成,为了隔离环境的正常工作,必须它与正在运行的内核之间建立一些通信机制。这些通信机制通过所谓的虚拟内核文件系统实现,需要在进入chroot之前挂载它们,而接下来的命令都必须以root执行,且环境变量$LFS设置正确。在进入chroot之后仍然以root执行所有命令。
先切换到root,确保先从lfs用户中登出,重新设置环境变量并检查
1 | sudo su - |
改变$LFS目录的所有者并检查
目前,$LFS 中整个目录树的所有者都是 lfs,这个用户只在宿主系统存在。如果不改变 $LFS 中文件和目录的所有权,它们会被一个没有对应账户的用户 ID 所有。这是危险的,因为后续创建的新用户可能获得这个用户 ID,并成为 $LFS 中全部文件的所有者,从而产生恶意操作这些文件的可能。
1 | chown --from lfs -R root:root $LFS/{usr,lib,var,etc,bin,sbin,tools} |
创建虚拟文件系统的挂载点,这些挂载点都位于$LFS目录下,虚拟文件系统不会占用空间,然后挂载这些虚拟内核文件系统。
接下来需要把宿主机的/dev绑定挂载到$LFS/dev上,绑定挂载允许通过不同的位置访问一个目录树或一个文件;其他虚拟文件系统可以正常挂载处理
1 | mkdir -pv $LFS/{dev,proc,sys,run} |
在某些宿主系统上,/dev/shm 是一个符号链接,通常指向 /run/shm 目录。我们已经在 /run 下挂载了 tmpfs 文件系统,因此在这里只需要创建一个访问权限符合要求的目录。
在其他宿主系统上,/dev/shm 是一个 tmpfs 的挂载点。此时,绑定挂载 /dev 只会在 chroot 环境中生成 /dev/shm 目录。这样,我们必须显式挂载一个 tmpfs
1 | if [ -h $LFS/dev/shm ]; then |
现在可以进入chroot环境完成其他临时工具的安装了
1 | chroot "$LFS" /usr/bin/env -i \ |
从现在开始,就不再需要使用 LFS 环境变量,因为所有工作都被局限在 LFS 文件系统内。这是由于 chroot 命令启动 Bash 时,已经将根目录 (/) 设置为 $LFS。
注意 /tools/bin 不在 PATH 中。这意味着不再使用交叉工具链 。
另外,注意 bash 的提示符会包含 I have no name!。这是正常 的,因为现在还没有创建 /etc/passwd 文件。
在根目录下创建剩余的目录
1 | mkdir -pv /{boot,home,mnt,opt,srv} |
历史上,Linux 曾在 /etc/mtab 维护已经挂载的文件系统的列表。现代内核在内部维护该列表,并通过 /proc 文件系统将它展示给用户。为了满足一些仍然使用 /etc/mtab 的工具,执行以下命令,创建符号链接
1 | ln -sv /proc/self/mounts /etc/mtab |
创建一个基本的 /etc/hosts 文件
1 | cat > /etc/hosts << EOF |
创建 /etc/passwd 和 /etc/groups 文件,加入root,使得root能被正常识别
在/etc/passwd中写入
1 | cat > /etc/passwd << "EOF" |
在/etc/group中写入
1 | cat > /etc/group << "EOF" |
一些软件包需要一个 locale
1 | localedef -i C -f UTF-8 C.UTF-8 |
创建一个用户,因为一些测试需要使用一个非特权用户,之后会删除
1 | echo "tester:x:101:101::/home/tester:/bin/bash" >> /etc/passwd |
打开一个新shell,因为已经创建了/etc/passwd和/etc/group,用户名和组名可以正常解析了
1 | exec /usr/bin/bash --login |
为login、agetty 和 init 等程序创建日志文件
1 | touch /var/log/{btmp,lastlog,faillog,wtmp} |
login、agetty 和 init 等程序使用一些日志文件,以记录登录系统的用户和登录时间等信息。然而,这些程序不会创建不存在的日志文件
文件 /var/log/wtmp 记录所有的登录和登出,文件 /var/log/lastlog 记录每个用户最后登录的时间,文件 /var/log/faillog 记录所有失败的登录尝试,文件 /var/log/btmp 记录所有错误的登录尝试。
现在可以继续安装软件包了
Gettext
解压Gettext
1 | cd /sources/ |
准备编译Gettext
1 | ./configure --disable-shared |
开始编译,完成后安装其中的msgfmt,msgmerge xgettext 三个程序
1 | make |
Bison
解压Bison
1 | cd /sources/ |
准备编译Bison
1 | ./configure --prefix=/usr \ |
开始编译,完成后安装软件包
1 | make && make install |
Perl
解压Perl
1 | cd /sources |
准备编译Perl
1 | sh Configure -des \ |
开始编译,完成后安装软件包
1 | make && make install |
Python
解压Python
1 | cd /sources/ |
准备编译Python
1 | ./configure --prefix=/usr \ |
开始编译,完成后安装软件包
1 | make && make install |
Texinfo
解压Texinfo
1 | cd /sources/ |
准备编译Texinfo
1 | ./configure --prefix=/usr |
开始编译,完成后安装软件包
1 | make && make install |
Util-linux
解压Util-linux
1 | cd /sources/ |
创建/var/lib/hwclock目录作为adjtime文件的位置
1 | mkdir -pv /var/lib/hwclock |
准备编译Util-linux
1 | ./configure --libdir=/usr/lib \ |
开始编译,完成后安装软件包
1 | make && make install |
至此所有临时工具构建完成,现在需要清理和备份临时系统,首先删除已经安装的临时工具文档文件,以防止它们进入最终构建的系统
1 | rm -rf /usr/share/{info,man,doc}/* |
其次,在现代 Linux 系统中,libtool 的 .la 文件仅用于 libltdl。LFS 中没有库通过 libltdl 加载,而且已知一些 .la 文件会导致 BLFS 软件包出现异常。现在删除这些文件
1 | find /usr/{lib,libexec} -name \*.la -delete |
删除/tools目录
1 | rm -rf /tools |
退出chroot环境,切换到root用户,设置并检查环境变量$LFS,然后准备备份整个系统
1 | exit |
先解除虚拟内核文件系统的挂载
1 | mountpoint -q $LFS/dev/shm && umount $LFS/dev/shm |
开始备份
1 | cd $LFS |
现在可以开始构建LFS系统了
FAQ
明明文件夹的权限都设置对了但是make install就是不成功(Permission denied)?
可能是前面~/.bashrc中LFS的路径设置错了,如果不是这个问题,可以试着重新挂载LFS分区
为什么是这个构建流程?
这边有一篇很好的文章解释了构建流程中涉及到的循环依赖以及构建流程:编译工具链
个人理解为:当前主机有编译器二进制为体系结构A,可以生成目标代码A,然后需要编译出一个编译器二进制为体系结构A,可以生成目标代码B,最后用这个新编译器编译出一个编译器二进制为体系结构B,可以生成目标代码B
为什么需要虚拟内核文件系统?
虚拟内核文件系统是指由内核产生而不存在于硬盘中的文件系统,但是存在于内存中
如何用备份进行还原
仔细根据7.13.3. 还原进行还原操作
