LFS准备工作

备注

中文社区翻译的LFS文档在2024年3月1日发布翻译版本12.2已经紧跟官方文档,非常方便实践,主要参考

备注

LFS分为两个分支:

考虑到我的底座系统是为了运行 KVMDocker ,没有复杂的主机服务,纯粹是运行环境,所以我目前采用 sysv 版本

警告

实际上官方文档包括中文版已经非常严谨,理论上按照文档一步步走下来必然能够完成构建。所以我不是完整摘抄文档,而是做一些笔记,以便后续能够自己重复完成,并且构建自动完成系统,定制能够充分发挥我的现有硬件性能的系统。

请参考管官方文档为准,本章节是一些笔记和自己的补充信息

目标架构

LFS的目标架构是 AMD/Intel 的 x86(32位) 和 x86_64(64位) CPU (需要修订一些指令才能适用于Power PC和 ARM 架构的CPU)。

构建LFS至少需要一个现有的Linux系统:

  • 可以是一个已经正在运行的Linux系统,现代Linux发行版(需要较新版本,对内核有和工具链有要求),我在 在 mba11_late_2010 部署LFS 中先安装一个 Arch Linux 作为编译基础环境(同时也是向arch linux发行版学习软件组合和配置方法)

  • 也可以是某个发行版的LiveCD运行一个编译环境,例如我就使用 Fedora 的一个LiveCD来完成,具体是通过 创建KVM虚拟机 运行Fedoraa LiveCD系统来编译构建

32位 vs. 64位

  • LFS官方文档是构建纯粹的64位系统,也就是值运行64位可执行程序

  • 如果要构建 multi-lib 系统(同时支持32位和64位)则需要两次编译很多应用(考虑到现代硬件需要巨大的内存,远超4GB,所以我也按照官方文档构建 纯粹64位系统 以便获得一个精简的能够用于物理主机host底座的OS)

软件包选择

LFS的目标是构建一个完整且基本可用的系统 ,但是LFS并不是最小可用系统,因为LFS中有一些软件包并不是必须安装的(具体列表见 LFS vi. 本书选择软件包的逻辑 )

勘误和安全公告

准备宿主机

宿主机(也就是用来编译构建LFS的物理主机):

  • 建议至少4个CPU,内存8GB

  • LFS 宿主系统需求 列出了基本软件包版本要求(大多数发行版都能满足),并且提供了一个检查脚本

执行检查主机环境
cat > version-check.sh << "EOF"
#!/bin/bash
# A script to list version numbers of critical development tools

# If you have tools installed in other directories, adjust PATH here AND
# in ~lfs/.bashrc (section 4.4) as well.

LC_ALL=C 
PATH=/usr/bin:/bin

bail() { echo "FATAL: $1"; exit 1; }
grep --version > /dev/null 2> /dev/null || bail "grep does not work"
sed '' /dev/null || bail "sed does not work"
sort   /dev/null || bail "sort does not work"

ver_check()
{
   if ! type -p $2 &>/dev/null
   then 
     echo "ERROR: Cannot find $2 ($1)"; return 1; 
   fi
   v=$($2 --version 2>&1 | grep -E -o '[0-9]+\.[0-9\.]+[a-z]*' | head -n1)
   if printf '%s\n' $3 $v | sort --version-sort --check &>/dev/null
   then 
     printf "OK:    %-9s %-6s >= $3\n" "$1" "$v"; return 0;
   else 
     printf "ERROR: %-9s is TOO OLD ($3 or later required)\n" "$1"; 
     return 1; 
   fi
}

ver_kernel()
{
   kver=$(uname -r | grep -E -o '^[0-9\.]+')
   if printf '%s\n' $1 $kver | sort --version-sort --check &>/dev/null
   then 
     printf "OK:    Linux Kernel $kver >= $1\n"; return 0;
   else 
     printf "ERROR: Linux Kernel ($kver) is TOO OLD ($1 or later required)\n" "$kver"; 
     return 1; 
   fi
}

