1. 宿主机系统准备

1.1 所需要的软件包

⽐下列最低版本更古⽼的版本可能正常⼯作,但作者没有进⾏测试。

Bash-3.2 (/bin/sh 必须是到 bash 的符号链接或硬连接)

Binutils-2.25 (⽐ 2.37 更新的版本未经测试,不推荐使⽤)

Bison-2.7 (/usr/bin/yacc 必须是到 bison 的链接,或者是⼀个执⾏ bison 的小脚本)

Bzip2-1.0.4

Coreutils-6.9

Diffutils-2.8.1

Findutils-4.2.31

Gawk-4.0.1 (/usr/bin/awk 必须是到 gawk 的链接)

GCC-6.2 包括 C++ 编译器, g++ (⽐ 11.2.0 更新的版本未经测试,不推荐使⽤)

Glibc-2.11 (⽐ 2.34 更新的版本未经测试,不推荐使⽤)

Grep-2.5.1a

Gzip-1.3.12

Linux Kernel-3.2

内核版本的要求是为了符合后面编译 glibc 时开发者推荐的配置选项。udev 也要求⼀定的内核版本。

如果宿主内核⽐ 3.2 更早,您需要将内核升级到较新的版本。升级内核有两种⽅法,如果您的发⾏版供应商提供了 3.2 或更新的内核软件包,您可以直接安装它。如果供应商没有提供⼀个⾜够新的内核包,或者您不想安装它,您可以⾃⼰编译内核。

M4-1.4.10

Make-4.0

Patch-2.5.4

Perl-5.8.8

Python-3.4

Sed-4.1.5

Tar-1.22

Texinfo-4.7

Xz-5.0.0

1.2 安装epel源

  1. yum install -y epel-release

1.3 安装各以上各软件

  1. yum install -y gcc gcc-c++ byacc flex bash binutils bison bzip2 coreutils diffutils findutils gawk gcc glibc grep gzip m4 make patch perl sed tar info xz python38.x86_64 bzip2

1.4 宿主机各软件需要的版本检查

可执行以下脚本检查

  1. #!/bin/bash
  2. # Simple script to list version numbers of critical development tools
  3. export LC_ALL=C
  4. bash --version | head -n1 | cut -d" " -f2-4
  5. MYSH=$(readlink -f /bin/sh)
  6. echo "/bin/sh -> $MYSH"
  7. echo $MYSH | grep -q bash || echo "ERROR: /bin/sh does not point to bash"
  8. unset MYSH
  9. echo -n "Binutils: "; ld --version | head -n1 | cut -d" " -f3-
  10. bison --version | head -n1
  11. if [ -h /usr/bin/yacc ]; then
  12. echo "/usr/bin/yacc -> `readlink -f /usr/bin/yacc`";
  13. elif [ -x /usr/bin/yacc ]; then
  14. echo yacc is `/usr/bin/yacc -V | head -n1`
  15. else
  16. echo "yacc not found"
  17. fi
  18. bzip2 --version 2>&1 < /dev/null | head -n1 | cut -d" " -f1,6-
  19. echo -n "Coreutils: "; chown --version | head -n1 | cut -d")" -f2
  20. diff --version | head -n1
  21. find --version | head -n1
  22. gawk --version | head -n1
  23. if [ -h /usr/bin/awk ]; then
  24. echo "/usr/bin/awk -> `readlink -f /usr/bin/awk`";
  25. elif [ -x /usr/bin/awk ]; then
  26. echo awk is `/usr/bin/awk --version | head -n1`
  27. else
  28. echo "awk not found"
  29. fi
  30. gcc --version | head -n1
  31. g++ --version | head -n1
  32. ldd --version | head -n1 | cut -d" " -f2- # glibc version
  33. grep --version | head -n1
  34. gzip --version | head -n1
  35. cat /proc/version
  36. m4 --version | head -n1
  37. make --version | head -n1
  38. patch --version | head -n1
  39. echo Perl `perl -V:version`
  40. python3 --version
  41. sed --version | head -n1
  42. tar --version | head -n1
  43. makeinfo --version | head -n1 # texinfo version
  44. xz --version | head -n1
  45. echo 'int main(){}' > dummy.c && g++ -o dummy dummy.c
  46. if [ -x dummy ]
  47. then echo "g++ compilation OK";
  48. else echo "g++ compilation failed"; fi
  49. rm -f dummy.c dummy

2. 创建新的分区&建立文件系统

使用 cfdisk 或者 fdisk 对硬盘 /dev/sdb 进行分区,此分区用于挂在LFS

