LFS准备工作
备注
中文社区翻译的LFS文档在2024年3月1日发布翻译版本12.2已经紧跟官方文档,非常方便实践,主要参考
警告
实际上官方文档包括中文版已经非常严谨,理论上按照文档一步步走下来必然能够完成构建。所以我不是完整摘抄文档,而是做一些笔记,以便后续能够自己重复完成,并且构建自动完成系统,定制能够充分发挥我的现有硬件性能的系统。
请参考管官方文档为准,本章节是一些笔记和自己的补充信息
目标架构
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 12.1 手册发布后发现的安全缺陷列表 请根据安全公告建议在构建过程中对相关操作进行修正
要将 LFS 系统实际用作桌面或服务器系统,那么即使在 LFS 系统构建完成后,也要继续关注安全公告并修复列出的所有安全缺陷
准备宿主机
宿主机(也就是用来编译构建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
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) 执行以下脚本命令:
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)
分区实践:
# 案例: /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
输出信息类似如下:
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 命令的输入来下载所有软件包和补丁:
wget --input-file=wget-list-sysv --continue --directory-prefix=$LFS/sources
使用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
,而且这个目录里面只有一个软链接:
/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
用户,避免微小错误损坏或摧毁系统:
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在运行程序的时候总是搜索PATHumask 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/binif [ ! -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数量:
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读取刚才创建的配置文件:
source ~/.bash_profile