# Coreutils first because --version-sort needs Coreutils >= 7.0
ver_check Coreutils      sort     8.1 || bail "Coreutils too old, stop"
ver_check Bash           bash     3.2
ver_check Binutils       ld       2.13.1
ver_check Bison          bison    2.7
ver_check Diffutils      diff     2.8.1
ver_check Findutils      find     4.2.31
ver_check Gawk           gawk     4.0.1
ver_check GCC            gcc      5.2
ver_check "GCC (C++)"    g++      5.2
ver_check Grep           grep     2.5.1a
ver_check Gzip           gzip     1.3.12
ver_check M4             m4       1.4.10
ver_check Make           make     4.0
ver_check Patch          patch    2.5.4
ver_check Perl           perl     5.8.8
ver_check Python         python3  3.4
ver_check Sed            sed      4.1.5
ver_check Tar            tar      1.22
ver_check Texinfo        texi2any 5.0
ver_check Xz             xz       5.0.0
ver_kernel 4.19

if mount | grep -q 'devpts on /dev/pts' && [ -e /dev/ptmx ]
then echo "OK:    Linux Kernel supports UNIX 98 PTY";
else echo "ERROR: Linux Kernel does NOT support UNIX 98 PTY"; fi

alias_check() {
   if $1 --version 2>&1 | grep -qi $2
   then printf "OK:    %-4s is $2\n" "$1";
   else printf "ERROR: %-4s is NOT $2\n" "$1"; fi
}
echo "Aliases:"
alias_check awk GNU
alias_check yacc Bison
alias_check sh Bash

echo "Compiler check:"
if printf "int main(){}" | g++ -x c++ -
then echo "OK:    g++ works";
else echo "ERROR: g++ does NOT work"; fi
rm -f a.out

if [ "$(nproc)" = "" ]; then
   echo "ERROR: nproc is not available or it produces empty output"
else
   echo "OK: nproc reports $(nproc) logical cores are available"
fi
EOF

bash version-check.sh
  • 如果和我一样使用 LFS虚拟机 ,并且是 Fedora ,可以先安装基本开发工具链:

在Fedora环境安装LFS编译环境
dnf install coreutils bash binutils bison diffutils findutils gawk \
    gcc gcc-c++ grep gzip m4 make patch perl-core python3 sed tar texinfo xz

不过,这里还有一个报错:

ERROR: yacc is NOT Bison

参考 How can i change yacc to bison (LFS) 执行以下脚本命令:

设置yacc
cat > /usr/bin/yacc << "EOF"
#!/bin/sh
# Begin /usr/bin/yacc

/usr/bin/bison -y $*

# End /usr/bin/yacc
EOF
chmod 755 /usr/bin/yacc

分阶段构建LFS

LFS被设计成在一次会话中构建完成,也就是架设整个编译过程中,系统不会关闭或重启。不过,并非要求严格地一气呵成,需要注意如果重启后继续编译LFS,根据进度不同,可能需要再次进行某些操作。

LFS分区

LFS对分区没有硬性规定,但是有一些建议:

  • 最小的系统大约需要10G空间来存储源代码并编译所有软件包,不过作为日常Linux系统,建议使用30G空间以便后续添加功能

  • 内存RAM需要8G以上,不建议使用SWAP(会影响性能)而是建议如果出现使用swap情况则扩容内存

  • LFS分区建议使用成熟的文件系统,例如 EXT文件系统XFS文件系统 ,对于后续存储数据可以使用更为高级的文件系统,如 ZFS (这是我的建议)

  • 如果启动磁盘采用GUID分区表(GPT),则必须创建一个小的大约1MB的分区,这个分区不能格式化,但是必须被启动引导器GRUB发现,且这个分区在 fdisk 下显示为 BIOS Boot 分区,而使用 gdisk 命令则显示分区类型代号位 EF02

常用分区

以下是建议使用的分区(作为参考):

  • /boot 分区,存储内核以及引导信息。为了减少大磁盘可能引起的问题,建议将 /boot 分区设置为第一块磁盘的第一个分区,建议分配 512MB 或 1GB (我的建议,因为需要存储不同内核进行测试;原文建议200MB,对于我的实践经验来看这样大小的分区可能不能存储2个或以上内核)

  • /boot/efi EFI系统分区,对于使用UEFI引导系统是必要的

  • /home 分区,这个分区我准备后续用 ZFS 来构建

  • /usr 分区通常不用独立划分(可选,我没有使用独立分区)

  • /opt 分区(可选,我没有使用独立分区)

  • /tmp 分区,一般是配置瘦客户机时使用,如果系统有足够内存,可以在 /tmp 挂载一个 tmpfs 以加速访问临时文件

  • /usr/src 用于存储BLFS源代码,并且可以在多个LFS系统之间共享;可以用来编译BLFS软件包,通常划分 30-50 GB