2.1 创建分区

  1. 根分区(/)
    ⼀个LFS 根分区 (不要与 /root ⽬录混淆) ⼀般分配 20 GB 的空间就⾜以保证多数系统的运⾏。它提供了构建 LFS 以及 BLFS 的⼤部分软件包的充⾜空间,但⼜不太⼤,因此能够创建多个分区,多次尝试构建 LFS 系统
  2. 交换分区(SWAP)
    许多发⾏版⾃动创建交换空间。⼀般来说,推荐采⽤两倍于物理内存的交换空间,然而这⼏乎没有必要。如果磁盘空间有限,可以创建不超过 2GB 的交换空间,并注意它的使⽤情况。
    如果您希望使⽤ Linux 的休眠功能 (挂起到磁盘),它会在关机前将内存内容写⼊到交换分区。这种情况下,交换分区的⼤小应该⾄少和系统内存相同
  3. Grub Bios分区(只有GPT格式磁盘下才必须创建)
    如果启动磁盘采⽤ GUID 分区表 (GPT),那么必须创建⼀个小的,⼀般占据 1MB 的分区,除⾮它已经存 在。这个分区不能格式化,在安装启动引导器时必须能够被 GRUB 发现。这个分区在 fdisk 下显⽰为 ‘BIOS Boot’ 分区,在 gdisk 下显⽰分区类型代号为 EF02
  4. 常用分区
    还有其他⼏个并⾮必须,但在设计磁盘布局时应当考虑的分区。下⾯的列表并不完整,但可以作为⼀个参考。
    • /boot ‒ ⾼度推荐。这个分区可以存储内核和其他引导信息。为了减少⼤磁盘可能引起的问题,建议将 / boot 分区设为第⼀块磁盘的第⼀个分区。为它分配 200 MB就绰绰有余。
    • /home ‒ ⾼度推荐。独⽴的 /home 分区可以在多个发⾏版或 LFS 系统之间共享home ⽬录和⽤⼾设 置。它的尺⼨⼀般很⼤,取决于硬盘的可⽤空间。
    • /usr ‒ 在 LFS 中,/bin,/lib,以及 /sbin 是指向 /usr 中对应⽬录的符号链接。因此,/usr 包含系统 运⾏需要的所有⼆进制程序和库。对于LFS,通常不需要为 /usr 创建单独的分区。如果仍然需要这种配 置,需要为其建⽴⼀个能够容纳系统中所有程序和库的分区。同时,在这种配置下,根分区可以⾮常小(可能只需要⼀吉字节),因此它适⽤于瘦客⼾端或者⽆盘⼯作站 (此时 /usr 从远程服务器挂载)。然而, 需要注意的是,必须使⽤ initramfs (LFS 没有包含),才能引导具有单独的 /usr 分区的系统。
    • /opt ‒ 这个⽬录往往被⽤于在 BLFS 中安装 Gnome 或 KDE 等⼤型软件,以免把⼤量⽂件塞进 /usr ⽬录 树。如果将它划分为独⽴分区,5 到 10 GB⼀般就⾜够了。
    • /tmp ‒ ⼀个独⽴的 /tmp 分区是很少⻅的,但在配置瘦客⼾端时很有⽤。如果分配了这个分区,⼤小⼀ 般不会超过⼏个 GB。
    • /usr/src ‒ 将它划分为独⽴分区,可以⽤于存储 BLFS 源代码,并在多个 LFS 系统之间共享它们。它也可 以⽤于编译 BLFS 软件包。30-50 GB 的分区可以提供⾜够的空间。 如果您希望在启动时⾃动挂载任何⼀个独⽴的分区,就要在 /etc/fstab ⽂件中说明。

本次创建分区如下:

分区 挂载点 用途 大小
sdb1 /mnt/lfs / 20 GB
sdb2 [SWAP] swap 8 GB
sdb3 /mnt/lfs/boot /boot 200 MB
sdb5 /mnt/lfs/tmp /tmp 4 GB
sdb6 /mnt/lfs/home /home 50 GB

2.2 建立文件系统

现在我们建⽴好了空⽩分区,可以在分区上建⽴⽂件系统。LFS 可以使⽤ Linux 内核能够识别的任何⽂件系统,最常⻅的是 ext3 和 ext4。⽂件系统的选型是⼀个复杂的问题,要综合考虑分区的⼤小,以及其中所存储⽂件的特征。例如:

  • ext2
    适⽤于不经常更新的小分区,例如 /boot。
  • ext3
    ext2 的升级版本,拥有⽇志系统,能够在⾮正常关机的情况下恢复分区的正常状态。它被⼴泛⽤于 ⼀般场合。
  • ext4
    ⽂件系统家族的最新成员,它具有纳秒精度时间戳、超⼤ (16 TB) ⽂件⽀持等新功能,速度也更快。
  • 其他⽂件系统
    包括 FAT32, NTFS, ReiserFS, JFS 和 XFS 在特定场合也很有⽤。关于这些⽂件系统的更多 信息,可以在 http://en.wikipedia.org/wiki/Comparison_of_file_systems 找到。

LFS假设根⽂件系统 (/) 采⽤ ext4 ⽂件系统。输⼊以下命令在 LFS 分区创建⼀个 ext4 ⽂件系统:

  1. mkfs -v -t ext4 /dev/<xxx>

命令中应该替换成 LFS 分区的名称

如果您拥有⼀个现成的 swap 分区,就不需要格式化它。如果新创建了⼀个 swap 分区,需要执⾏以下命令以 初始化它:

  1. mkswap /dev/<yyy>

命令中 应该替换成 swap 分区的名称。

2.2.1 设置$LFS环境变量

  1. export LFS=/mnt/lfs

设置该环境变量的好处是,我们可以直接输⼊书中的命令,例如 mkdir -v $LFS/tools。Shell 在解析命令 时会⾃动将 “$LFS” 替换成 “/mnt/lfs” (或是您设置的其他值)。

  • 注意:

确保 LFS 始终正确的⼀种⽅法是:编辑您的主⽬录中的 .bash_profile,以及/root/.bash_profile,为它们加⼊上述设置并导出 LFS 变量的 export 命令。还要确认 /etc/passwd 中为每个需 要使⽤ LFS 变量的⽤⼾指定的 shell 都是 bash,以保证每次登录时都执⾏ .bash_profile 中的命令。

另外还要考虑登录宿主系统的⽅式,如果您使⽤图形显⽰管理器登录,再启动虚拟终端,那么 . bash_profile ⼀般不会被虚拟终端执⾏。此时,应该将 export 命令加⼊到您使⽤的⽤⼾和 root ⽤⼾的 .bashrc ⽂件中。另外,⼀些发⾏版的 .bashrc 中加⼊了检测交互性的命令,使其在⾮交互 bash 的启动过程中不执⾏其余命令。此时必须将 export 命令添加到交互性检测之前。

2.2.2 挂载分区

我们已经在分区上建⽴了⽂件系统,为了访问分区,我们需要把分区挂载到选定的挂载点上。