LFS假设根文件系统 / 采用 ext4 文件系统 (考虑到简化内核且根分区并没有极端的性能要求,我目前按照官方文档采用默认的 ext4 文件系统)

我的分区

  • /dev/sda1 vfat 格式, EFI System ,设置200M

  • /dev/sda2 EXT文件系统 4 文件系统,挂载为 /boot 分区,设置512M

  • /dev/sda3 EXT文件系统 4 文件系统,直接挂载 / 根分区

  • 如果是物理服务器 HPE ProLiant DL360 Gen9服务器 ,我会把大容量SSD磁盘再划分一个 /dev/sda4 作为 ZFS 存储(虚拟机则单独配置一块虚拟磁盘来构建ZFS)

分区实践:

对LFS分区
# 案例: /dev/vdb (第二块虚拟磁盘)作为LFS磁盘

# 初始化磁盘分区表
parted /dev/vdb mklabel gpt

# 创建第一个vdb1分区,用于EFI启动
parted -a optimal /dev/vdb mkpart ESP fat32 0% 256MB
parted /dev/vdb set 1 esp on

# /boot分区 vdb2,分配512MB, ext4
parted -a optimal /dev/vdb mkpart bootfs ext4 256MB 768MB

# /分区 vdb3,所有剩余磁盘, ext4
parted -a optimal /dev/vdb mkpart rootfs ext4 768MB 100%

# 完成后检查
parted /dev/vdb print

# 分区格式化
mkfs.vfat -F 32 -n EFI /dev/vdb1
mkfs.ext4 /dev/vdb2
mkfs.ext4 /dev/vdb3

完成后执行 parted /dev/vdb print 输出信息类似如下:

对LFS分区后分区信息
Model: Virtio Block Device (virtblk)
Disk /dev/vdb: 32.2GB
Sector size (logical/physical): 512B/512B
Partition Table: gpt
Disk Flags: 

Number  Start   End     Size    File system  Name    Flags
 1      1049kB  256MB   255MB   fat32        ESP     boot, esp
 2      256MB   768MB   512MB   ext4         bootfs
 3      768MB   32.2GB  31.4GB  ext4         rootfs

设置环境变量

  • /etc/profile 中配置:

/etc/profile 中添加LFS环境变量
export LFS=/mnt/lfs

挂载分区

  • 创建 /mnt/lfs 目录,并挂载分区

挂载分区
mkdir -pv $LFS
mount -v -t ext4 /dev/vdb3 $LFS
mkdir -pv $LFS/boot
mount -v -t ext4 /dev/vdb2 $LFS/boot
  • 完成后使用 df -h 检查

挂载分区情况
Filesystem      Size  Used Avail Use% Mounted on
...
/dev/vdb3        29G  2.1M   28G   1% /mnt/lfs
/dev/vdb2       448M  136K  419M   1% /mnt/lfs/boot

准备工作已经完成,现在可以开始准备软件包了

软件包下载

  • 创建软件包和补丁的存放目录,按照文档,建议 $LFS/sources 目录:

创建 $LFS/sources 目录用于存放源代码
mkdir -v $LFS/sources
# 目录添加写入权限和sticky标志(表示只有文件所有者能够删除文件)
chmod -v a+wt $LFS/sources
下载所有 wget-list-sysv 列出的软件包和补丁
wget --input-file=wget-list-sysv --continue --directory-prefix=$LFS/sources

使用LFS提供的 md5sums 文件校验下载的软件包:

使用LFS提供的 md5sums 文件校验下载的软件包
pushd $LFS/sources
  md5sum -c md5sums
popd

备注

对于LFS稳定版,可以从 LFS官方镜像网站直接下载打包好的软件包文件

备注

部分文件可能会因为上游下载源更改无法下载,需要手工处理

此外,还需要 下载LFS必要的补丁 (需要按照文档内容)

在LFS文件系统中创建有限目录布局

  • 以root身份执行以下命令创建所需目录布局:

创建目录布局
mkdir -pv $LFS/{etc,var} $LFS/usr/{bin,lib,sbin}

for i in bin lib sbin; do
  ln -sv usr/$i $LFS/$i
done

case $(uname -m) in
  x86_64) mkdir -pv $LFS/lib64 ;;
esac
创建目录布局的输出信息
mkdir: created directory '/mnt/lfs/etc'
mkdir: created directory '/mnt/lfs/var'
mkdir: created directory '/mnt/lfs/usr'
mkdir: created directory '/mnt/lfs/usr/bin'
mkdir: created directory '/mnt/lfs/usr/lib'
mkdir: created directory '/mnt/lfs/usr/sbin'

'/mnt/lfs/bin' -> 'usr/bin'
'/mnt/lfs/lib' -> 'usr/lib'
'/mnt/lfs/sbin' -> 'usr/sbin'

mkdir: created directory '/mnt/lfs/lib64'
  • 此外为交叉编译器准备一个专用目录,使得其和其他程序分离:

创建交叉编译器目录
mkdir -pv $LFS/tools

警告

LFS不使用 /usr/lib64 目录,一定要确保该目录不存在,否则可能破坏系统。需要经常检查并确认该目录不存在

但是,我发现 Ubuntu Linux 就具有 /usr/lib64 ,而且这个目录里面只有一个软链接:

Ubuntu Linux 使用了 /usr/lib64 ,该目录下有一个非常关键的软链接
# ls -lh /usr/lib64
total 0
lrwxrwxrwx 1 root root 42 May  7  2024 ld-linux-x86-64.so.2 -> /lib/x86_64-linux-gnu/ld-linux-x86-64.so.2

但是 但是Ubuntu Linux 这个 /usr/lib64 目录 千万不能移除 ,不要看只有一个软链接,现在系统中有太多软件依赖这个遗留的软链接。我好死不活尝试移除这个目录:

尝试移除 /usr/lib64 ,整个系统无法运行了
mv /usr/lib64 /usr/lib64.bak

接下来发现,任何系统命令都无法运行,都显示文件不存在:

# groupadd
-bash: /usr/sbin/groupadd: No such file or directory

ssh 登陆也会提示bash不存在:

Last login: Tue Dec 10 10:59:43 2024 from 192.168.7.221
/bin/bash: No such file or directory
Shared connection to 192.168.7.200 closed.

也就是说, bash 运行依赖这个库文件软链接,没有它系统无法工作

添加LFS用户

  • 创建 lfs 用户,避免微小错误损坏或摧毁系统:

创建lfs用户
groupadd lfs
useradd -s /bin/bash -g lfs -m -k /dev/null lfs

如果是使用 root 用户身份切换到 lfs 用户,则不要求 lfs 用户帐号设置过密码;其他用户如果要切换到 lfs 用户,则需要为 lfs 用户设置密码( passwd lfs )

  • lfs 设置为 $LFS 中所有目录的所有者,这样 lfs 就对它们拥有 完全访问权 :