输⼊以下命令以创建挂载点,并挂载 LFS ⽂件系统:

  1. mkdir -pv $LFS
  2. mkdir -v $LFS/boot
  3. mkdir -v $LFS/tmp
  4. mkdir -v $LFS/homme
  5. mount -v -t ext4 /dev/sdb1 $LFS
  6. mount -v -t ext4 /dev/sdb3 $LFS/boot
  7. mount -v -t ext4 /dev/sdb5 $LFS/tmp
  8. mount -v -t ext4 /dev/sdb6 $LFS/home
  9. swapon -v /dev/sdb2
  • 注意:
    上⾯的命令假设您在构建 LFS 的过程中不会重启计算机。如果您关闭了系统,那么您要么在继续构 建过程时重新挂载分区,要么修改宿主系统的 /etc/fstab ⽂件,使得系统在引导时⾃动挂载它们。 例如:
    如果您使⽤了多个分区,它们都需要添加到 fstab 中
    1. /dev/sdb1 /mnt/lfs ext4 defaults 1 1
    2. /dev/sdb3 /mnt/lfs/boot ext4 defaults 1 1
    3. /dev/sdb5 /mnt/lfs/tmp ext4 defaults 1 1
    4. /dev/sdb6 /mnt/lfs/home ext4 defaults 1 1

3. 软件包与补丁

3.1 准备工作目录

首先需要创建一个位置,用来保存下载好的软件包和补丁,使得在整个构建过程中能够容易地访问它们。同时还需要一个工作目录,以便解压和编译软件包。我们可以将 $LFS/sources 既用于保存软件包和补丁,又作为工作目录。

为创建这个目录,在开始下载软件包之前,以 root 身份执行:

  1. mkdir -v $LFS/sources

为该目录添加写入权限和 sticky 标志。“Sticky” 标志使得即使有多个⽤⼾对该⽬录有写⼊权限,也只有⽂件所有者能够删除其中的⽂件。输⼊以下命令,启⽤写⼊权限和 sticky 标志:

  1. chmod -v a+wt $LFS/sources

3.2 全部软件包

官网下载wgetlist

  1. cd $LFS/sources
  2. wget https://www.linuxfromscratch.org/lfs/downloads/stable/wget-list

使用 wget 和上面描述的 wget-list 下载这些文件。 wget-list 作为 wget 命令输入,以下载所有软件包和补丁,使用命令:

  1. wget --input-file=wget-list --continue --directory-prefix=$LFS/sources

4. 最后准备工作

4.1 在 LFS ⽂件系统中创建有限⽬录布局

我们将在 $LFS 中创建⼀些⽤于安装临时⼯具 的⽬录,增加⼀个⾮特权⽤⼾以降低⻛险,并为该⽤⼾建⽴合适的构建环境。

在 LFS 分区中需要进行的第一项任务是,创建一个有限的目录树,使得编译的程序可以被安装到他们的最终位置。这样在以后重新构建他们时,就能直接覆盖这些临时程序。

以 root 身份,执行一下命令创建所需要的目录布局:

  1. mkdir -pv $LFS/{etc,var} $LFS/usr/{bin,lib,sbin}
  2. for i in bin lib sbin; do
  3. ln -sv usr/$i $LFS/$i
  4. done
  5. case $(uname -m) in
  6. x86_64) mkdir -pv $LFS/lib64 ;;
  7. esac

为了将以后使用的交叉编译器和其他程序分离,它会被安装在一个专门的目录。执行一下命令创建该目录:

  1. mkdir -pv $LFS/tools

4.2 添加 LFS ⽤⼾

在作为 root 用户登录时,一个微小的错误就可能损坏甚至摧毁整个系统。因此,建议在后续中已非特权用户身份编译软件包。为了更容易建立一个干净的工作环境,最好创建一个名为 lfs 的新用户,以及它从属的一个新组 lfs 。

  1. groupadd lfs
  2. useradd -s /bin/bash -g lfs -m -k /dev/null lfs

命令⾏各选项的含义:

  • -s /bin/bash 设置 bash 为⽤⼾ lfs 的默认 shell。
  • -g lfs 添加⽤⼾ lfs 到组 lfs
  • -m 为⽤⼾ lfs 创建⼀个主⽬录。
  • -k /dev/null 将模板⽬录设置为空设备⽂件,从而不从默认模板⽬录 (/etc/skel) 复制⽂件到新的主⽬录。
  • lfs 要创建的⽤⼾的名称。

为lfs 设置密 码:

  1. passwd lfs