设置 $LFS 目录属主为 lfs 用户
chown -v lfs $LFS/{usr{,/*},lib,var,etc,bin,sbin,tools}
case $(uname -m) in
  x86_64) chown -v lfs $LFS/lib64 ;;
esac

# 还需要将 sources 目录改成lfs属主,否则解压缩文件会报错
chown -v lfs $LFS/sources
  • root 切换身份到 lfs ,后续操作以 lfs 用户身份来执行(避免出现误操作破坏系统):

    su - lfs
    

配置环境

为了配置良好工作环境,需要为bash创建两个新的启动脚本,以下命令以 lfs 身份执行,创建一个新的 .bash_profile :

创建 lfs 用户的 .bash_profile
cat > ~/.bash_profile << "EOF"
exec env -i HOME=$HOME TERM=$TERM PS1='\u:\w\$ ' /bin/bash
EOF

上述 .bash_profile 中采用 exec env -i .../bin/bash 可以确保除了 HOME, TERM 以及 PS1 之外没有任何环境变量的 shell,防止宿主机环境中有不需要和有潜在风险的环境变量进入构建环境。

  • 由于新的shell实例是 非登录shell ,所以它不会读取和执行 /etc/profile 或者 .bash_profile 中内容,而是读取并执行 .bashrc ,所以我们现在需要创建一个 .bashrc 文件:

lfs 的非登录shell创建一个独立使用的 ~/.bashrc
cat > ~/.bashrc << "EOF"
set +h
umask 022
LFS=/mnt/lfs
LC_ALL=POSIX
LFS_TGT=$(uname -m)-lfs-linux-gnu
PATH=/usr/bin
if [ ! -L /bin ]; then PATH=/bin:$PATH; fi
PATH=$LFS/tools/bin:$PATH
CONFIG_SITE=$LFS/usr/share/config.site
export LFS LC_ALL LFS_TGT PATH CONFIG_SITE
EOF

.bashrc 中设置含义(部分摘录参考):

  • set +h 关闭bash的散列功能。bash使用一个散列表维护各个可执行文件的完整路径,这样就不用每次都在 PATH 指定的目录中搜索可执行文件。这是一个非常有用的功能,但是在LFS构建中,我们希望总是使用最新安装的工具,所以关闭散列功能来强制shell在运行程序的时候总是搜索PATH

  • umask 022 将用户的文件创建掩码(umask)设置为 022 ,确保只有文件的所有者可以写新创建的文件和目录,但是其他任何人都可以读取、执行它们

  • LFS=/mnt/lfs LFS 环境变量必须被设定为之前选择的挂载点

  • C_ALL=POSIX 将 LC_ALL 设置为 “POSIX” 或者 “C”(这两种设置是等价的) 可以保证在交叉编译环境中所有命令的行为完全符合预期,而与宿主的本地化设置无关

  • LFS_TGT=$(uname -m)-lfs-linux-gnu LFS_TGT变量设定了一个非默认,但与宿主系统兼容的机器描述符。该描述符被用于构建交叉编译器和交叉编译临时工具链

  • PATH=/usr/bin 许多现代 Linux 发行版合并了 /bin 和 /usr/bin。在这种情况下,标准 PATH 变量应该被设定为 /usr/bin

  • if [ ! -L /bin ]; then PATH=/bin:$PATH; fi 如果 /bin 不是符号链接,则它需要被添加到 PATH 变量中

  • PATH=$LFS/tools/bin:$PATH 将 $LFS/tools/bin 附加在默认的 PATH 环境变量之前,一旦安装了新的程序,shell 就能立刻使用它们

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

  • export ... 设定了一些变量,为了让所有子 shell 都能使用这些变量

对于多CPU core的主机,可以并行执行make,所以在 .bashrc 添加以下配置(如果不希望所有cpu core被使用,则将 $(nproc) 替换成希望使用的cpu core数量:

为make程序设置使用的cpu core数量
cat >> ~/.bashrc << "EOF"
export MAKEFLAGS=-j$(nproc)

export CFLAGS="-O3 -march=native"
export CXXFLAGS=$CFLAGS
EOF

备注

LFS optimize.txt 介绍了类似 Gentoo Linux 配置 make.conf 的优化方法。也就是可以进一步配置GCC optimize级别以及生成针对CPU架构优化的执行程序。

所以,我参考 在MacBook Pro上安装Gentoo Linux 的配置,修订了上述 make 的参数

Is optimisation level -O3 dangerous in g++? 讨论了O3级别优化,可以参考

gentoo linux wiki: GCC optimization 有详细的说明,建议参考

不过,我注意到实际运行编译参数是 -g -O2 -O3 -mach=native ... 那么究竟是 O3 还是 O2 优化呢?

参考 GCC官方文档: Options That Control Optimization : If you use multiple -O options, with or without level numbers, the last such option is the one that is effective. ,也就是说,后一个 -O 参数实际生效,也就是 -O3

最后确保构建临时工具所需环境就绪,强制bash shell读取刚才创建的配置文件:

强制shell读取配置文件
source ~/.bash_profile

参考