将 lfs 设为 $LFS 中所有⽬录的所有者,使 lfs 对它们拥有完全访问权:

  1. chown -v lfs $LFS/{usr{,/*},lib,var,etc,bin,sbin,tools}
  2. case $(uname -m) in
  3. x86_64) chown -v lfs $LFS/lib64 ;;
  4. esac
  5. chown -v lfs $LFS/sources

4.3 配置环境

为了配置⼀个良好的⼯作环境,我们为 bash 创建两个新的启动脚本。以 lfs 的⾝份,执⾏以下命令,创建 ⼀个新的 .bash_profile:

  1. cat > ~/.bash_profile << "EOF"
  2. exec env -i HOME=$HOME TERM=$TERM PS1='\u:\w\$ ' /bin/bash
  3. EOF

在以 lfs ⽤⼾登录时,初始的 shell ⼀般是⼀个登录 shell。它读取宿主系统的 /etc/profile ⽂件 (可能包含⼀些设置和环境变量),然后读取 .bash_profile。我们在 .bash_profile 中使⽤ exec env -i…/bin/bash 命令,新建⼀个除了 HOME, TERM 以及 PS1 外没有任何环境变量的 shell,替换当前 shell,防⽌宿主环境中不必要和有潜在⻛险的环境变量进⼊编译环境。

新的 shell 实例是 ⾮登录 shell,它不会读取和执⾏ /etc/profile 或者 .bash_profile 的内容,而是读取并 执⾏ .bashrc ⽂件。现在我们就创建⼀个 .bashrc ⽂件:

  1. cat > ~/.bashrc << "EOF"
  2. set +h
  3. umask 022
  4. LFS=/mnt/lfs
  5. LC_ALL=POSIX
  6. LFS_TGT=$(uname -m)-lfs-linux-gnu
  7. PATH=/usr/bin
  8. if [ ! -L /bin ]; then PATH=/bin:$PATH; fi
  9. PATH=$LFS/tools/bin:$PATH
  10. CONFIG_SITE=$LFS/usr/share/config.site
  11. export LFS LC_ALL LFS_TGT PATH CONFIG_SITE
  12. EOF

.bashrc 中设定的含义:

  • set +h

set +h 命令关闭 bash 的散列功能。⼀般情况下,散列是很有⽤的 ⸺ bash 使⽤⼀个散列表维护各个可执⾏⽂件的完整路径,这样就不⽤每次都在 PATH 指定的⽬录中搜索可执⾏⽂件。然而,在构建 LFS时,我们希望总是使⽤最新安装的⼯具。因此,需要关闭散列功能,使得 shell 在运⾏程序时总是搜索PATH。这样,shell 总是能够找到 $LFS/tools ⽬录中那些最新编译的⼯具,而不是使⽤之前记忆的另⼀个⽬录中的程序。

  • umask 022

将⽤⼾的⽂件创建掩码 (umask) 设定为 022,保证只有⽂件所有者可以写新创建的⽂件和⽬录,但任何⼈都可读取、执⾏它们。(如果 open(2) 系统调⽤使⽤默认模式,则新⽂件将具有权限模式 644,而新⽬录具有权限模式 755)。

  • LFS=/mnt/lfs

LFS 环境变量必须被设定为之前选择的挂载点。

  • LC_ALL=POSIX

LC_ALL 环境变量控制某些程序的本地化⾏为,使得它们以特定国家的语⾔和惯例输出消息。将 LC_ALL设置为 “POSIX” 或者 “C”(这两种设置是等价的) 可以保证在 chroot 环境中所有命令的⾏为完全符合预期,而与宿主的本地化设置⽆关。

  • LFS_TGT=(uname-m)-lfs-linux-gnu

LFS_TGT变量设定了⼀个⾮默认,但与宿主系统兼容的机器描述符。该描述符被⽤于构建交叉编译器和交叉编译临时⼯具链。⼯具链技术说明包含了关于这个描述符的更多信息。

  • PATH=/usr/bin

许多现代 Linux 发⾏版合并了 /bin 和 /usr/bin。在这种情况下,标准 PATH 变量只需要被设定为 /usr/bin。否则,后续命令将会增加 /bin 到搜索路径中。

  • if [ ! -L /bin ]; then PATH=/bin:$PATH; fi

如果 /bin 不是符号链接,则它需要被添加到 PATH 变量中。

  • PATH=$LFS/tools/bin:$PATH

我们将 $LFS/tools/bin 附加在默认的 PATH 环境变量之前,这样在第 5 章中,我们⼀旦安装了新的程序,shell 就能⽴刻使⽤它们。这与关闭散列功能相结合,降低了在以后环境中新程序可⽤时错误地使⽤宿主系统中旧程序的⻛险。

  • CONFIG_SITE=$LFS/usr/share/config.site

如果没有设定这个变量,在以后的configure 脚本可能会从宿主系统的 /usr/share/config.site 加载⼀些发⾏版特有的配置信息。覆盖这⼀默认路径,避免宿主系统可能造成的污染。

  • export …

前⾯的命令设定了⼀些变量,为了让所有⼦ shell 都能使⽤这些变量,需要导出它们。

最后,为了完全准备好编译临时⼯具的环境,指⽰ shell 读取刚才创建的配置⽂件:

  1. source ~/.bash_profile

5. 编译交叉工具链

本节中编译的程序会被安装在 $LFS/tools 目录中,以将它们和后续的安装的文件分开

5.1 Binutils-2.37 - 第⼀遍

Binutils 包含汇编器、链接器以及其他⽤于处理⽬标⽂件的⼯具。

估计构建时间 需要硬盘空间
1 SBU 602 MB

⾸先构建 Binutils 相当重要,因为 Glibc 和 GCC 都会对可⽤的链接器和汇编器进⾏测试,以决定可以启⽤ 它们⾃带的哪些特性。

解压 binutils:

  1. cd $LFS/sources
  2. tar -xf binutils-2.37.tar.xz
  3. mv -v binutils-2.37 binutils
  4. cd binutils

Binutils ⽂档推荐在⼀个专⽤的⽬录中构建 Binutils:

  1. mkdir -v build
  2. cd build

准备编译binutiils:

  1. ../configure --prefix=$LFS/tools \
  2. --with-sysroot=$LFS \
  3. --target=$LFS_TGT \
  4. --disable-nls \
  5. --disable-werror

配置选项的含义:

  • —prefix=$LFS/tools 这告诉配置脚本准备将 Binutils 程序安装在 $LFS/tools ⽬录中。
  • —with-sysroot=$LFS 该选项告诉构建系统,交叉编译时在 $LFS 中寻找⽬标系统的库。
  • —target=$LFS_TGT 由于 LFS_TGT 变量中的机器描述和 config.guess 脚本的输出略有不同, 这个开关使得 configure 脚本 调整 Binutils 的构建系统,以构建交叉链接器。
  • —disable-nls 该选项禁⽤临时⼯具不需要的国际化功能。
  • —disable-werror 该选项防⽌宿主系统编译器警告导致构建失败。

然后编译该软件包:

  1. make

安装该软件包:

  1. make install -j1

make 命令参数的含义:

  • -j1 如果 MAKEFLAGS 中包含了 -j N,构建系统中的⼀个问题可能导致安装过程失败。该选项覆盖其设置,以绕过这个问题。

5.2 GCC-11.2.0 - 第一遍

GCC 软件包包含 GNU 编译器集合,其中有 C 和 C++ 编译器。

估计构建时间 需要硬盘空间
12 SBU 3.4 GB

GCC 依赖于 GMP、MPFR 和 MPC 这三个包。由于宿主发⾏版未必包含它们,我们将它们和GCC ⼀同构建。将它们都解压到 GCC 源码⽬录中,并重命名解压出的⽬录,这样 GCC 构建过程就能⾃动使⽤它们:

  1. tar -xf gcc-11.2.0.tar.xz
  2. mv gcc-11.2.0 gcc
  3. cd gcc
  4. tar -xf ../mpfr-4.1.0.tar.xz
  5. mv -v mpfr-4.1.0 mpfr
  6. tar -xf ../gmp-6.2.1.tar.xz
  7. mv -v gmp-6.2.1 gmp
  8. tar -xf ../mpc-1.2.1.tar.gz
  9. mv -v mpc-1.2.1 mpc

对于 x86_64 平台,还要设置存放64位库的默认目录为 “lib”:

  1. case $(uname -m) in
  2. x86_64)
  3. sed -e '/m64=/s/lib64/lib/' -i.orig gcc/config/i386/t-linux64
  4. ;;
  5. esac

GCC 文档建议在一个专用目录中构建 GCC:

  1. mkdir -v build
  2. cd build

准备编译 GCC :

  1. ../configure \
  2. --target=$LFS_TGT \
  3. --prefix=$LFS/tools \
  4. --with-glibc-version=2.34 \
  5. --with-sysroot=$LFS \
  6. --with-newlib \
  7. --without-headers \
  8. --enable-initfini-array \
  9. --disable-nls \
  10. --disable-shared \
  11. --disable-multilib \
  12. --disable-decimal-float \
  13. --disable-threads \
  14. --disable-libatomic \
  15. --disable-libgomp \
  16. --disable-libquadmath \
  17. --disable-libssp \
  18. --disable-libvtv \
  19. --disable-libstdcxx \
  20. --enable-languages=c,c++

配置选项的含义:

  • —with-glibc-version=2.34

该选项指定⽬标系统将要使⽤的 glibc 版本。这与
宿主系统需求 中给出的最低 glibc 版本要求没有关系,因为第⼀遍的 gcc 产⽣的所有代码都会与宿主系统的 glibc 完全隔离的 chroot 环境中运⾏。

  • —with-newlib

由于现在没有可⽤的 C 运⾏库,使⽤该选项保证构建 libgcc 时 inhibit_libc 常量被定义,以防⽌编译任何需要 libc ⽀持的代码。

  • —without-headers

在创建完整的交叉编译器时,GCC 需要与⽬标系统兼容的标准头⽂件。由于我们的特殊⽬的,这些头⽂件并不必要。这个开关防⽌ GCC 查找它们。

  • —enable-initfini-array

这个开关强制启⽤⼀些内部数据结构,它们是必要的,但是在构建交叉编译器时,⽆法被检测到。

  • —disable-shared

这个开关强制 GCC 静态链接它的内部库。我们必须这样做,因为动态库需要⽬标系统中尚未安装的glibc。

  • —disable-multilib

在 x86_64 平台上,LFS 不⽀持 multilib 配置。这个开关对于 x86 来说可有可⽆。

  • —disable-decimal-float, —disable-threads, —disable-libatomic, —disable-libgomp,
    —disable-libquadmath, —disable-libssp, —disable-libvtv, —disable-libstdcxx

这些开关禁⽤对于⼗进制浮点数、线程、libatomic、libgomp、libquadmath、libssp、libvtv 和 C++ 标准库的⽀持。在构建交叉编译器时它们的编译会失败,而且在交叉编译临时 libc 时并不需要它们。

  • —enable-languages=c,c++

这个选项保证只构建 C 和 C++ 编译器。⽬前只需要这两个语⾔。

执行以下命令编译 GCC:

  1. make

安装该软件包:

  1. make install

刚刚构建的 GCC 安装了若⼲内部系统头⽂件。其中的 limits.h ⼀般来说,应该包含对应的系统头⽂件 limits.h,在我们的 LFS 环境中,就是 $LFS/usr/include/limits.h。然而,在构建 GCC 的时候,$LFS/ usr/include/limits.h 还不存在,因此 GCC 安装的内部头⽂件是⼀个不完整的、⾃给⾃⾜的⽂件,不包含 系统头⽂件提供的扩展特性。这对于构建临时的 libc 已经⾜够了,但后续⼯作将需要完整的内部头⽂件。使⽤以下命令创建⼀个完整版本的内部头⽂件,该命令与 GCC 构建系统在⼀般情况下⽣成该头⽂件的命令是 ⼀致的:

  1. cd ..
  2. cat gcc/limitx.h gcc/glimits.h gcc/limity.h > \
  3. `dirname $($LFS_TGT-gcc -print-libgcc-file-name)`/install-tools/include/limits.h

5.3 Linux-5.15.12 API头文件

Linux API 头⽂件 (在 linux-5.15.12.tar.xz 中) 导出内核 API 供 Glibc 使⽤。

估计构建时间 需要硬盘空间
0.1 SBU 1.2 GB

5.3.1 安装Linux API头文件

Linux 内核需要导出⼀个应⽤程序编程接口 (API) 供系统的 C 运⾏库 (例如 LFS 中的 Glibc) 使⽤。这通过 净化内核源码包中提供的若⼲ C 头⽂件完成。

  1. cd $LFS/sources
  2. tar -xvf linux-5.15.12.tar.xz
  3. mv linux-5.15.12 linux
  4. cd linux

确保软件包中没有遗留陈旧的文件:

  1. make mrproper

下⾯从源代码中提取⽤⼾可⻅的头⽂件。我们不能使⽤推荐的make ⽬标“headers_install”,因为它需 要 rsync,这个程序在宿主系统中未必可⽤。头⽂件会先被放置在 ./usr⽬录中,之后再将它们复制到最终的位置。

  1. make headers
  2. find usr/include -name '.*' -delete
  3. rm usr/include/Makefile
  4. cp -rv usr/include $LFS/usr

5.3.2 Linux API 头文件内容

安装的头文件:

  1. /usr/include/asm/*.h
  2. /usr/include/asm-generic/*.h
  3. /usr/include/drm/*.h
  4. /usr/include/linux/*.h
  5. /usr/include/misc/*.h
  6. /usr/include/mtd/*.h
  7. /usr/include/rdma/*.h
  8. /usr/include/scsi/*.h
  9. /usr/include/sound/*.h
  10. /usr/include/video/*.h
  11. /usr/include/xen/*.h

安装的目录:

  1. /usr/include/asm
  2. /usr/include/asm-generic
  3. /usr/include/drm
  4. /usr/include/linux
  5. /usr/include/misc
  6. /usr/include/mtd
  7. /usr/include/rdma
  8. /usr/include/scsi
  9. /usr/include/sound
  10. /usr/include/video
  11. /usr/include/xen

简要描述:

头文件 描述
/usr/include/asm/*.h Linux API 汇编头⽂件
/usr/include/asm-generic/*.h Linux API 通⽤汇编头⽂件
/usr/include/drm/*.h Linux API DRM 头⽂件
/usr/include/linux/*.h Linux API Linux 头⽂件
/usr/include/misc/*.h Linux API 杂项头⽂件
/usr/include/mtd/*.h Linux API MTD 头⽂件
/usr/include/rdma/*.h Linux API RDMA 头⽂件
/usr/include/scsi/*.h Linux API SCSI 头⽂件
/usr/include/sound/*.h Linux API ⾳频头⽂件
/usr/include/video/*.h Linux API 视频头⽂件
/usr/include/xen/*.h Linux API Xen 头⽂件

5.4 Glibc

Glibc 软件包包含主要的 C 语言库。它提供用于分配内存、检索目录、打开和关闭文件、读写文件、字符串处理、模式匹配、算术等用途的基本子程序。

估计构建时间 需要硬盘空间
4.2 SBU 744 MB
  1. cd $LFS/sources
  2. tar -xvf glibc-2.34.tar.xz
  3. mv glibc-2.34 glibc
  4. cd glibc

首先,创建一个 LSB 兼容性符号链接。另外,对于 x86_64,创建⼀个动态链接器正常⼯作所必须的符号链接:

  1. case $(uname -m) in
  2. i?86) ln -sfv ld-linux.so.2 $LFS/lib/ld-lsb.so.3
  3. ;;
  4. x86_64) ln -sfv ../lib/ld-linux-x86-64.so.2 $LFS/lib64
  5. ln -sfv ../lib/ld-linux-x86-64.so.2 $LFS/lib64/ld-lsb-x86-64.so.3
  6. ;;
  7. esac

⼀些 Glibc 程序使⽤与 FHS 不兼容的 /var/db ⽬录存放它们的运⾏时数据。下⾯应⽤⼀个补丁,使得这些 程序在 FHS 兼容的位置存放运⾏时数据:

  1. patch -Np1 -i ../glibc-2.34-fhs-1.patch

Glibc ⽂档推荐在⼀个专⽤⽬录中构建 Glibc:

  1. mkdir -v build
  2. cd builld

确保将 ldconfig 和 sln ⼯具安装到 /usr/sbin ⽬录中:

  1. echo "rootsbindir=/usr/sbin" > configparms

下⾯,准备编译 Glibc:

  1. ../configure \
  2. --prefix=/usr \
  3. --host=$LFS_TGT \
  4. --build=$(../scripts/config.guess) \
  5. --enable-kernel=3.2 \
  6. --with-headers=$LFS/usr/include \
  7. libc_cv_slibdir=/usr/lib

配置选项的含义:

  • —host=$LFS_TGT,
    —build=$(../scripts/config.guess)

在它们的共同作⽤下,Glibc 的构建系统将⾃⾝配置为使⽤ $LFS/tools 中的交叉链接器和交叉编译器,进⾏交叉编译。

  • —enable-kernel=3.2

该选项告诉 Glibc 编译出⽀持 3.2 版或者更新的 Linux 内核,这样就不会使⽤那些为更⽼内核准备的替代⽅案。

  • —with-headers=$LFS/usr/include

该选项告诉 Glibc 在编译过程中,使⽤ $LFS/usr/include ⽬录中的头⽂件,这样它就知道内核拥有哪些特性,并据此对⾃⾝进⾏优化。

  • libc_cv_slibdir=/usr/lib

在 64 位机器上,这保证将库安装到 /usr/lib,而不是默认的 /lib64。

  1. 在当前阶段,可能出现下列警告:
  2. configure: WARNING:
  3. *** These auxiliary programs are missing or
  4. *** incompatible versions: msgfmt
  5. *** some features will be disabled.
  6. *** Check the INSTALL file for required vers
  7. msgfmt 程序的缺失或不兼容⼀般是⽆害的。msgfmt 程序是 Gettext 软件包的⼀部分,宿主发⾏版应该提供它。

编译该软件包:

  1. make

注意:有报告称该软件包在并⾏构建时可能失败,如果发⽣了这种情况,加上 “-j1” 选项重新执⾏ make 命令。

安装该软件包:

  1. make DESTDIR=$LFS install

make install 选项的含义:

  • DESTDIR=$LFS

多数软件包使⽤ DESTDIR 变量指定软件包应该安装的位置。如果不设定它,默认值为根 (/) ⽬录。这⾥我们指定将软件包安装到 $LFS,它在第 7.4 节 “进⼊ Chroot 环境”之后将成为根⽬录。

改正 ldd 脚本中硬编码的可执⾏⽂件加载器路径:

  1. sed '/RTLDLIST=/s@/usr@@g' -i $LFS/usr/bin/ldd
  1. # 现在我们不可避免地要停下确认新⼯具链的各基本功能 (编译和链接) 能如我们所预期的那样⼯作。 执⾏以下命令进⾏完整性检查:
  2. echo 'int main(){}' > dummy.c
  3. $LFS_TGT-gcc dummy.c
  4. readelf -l a.out | grep '/ld-linux'
  5. # 如果⼀切正常,那么应该没有错误消息,而且最后⼀⾏命令应该输出下列格式的内容:
  6. [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  7. # 注意,对于 32 位机器,解释器的名字将会是 /lib/ld-linux.so.2。
  8. # 如果输出不像上⾯描述的那样,或者根本没有输出,就说明出了问题。检查并重新跟踪各个步骤,找到出问题的地⽅并修正它。在继续构建之前,必须解决这个问题。
  9. # 检验步骤顺利完成后,清理测试⽂件:
  10. rm -v dummy.c a.out

现在我们的交叉⼯具链已经构建完成,可以完成 limits.h 头⽂件的安装。为此,运⾏ GCC 开发者提供的⼀个⼯具:

  1. $LFS/tools/libexec/gcc/$LFS_TGT/11.2.0/install-tools/mkheaders

5.5 GCC-11.2.0中的Libstdc++ 第一遍

Libstdc++ 是 C++ 标准库。我们需要它才能编译 C++ 代码 (GCC 的⼀部分⽤ C++ 编写)。但在构建第⼀遍的 GCC 时我们不得不暂缓安装它,因为它依赖于当时还没有安装到⽬标⽬录的 Glibc。

估计构建时间 需要硬盘空间
0.4 SBU 1.0 GB

Libstdc++ 是 GCC 源代码的⼀部分。您应该先解压 GCC 源码包并切换到解压出来的 gcc-11.2.0 ⽬录。

为libstdc++创建一个单独的构建目录,并进入它:

  1. cd $LFS/sources/gcc
  2. mkdir -v build-libstdc++
  3. cd build-libstdc++

准备编译 libstdc++ :

  1. ../libstdc++-v3/configure \
  2. --host=$LFS_TGT \
  3. --build=$(../config.guess) \
  4. --prefix=/usr \
  5. --disable-multilib \
  6. --disable-nls \
  7. --disable-libstdcxx-pch \
  8. --with-gxx-include-dir=/tools/$LFS_TGT/include/c++/11.2.0

配置选项的含义:

  • —host=…

指定使⽤我们刚刚构建的交叉编译器,而不是 /usr/bin 中的宿主系统编译器。

  • —disable-libstdcxx-pch

这个开关防⽌安装预编译头⽂件,在这个阶段不需要它们。

  • —with-gxx-include-dir=/tools/$LFS_TGT/include/c++/11.2.0

C++ 编译器应该在这个位置搜索标准头⽂件。在正常的构建过程中,这项信息被顶层⽬录构建系统⾃动传递给 Libstdc++ configure 脚本。然而我们没有使⽤顶层构建系统,必须明确给出这项信息。

运⾏以下命令编译Libstdc++:

  1. make

安装这个库:

  1. make DESTDIR=$LFS install

6. 交叉编译临时⼯具

6.1 M4-1.4.19

M4 软件包包含一个宏处理器。

估计构建时间 需要硬盘空间
0.2 SBU 32 MB
  1. tar -xvf m4-1.4.19.tar.xz
  2. mv -v m4-1.4.19 m4
  3. cd m4

准备编译M4:

  1. ./configure --prefix=/usr \
  2. --host=$LFS_TGT \
  3. --build=$(build-aux/config.guess)

编译该软件:

  1. make

安装该软件包:

  1. make DESTDIR=$LFS install

6.2 Ncurses-6.3

Ncurses 软件包包含使⽤时不需考虑终端特性的字符屏幕处理函数库。

估计构建时间 需要硬盘空间
0.7 SBU 48 MB
  1. tar -xvf ncurses-6.3.tar.gz
  2. mv -v ncurses-6.3 ncurses
  3. cd ncurses

首先,保证在配置时优先查找 gawk 命令:

  1. sed -i s/mawk// configure

然后,运行以下命令,在宿主系统构建“tic”程序:

  1. mkdir build
  2. pushd build
  3. ../configure
  4. make -C include
  5. make -C progs tic
  6. popd

准备编译 Ncurses:

  1. ./configure --prefix=/usr \
  2. --host=$LFS_TGT \
  3. --build=$(./config.guess) \
  4. --mandir=/usr/share/man \
  5. --with-manpage-format=normal \
  6. --with-shared \
  7. --without-debug \
  8. --without-ada \
  9. --without-normal \
  10. --disable-stripping \
  11. --enable-widec

新出现的配置选项的含义:

  • —with-manpage-format=normal

这防⽌ Ncurses 安装压缩的⼿册⻚⾯,否则在宿主发⾏版使⽤压缩的⼿册⻚⾯时,Ncurses 可能这样做。

  • —without-ada

这保证不构建 Ncurses 的 Ada 编译器⽀持,宿主环境可能有 Ada 编译器,但进⼊ chroot 环境后 Ada编译器就不再可⽤。

  • —disable-stripping

该选项防⽌构建过程使⽤宿主系统的 strip 移除调试符号。对交叉编译产⽣的程序使⽤宿主⼯具可能导致构建失败。

  • —enable-widec

该选项使得宽字符库 (例如 libncursesw.so.6.3) 被构建,而不构建常规字符库 (例如 libncurses.so.6.3)。宽字符库在多字节和传统 8 位 locale 中都能⼯作,而常规字符库只能在 8 位 locale 中⼯作。宽字符库和普通字符库在源码层⾯是兼容的,但⼆进制不兼容。

  • —without-normal

该选项禁⽌多数静态库的构建和安装。

编译该软件包:

  1. make

安装该软件包:

  1. make DESTDIR=$LFS TIC_PATH=$(pwd)/build/progs/tic install
  2. echo "INPUT(-lncursesw)" > $LFS/usr/lib/libncurses.so

6.3 Bash - 5.1.8

Bash 软件包包含 Bourne-Again SHell。

估计构建时间 需要硬盘空间
0.4 SBU 64 MB
  1. tar -xvf bash-5.1.8.tar.gz
  2. mv -v bash-5.1.8 bash
  3. cd bash

准备编译 Bash:

  1. ./configure --prefix=/usr \
  2. --build=$(support/config.guess) \
  3. --host=$LFS_TGT \
  4. --without-bash-malloc

配置选项的含义:

  • —without-bash-malloc

该选项禁⽤ Bash ⾃⼰的内存分配 (malloc) 函数,因为已知它会导致段错误。这样,Bash 就会使⽤Glibc 的更加稳定的 malloc 函数。

编译该软件包:

  1. make

安装该软件包:

  1. make DESTDIR=$LFS install

为那些使⽤ sh 命令运⾏ shell 的程序考虑,创建⼀个链接:

  1. ln -sv bash $LFS/bin/sh

6.4 Coreutils-9.0

Coreutils 软件包包含⽤于显⽰和设定系统基本属性的⼯具。

估计构建时间 需要硬盘空间
0.6 SBU 151 MB
  1. tar -xvf coreutils-9.0.tar.xz
  2. mv coreutils-9.0 coreutils
  3. cd coreutils

准备编译 Coreutils:

  1. ./configure --prefix=/usr \
  2. --host=$LFS_TGT \
  3. --build=$(build-aux/config.guess) \
  4. --enable-install-program=hostname \
  5. --enable-no-install-program=kill,uptime

配置选项的含义:

  • —enable-install-program=hostname

该选项表⽰构建 hostname 程序并安装它⸺默认情况下它被禁⽤,但 Perl 测试套件需要它。

编译该软件包:

  1. make

安装该软件包:

  1. make DESTDIR=$LFS install

将程序移动到它们最终安装时的正确位置。在临时环境中这看似不必要,但⼀些程序会硬编码它们的位置,因此必须进⾏这步操作:

  1. mv -v $LFS/usr/bin/chroot $LFS/usr/sbin
  2. mkdir -pv $LFS/usr/share/man/man8
  3. mv -v $LFS/usr/share/man/man1/chroot.1 $LFS/usr/share/man/man8/chroot.8
  4. sed -i 's/"1"/"8"/' $LFS/usr/share/man/man8/chroot.8

6.5 Diffutils-3.8

Diffutils 软件包包含显示文件或目录之间差异的程序

估计构建时间 需要硬盘空间
0.2 SBU 28 MB
  1. tar -xvf diffutils-3.8.tar.xz
  2. mv diffutils-3.8 diffutils
  3. cd diffutils

准备编译 Diffutils:

  1. ./configure --prefix=/usr --host=$LFS_TGT

编译该软件包:

  1. make

安装该软件包:

  1. make DESTDIR=$LFS install

6.6 File-5.41

File 软件包包含用于确定给定文件类型的工具

估计构建时间 需要硬盘空间
0.2 SBU 31 MB
  1. tar -xvf file-5.41.tar.gz
  2. mv file-5.41 file
  3. cd file

宿主系统 file 命令的版本必须和正在构建的软件包相同,才能在构建过程中创建必要的签名数据⽂件。运⾏以下命令,为宿主系统构建它:

  1. mkdir build
  2. pushd build
  3. ../configure --disable-bzlib \
  4. --disable-libseccomp \
  5. --disable-xzlib \
  6. --disable-zlib
  7. make
  8. popd

新的配置选项的含义:

  • —disable-*

如果相关的库⽂件存在,配置脚本企图使⽤宿主发⾏版的⼀些软件包。当库⽂件存在,但对应的头⽂件不存在时,这会导致编译失败。该选项防⽌使⽤这些来⾃宿主系统的⾮必要功能.

准备编译 File:

  1. ./configure --prefix=/usr --host=$LFS_TGT --build=$(./config.guess)

编译该软件包:

  1. make FILE_COMPILE=$(pwd)/build/src/file

安装该软件包:

  1. make DESTDIR=$LFS install

6.7 Findutils-4.8.0

Findutils 软件包包含用于查找文件的程序。这些程序能够递归地搜索⽬录树,以及创建、维护和搜索⽂件数据库 (⼀般⽐递归搜索快,但在数据库最近没有更新时不可靠)。

估计构建时间 需要硬盘空间
0.2 SBU 40 MB
  1. tar -xvf findutils-4.8.0.tar.xz
  2. mv findutils-4.8.0 findutils
  3. cd findutils


准备编译 Findutils:

  1. ./configure --prefix=/usr \
  2. --localstatedir=/var/lib/locate \
  3. --host=$LFS_TGT \
  4. --build=$(build-aux/config.guess)

编译该软件包:

  1. make

安装该软件包:

  1. make DESTDIR=$LFS install

6.8 Gawk

Gawk 软件包包含操作⽂本⽂件的程序。

估计构建时间: 0.2 SBU
需要硬盘空间: 43 MB
  1. tar -xvf gawk-5.1.1.tar.xz
  2. mv gawk-5.1.1 gawk
  3. cd gawk

⾸先,确保不要安装⼀些没有必要的⽂件:

  1. sed -i 's/extras//' Makefile.in

准备编译 Gawk:

  1. ./configure --prefix=/usr \
  2. --host=$LFS_TGT \
  3. --build=$(build-aux/config.guess)

编译该软件包:

  1. make

安装该软件包:

  1. make DESTDIR=$LFS install

6.9 Grep

Grep 软件包包含在⽂件内容中进⾏搜索的程序。

估计构建时间: 0.2 SBU
需要硬盘空间: 25 MB
  1. tar -xvf grep-3.7.tar.xz
  2. mv grep-3.7 grep
  3. cd grep

准备编译 Grep:

  1. ./configure --prefix=/usr \
  2. --host=$LFS_TGT

编译该软件包:

  1. make

安装该软件包:

  1. make DESTDIR=$LFS install

6.10 Gzip

Gzip 软件包包含压缩和解压缩⽂件的程序。

估计构建时间: 0.1 SBU
需要硬盘空间: 10 MB
  1. tar -xvf gzip-1.11.tar.xz
  2. mv gzip-1.11 gzip
  3. cd gzip

准备编译 Gzip:

  1. ./configure --prefix=/usr --host=$LFS_TGT

编译该软件包:

  1. make

安装该软件包:

  1. make DESTDIR=$LFS install

6.11 Make

Make 软件包包含⼀个程序,⽤于控制从软件包源代码⽣成可执⾏⽂件和其他⾮源代码⽂件的过程。

估计构建时间: 0.1 SBU
需要硬盘空间: 15 MB
  1. tar -xvf make-4.3.tar.gz
  2. mv make-4.3 make
  3. cd make

准备编译 Make:

  1. ./configure --prefix=/usr \
  2. --without-guile \
  3. --host=$LFS_TGT \
  4. --build=$(build-aux/config.guess)

新出现的配置选项的含义:

  • —without-guile

尽管我们在进⾏交叉编译,配置脚本如果找到宿主系统的 guile,仍然会试图使⽤它。这导致编译失败,因此使⽤该选项防⽌使⽤ guile。

编译该软件包:

  1. make

安装该软件包:

  1. make DESTDIR=$LFS install