准备宿主系统
准备宿主系统是为了让一个已经存在的 Linux 操作系统具备构建 LFS 的条件。当然如果本来没有 Linux 作为宿主系统,还要加一个构建 Linux 系统的步骤。
在 2.1.1 对宿主系统要求进行介绍后;2.1.2 会假设读者使用的是 Windows,整个实践操作会从安装虚拟机这步开始,使用 Mac 的步骤也类似,如果本来使用的就是 Linux 那是否需要使用虚拟机可以自行判断。推荐初次尝试的读者使用虚拟机,因为 LFS 以学习研究为主,虚拟机的还原点可以很方便的进行多次尝试操作。当然也可以使用 LiveCD 的方式实施构建,就是需要注意如果 LiveCD 本身环境需要调整的话,每次终断后再开进度时需要再调整一次环境,会比较麻烦。
2.1.1 宿主系统要求
构建 LFS 对机器的硬件需求并不高,主要是需要有足够的磁盘空间来容纳 LFS 系统,如果后续需要实际使用 LFS,或是需要多次尝试构建 LFS 及 BLFS 的话,需要对空间有一定的规划。系统环境上其实主要是在 LFS 的构建前期需要使用以及依赖的软件包和软件包版本需要保持正确。
1. 调整分区
一般有两个分区需要率先考虑:
-
LFS 系统的独立分区,其实就是 LFS 系统的“/”分区。不过构建时,一般会将其挂载在宿主机的“/mnt/lfs”处,对于宿主系统而言应该叫做“/mnt/lfs”分区,因为在构建 LFS 过程中 LFS 的“/”分区大多会挂载在“/mnt/lfs”这个位置。该分区最小需要保证 6GB,推荐分配 10GB 以上,如后续需要构建 BLFS 则至少保证 20GB 的大小。当然 10GB 的建议是针对构建 LFS 以学习为目的的读者的;如果想要将 LFS 作为日常的系统,一般 10G 是无法容纳下扩张需求的,应该对分区有更合理的规划。
-
swap 分区,通常被称为交换分区,相当于划分一块硬盘的空间给内存使用,类似于 Windows 的虚拟内存。该分区对于以前内存比较小的机器非常有用,不过如果内存和硬盘交换数据的频率较高的话,其实就证明你应该升级内存了。不过考虑到 LFS 应用的场景一般机器配置不会很高,内存不足的情况估计也不少见,建议创建 swap 分区,一般给物理内存的两倍左右的容量即可。
然后还有一个 GRUBBIOS 分区比较特殊,如果是在 GPT(GUID Partition Table,GUID 分区表)下分 boot 磁盘,那么必然会产生一个约 1MB 左右的小分区(如果之前没有的话)。这个分区可能还没有被格式化,但是它必须存在,GRUB 会在安装引导器的时候用到。如果使用 fdisk,该分区通常会被标记为“BIOS Boot”。而如果使用的是 gdisk,则分区代码应为 EF02 。
最后还有几个在根“/”目录下的几个目录推荐或者在一些使用场景中推荐独立分区,推荐程度和说明见表 2-1 分区推荐表。
| 挂载点 | 推荐程度 | 说明 |
|---|---|---|
/boot | 强烈推荐 | 该目录用于存储内核和其他启动信息。独立分区可以减少大容量磁盘启动时的潜在问题,且应尽量将该分区设为磁盘驱动器上第一个物理分区,以便启动器寻找。推荐分配小于 1G 的容量。 |
/home | 强烈推荐 | 用户的家目录,主要用于存放用户的个人文件,在机器中存在多个 Linux 发行版或多个 LFS 版本时,可以使用独立的 home 分区共享用户的自定义内容。推荐在分配完其他磁盘后将剩余的磁盘空间全部分配给 home 分区。 |
/usr | 可选 | 独立的/usr 分区常见于瘦客户端服务器 或无盘工作站。LFS 通常不需要。5 GB 大小足以满足大部分安装。 |
/opt | 可选 | 该目录多用于在 BLFS 中安装像 Gnome 或 KDE 这样大型的软件包。独立分区可以避免在安装 Gnome 或 KDE 此类软件包的同时,将文件嵌入到/usr 的目录中去。软件的数据和依赖库也会独立存放。如果需要,分配 5-10 GB 即可。 |
/tmp | 可选 | 独立的/tmp 分区是比较少见的,但在配置瘦客户端时会有用。如果使用的话,很少超过几 GB。 |
/usr/src | 可选 | 如果之后还需要构建 BLFS 的话,推荐单独分区。该目录常被用于存储 BLFS 的源码文件,独立分区可能够有利于在不同版本的 LFS 中共享 这些文件。同事该目录也常被用于构建 BLFS 软件包。如果需要,推荐分配 30-50 GB。 |
2. 创建文件系统
LFS 可以使用所有 Linux 内核可以识别的文件系统,当然最常用的类型是 ext3 和 ext4,至于具体采用哪种文件系统,应该取决于所要存储的文件特点和分区的大小。
/boot 分区可以使用的 ext2,因为/boot 本身空间小,启动后也就基本不怎么用了,用不到 ext3、ext4 的新特性。值得注意的是/boot 是否需要用 FAT,如果你使用的是 UEFI 的引导方式,推荐使用 FAT32。
其他文件系统,包括 NTFS、ReiserFS、JFS 和 XFS 等都在专门领域有其独到的作用。关于这些文件系统更多的信息可以参考 http://en.wikipedia.org/wiki/Comparison_of_file_systems。
3. 需求软件及软件版本
构建 LFS 对于宿主系统中存在的软件及版本如表 2-2 所示,推荐以外的版本可能也行,但未经测试,请谨慎使用。如果属主系统中并未包含一下软件包或比较旧的版本请及时更新。
| 软件 | 版本 |
|---|---|
| bash | 3.2 |
| binutils | 2.25 |
| bison | 2.7 |
| bzip2 | 1.0.4 |
| coreutils | 6.9 |
| diffutils | 2.8.1 |
| findutils | 4.2.31 |
| gawk | 4.0.1 |
| gcc | 6.2 |
| glibc | 2.11 |
| grep | 2.5.1a |
| gzip | 1.3.12 |
| Linux Kernel | 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 |
可以在终端运行以下命令创建检查脚本:
cat > version-check.sh << "EOF"
#!/bin/bash
# Simple script to list version numbers of critical development tools
export LC_ALL=C
bash --version | head -n1 | cut -d" " -f2-4
MYSH=$(readlink -f /bin/sh)
echo "/bin/sh -> $MYSH"
echo $MYSH | grep -q bash || echo "ERROR: /bin/sh does not point to bash"
unset MYSH
echo -n "Binutils: "; ld --version | head -n1 | cut -d" " -f3-
bison --version | head -n1
if [ -h /usr/bin/yacc ]; then
echo "/usr/bin/yacc -> `readlink -f /usr/bin/yacc`";
elif [ -x /usr/bin/yacc ]; then
echo yacc is `/usr/bin/yacc --version | head -n1`
else
echo "yacc not found"
fi
bzip2 --version 2>&1 < /dev/null | head -n1 | cut -d" " -f1,6-
echo -n "Coreutils: "; chown --version | head -n1 | cut -d")" -f2
diff --version | head -n1
find --version | head -n1
gawk --version | head -n1
if [ -h /usr/bin/awk ]; then
echo "/usr/bin/awk -> `readlink -f /usr/bin/awk`";
elif [ -x /usr/bin/awk ]; then
echo awk is `/usr/bin/awk --version | head -n1`
else
echo "awk not found"
fi
gcc --version | head -n1
g++ --version | head -n1
ldd --version | head -n1 | cut -d" " -f2- # glibc version
grep --version | head -n1
gzip --version | head -n1
cat /proc/version
m4 --version | head -n1
make --version | head -n1
patch --version | head -n1
echo Perl `perl -V:version`
sed --version | head -n1
tar --version | head -n1
makeinfo --version | head -n1
xz --version | head -n1
echo 'int main(){}' > dummy.c && g++ -o dummy dummy.c
if [ -x dummy ]
then echo "g++ compilation OK";
else echo "g++ compilation failed"; fi
rm -f dummy.c dummy
EOF
然后,运行刚刚创建的脚本 version-check.sh,对当前的宿主环境进行评估:
bash version-check.sh
该脚本将检查表 2-2 中罗列的软件包,并将版本号输出在终端中。
2.1.2 构建宿主系统
本节将示例如何构建一个符合要求的宿主系统。构建过程包括在 Windows 系统上安装虚拟环机,并在虚拟机中实施宿主系统安装,和完善宿主系统使其符合 2.1.1 中罗列的宿主系统要求这两个步骤。Linux 或 Mac 环境也可以按照该步骤构建符合条件的构建宿主系统。当然如果本身使用的是 Linux,也可以选择直接在现有的 Linux 上,准备分区、安装更新必须的软件包。
1. 构建宿主系统
虚拟机的安装过程一般都是傻瓜式的书中便不多赘述了,常用的虚拟机 Virtual Box、VMWare、KVM 皆可。以 Virtual Box 为例,在安装完虚拟机后,新建虚拟电脑,选择系统类型和版本,本书以 Ubuntu 作为宿主系统进行演示。

配置上内存分配了 1G;磁盘分配了 20G,由于在宿主系统以外还需要考虑 LFS 系统,不要分配的过小。创建完成后稍微修改一下网络配置,尽量保证网络配置可以访问 Internet,因为在构建 LFS 前需要下载软件包和补丁。连接方式可以选择“网络地址转换(NAT)”,效果类似于虚拟机帮你做了个路由,好处是省事,缺点是主机在需要访问虚拟机时会比较麻烦,后续在配置 SSH 会提到怎么用端口转发功能来解决这个缺点。
配置完后启动虚拟机,并指定 ubuntu 的 ISO 镜像文件,可以访问 ubuntu 的官网下载 https://ubuntu.com。Ubuntu 的安装过程也是有画面指导选项,不过还记得 2.1.1 提到的需要吗? 我们需要为 LFS 系统配置空间,索性就在 ubuntu 安装的时候就顺便将 LFS 的分区一起规划好。
Ubuntu 的安装步骤如下:
- 选择“安装 Ubuntu”而非试用,然后设置一下语言和键盘。按界面指示进行下一步操作。
- 来到更新和第三方软件设置界面,这里我们选择 Miniamal Installation 选项(最小安装),并勾下载更新的选项。正如说明所述 Normal installation(正常安装)会安装一些娱乐办公的软件,这些对构建 LFS 并无益处。而且我们之前也精打细算过分区大小,没有必要为安装的这些软件耗费时间和空间。同样的下载更新和第三方软件也很花费时间,而且默认使用的下载源的下载非常慢。这里选择先不安装,待替换源后再选取需要软件包自行安装。

- 接下来需要对磁盘进行分区,这里务必选择“Something else”选项,即自定义分区。如果使用全盘安装 ubnutu,那么我们会失去构建 LFS 的空间,到时候调整起来十分麻烦。

紧接着就会进入图 2-3 的分区界面。目前还只有一整块硬盘/dev/sda,接下来我们要对其进行分区。按“New Partition Table…”按钮创建一个分区表。然后可以通过按“+”按钮或者通过右击选择“追加”来追加创建分区。

如图 2-4 所示便是分完区的样子。图 2-4 中的分区表从左到右,每一列分别代表的意思是:分区、文件系统、挂载点、是否格式化、大小、已用大小、已安装的系统。
分区从 sda1 直接跳到 sda4 的原因是 sda1 主分区,从 sda5 开始是逻辑分区。MBR+Grub 的引导模式下,只能创建 4 个主分区,所以跳掉的三个号码是给主分区预留的,创建逻辑分区自然就从 5 开始了。
在第一章曾介绍过 FHS,所以我们对根目录“/”应该不陌生。而在 FHS 中也分别规定了“/boot”和“/home”这两个目录的功能。“/boot”存放启动时需要的文件,“/home”是用户的数据。将这两个目录进行分区处理,是为了方便宿主和 LFS 的启动和数据可以互通。而“/mnt/lfs”是给 LFS 事先准备好的“/”分区,LFS 构建完成正常并启动后,“/”便是现在的“/mnt/lfs”。

关于分区容量以及构建完 LFS 后的实际用量如表 2-3 所示。
| 挂载点 | 安排容量 | 实际用量 | 备注 |
|---|---|---|---|
| /boot | 256MB | 124MB | 容量<1GB,按自身需要来 |
| / | 6GB | 未知 | 根据安装的宿主机系统分区,ubuntu 最小需要 5GB。日常使用的话根据自己的需求可以分配更大的容量 |
| /mnt/lfs | 10GB | 5.1GB | 空间设置的大一点,构建过程中会产生中间文件 |
| /home | 1.5GB | 212KB | 独立分区的情况下 home 分区最好不要分配这么小,由于这里只是演示,并不会用到 home 分区,所以没有必要给太大的容量 |
按“Install Now”按钮继续安装。之后的地域、用户可以根据自己喜好设置即可。
2. 配置 SSH
本节主要内容是宿主机 SSH 的配置,笔者在一开始测试的时候使用的是 ubuntu 桌面的终端来构建 LFS,由于分配的资源不多,构建的途中画面经常会卡死。同时虚拟机里开网页复制构建命令的体验非常不好。所以推荐一个使用 SSH 连接虚拟机的方法。以 Windows 安装 PuTTY 为例进行说明。
由于 ubuntu 没有 SSH 的 Server 端,所以需要先安装 openssh-server:
sudo apt-get install ssh
构建 LFS 的过程需要 root 权限,所以需要更改一下 SSH 的设置,让 root 用户可以用 SSH 登录。日常的 SSH 使用中,这一设置存在安全隐患,所以默认设置也是禁 root 登录的。我们需要更改配置文/etc/ssh/sshd_config 中 PermitRootLogin 相关的设置,更改设置时使用的编辑器可以根据自己习惯进行选择,本书在配置阶段的示例时,将使用 ubuntu 内置的 nano 编辑器。
首先,使用 nano 打开 SSH 的配置文件/etc/ssh/sshd_config:
sudo nano /etc/ssh/sshd_config
找到 PermitRootLogin 相关的设置,类似于下面的示例:
# PermitRootLogin prohibit-password
该行开头的“#”表明这行设置被注释了,不会生效。无论是在这行更改还是另起一行,保证设置成以下形式:
PermitRootLogin yes
更改完后,按 Ctrl+X 退出 nano,退出的时候 nano 会询问你是否保存,保存并关闭配置文件。
更改 root 用户的密码:
sudo passwd root
密码输入两遍,输入不会有提示。
重启一下 SSH 服务,让设置生效:
sudo service ssh restart
之前也提到了笔者使用的是“网络地址转换”(NAT)的连接方式,所以需要设置一下端口转发,SSH 的默认端口是 22,首先右键你的虚拟机选择“设置”,然后在“网络”的“高级”中可以看到“端口转发”的按钮。如果你之前选错了连接方式,你也可以在“网络”中更改。
一般主机端口可以随意设置,子系统端口默认为刚才配置 SSH 的端口 22,根据实际情况调整。图 2-5 是虚拟机 22 端口转发到主机 233 端口。

接下来,使用 PuTTY 等 SSH 的远程连接工具,按照刚才设置的端口进行访问,由于配置过端口转发,所以 IP 应该填写本机 IP 或是回环地址 127.0.0.1。下面是设置的情况,具体如图 2-6 所示。
- IP:127.0.0.1
- Port:233(根据上一步设置的主机端口自行更改)

- 完善宿主系统
安装 Ubuntu 时没有安装更新,是因为使用国内镜像源可以减少更新花费的时间,而在安装时使用未知镜像源,往往会花费大量的时间,即使这样还可能失败。
在配置之前先,我们先备份一下原先的软件源配置文件:
cp /etc/apt/sources.list /etc/apt/sources.list.backup
然后,打开配置文件:
nano /etc/apt/sources.list
可以按 Ctrl+K 删除所有内容,并使用下面给出的软件包镜像源替换。
# 默认注释了源码仓库,如有需要可自行取消注释
deb https://mirrors.ustc.edu.cn/ubuntu/ bionic main restricted universe multiverse
# deb-src https://mirrors.ustc.edu.cn/ubuntu/ bionic main restricted universe multiverse
deb https://mirrors.ustc.edu.cn/ubuntu/ bionic-updates main restricted universe multiverse
# deb-src https://mirrors.ustc.edu.cn/ubuntu/ bionic-updates main restricted universe multiverse
deb https://mirrors.ustc.edu.cn/ubuntu/ bionic-backports main restricted universe multiverse
# deb-src https://mirrors.ustc.edu.cn/ubuntu/ bionic-backports main restricted universe multiverse
deb https://mirrors.ustc.edu.cn/ubuntu/ bionic-security main restricted universe multiverse
# deb-src https://mirrors.ustc.edu.cn/ubuntu/ bionic-security main restricted universe multiverse
# 预发布软件源,不建议启用
# deb https://mirrors.ustc.edu.cn/ubuntu/ bionic-proposed main restricted universe multiverse
# deb-src https://mirrors.ustc.edu.cn/ubuntu/ bionic-proposed main restricted universe multiverse
复制好后按 Ctrl+X 退出 nano,退出的时候 nano 会提示你是否要保存该文件,按 Y 并回车确认。
读者通过以下命令安装更新:
apt update
apt upgrade
更新完后让我们看一下当前的环境是否符合需求。创建以下脚本来查看宿主机系统是否已具备所有软件包的适当版本和编译程序的能力:
cat > version-check.sh << "EOF"
#!/bin/bash
# Simple script to list version numbers of critical development tools
export LC_ALL=C
bash --version | head -n1 | cut -d" " -f2-4
MYSH=$(readlink -f /bin/sh)
echo "/bin/sh -> $MYSH"
echo $MYSH | grep -q bash || echo "ERROR: /bin/sh does not point to bash"
unset MYSH
echo -n "Binutils: "; ld --version | head -n1 | cut -d" " -f3-
bison --version | head -n1
if [ -h /usr/bin/yacc ]; then
echo "/usr/bin/yacc -> `readlink -f /usr/bin/yacc`";
elif [ -x /usr/bin/yacc ]; then
echo yacc is `/usr/bin/yacc --version | head -n1`
else
echo "yacc not found"
fi
bzip2 --version 2>&1 < /dev/null | head -n1 | cut -d" " -f1,6-
echo -n "Coreutils: "; chown --version | head -n1 | cut -d")" -f2
diff --version | head -n1
find --version | head -n1
gawk --version | head -n1
if [ -h /usr/bin/awk ]; then
echo "/usr/bin/awk -> `readlink -f /usr/bin/awk`";
elif [ -x /usr/bin/awk ]; then
echo awk is `/usr/bin/awk --version | head -n1`
else
echo "awk not found"
fi
gcc --version | head -n1
g++ --version | head -n1
ldd --version | head -n1 | cut -d" " -f2- # glibc version
grep --version | head -n1
gzip --version | head -n1
cat /proc/version
m4 --version | head -n1
make --version | head -n1
patch --version | head -n1
echo Perl `perl -V:version`
sed --version | head -n1
tar --version | head -n1
makeinfo --version | head -n1
xz --version | head -n1
echo 'int main(){}' > dummy.c && g++ -o dummy dummy.c
if [ -x dummy ]
then echo "g++ compilation OK";
else echo "g++ compilation failed"; fi
rm -f dummy.c dummy
EOF
创建后,运行该脚本,对当前的宿主环境进行评估:
bash version-check.sh
图 2-8 是脚本的输出。

从图 2-8 可以看出问题和错误不少,我们下面解决一下。
通过第 4 行的 Error 我们得知,目前使用的 shell 是 dash。由于 Ubuntu 的默认的 shell 使用的就是 dash,而 LFS 的构建要求的是 bash,所以我们需要使用 bash 代替 dash,运行以下指令会出现图 2-9 是切换 dash 的命令行界面。
dpkg-reconfigure dash

选择 NO 选项,即可使用 bash 来代替 dash。
图 2-9 中还有若干的“command not found”,意为需要的命令无法找到。接下来安装一些 Ubuntu 初始不安装的软件包,来补足这些命令:
apt install bison gawk gcc g++ make texinfo
等待软件包安装完成后,再运行一遍测试脚本,查看一下情况如何:
bash version-check.sh

运行通过,说明可以保证所有的命令都有,且版本符合要求。可以看到图 2-10 的输出和图 2-8 相比,问题点已被解决,且 g++ 也编译成功。
2.1.3 设置 $LFS 变量
在整本书里,会多次用到环境变量$LFS,应该确保这个变量在整个 LFS 构建过程中总是定义了的。LFS 应该设置为你将要构建的 LFS 系统的目录名,这里我们以/mnt/lfs 作为目录名,但是选择哪一个目录是你的自由。如果你把 LFS 构建到一个单独的分区里,这个目录将成为那个分区的挂载点。选择一个本地目录并用以下命令设置该变量:
export LFS=/mnt/lfs
设置这个变量的好处在于有些命令可以直接写成类似 mkdir -v $LFS/tool 的形式。在处理这条命令时,会自动替换$LFS 为/mnt/lfs(或任何这个变量所指的地方)。
不论何时,当你离开又重新进入这个工作环境时都不要忘了检查 LFS 是否设置(比如当你使用 su 切换到 root 或是另一个用户时)。使用如下命令检查 LFS 变量是否正确设置:
echo $LFS
请确保输出的是你构建 LFS 的那个目录的路径,如果你是按照例子设置的,那就是/mnt/lfs。如果你输出的路径不正确,请使用上述 export 命令把$LFS 重新设置到正确的目录。
确保 LFS 变量一直为设置的一个方法是编辑你 home 目录下的.bash_profile 文件和/root/.bash_profile,并输入上述 export 命令。这种方法仅对将 bash 作为 shell 的用户有作用,所以需要保证/etc/passwd 文件中,所有需要使用$LFS 变量的用户使用的都是 bash。以此来确保/root/.bash_profile 文件作为登录过程的一部分,让 export 产生作用。
另一个方法考虑用户登入宿主系统的方式。如果是通过图形显示管理器登入,那么就不会像寻常那样在虚拟终端启动时,执行用户.bash_profile 文件中的命令。在这种情况下,需要将 export 命令添加至 root 和用户的.bashrc 文件中。另外,在非交互式 bash 的调用中,某些发行版的某些指令,不运行.bashrc 中的命令。在非交互式使用的测试前,请确保 export 命令已经添加。
两个方法虽然修改的文件不同,但修改的内容一致。笔者使用的时第一种的登录方式,所以就以第一种方法中的/root/.bash_profile 为例,首先,打开/root/.bash_profile 文件。
nano /root/.bash_profile
然后输入 export 命令:
export LFS=/mnt/lfs
修改后,保存文件并退出配置文件。
设置完成后,关闭 PuTTY,重新登录再输入 echo $LFS 查看是否生效。这个设置可以保证每次重新登录后,$LFS 就已被正确设置。$LFS 变量非常重要,所以务必在每次登录、切换账户之后,使用 echo 命令确认其是否被正常设置。
2.1.4 挂载新分区
由于在之前 2.1.2 节安装宿主系统的时候创建了分区并设置了挂载点,所以可以跳过本节。如果不是根据 2.1.2 节安装系统,那么就需要挂载$LFS 等分区了。2.1.2 节以外的宿主机配置情况繁多,无法给出特定的操作流程,选择跳过 2.1.2 节需根据 2.1.1 中对于宿主系统的要求完成配置。 假设分区已经调整好,且文件系统也已经创建妥当,下一步就是访问这些分区了。为此,需要将这些建立的分区挂载到选定的挂载点。如前文所述,本书假定的挂载点为环境变量 LFS 指向的地址。 创建挂载点并用下面的命令挂载 LFS 文件系统:
mkdir -pv $LFS
mount -v -t ext4 /dev/<xxx> $LFS
注意,请用准备好的 LFS 分区替代上述命令中的
mkdir -pv $LFS
mount -v -t ext4 /dev/<xxx> $LFS
mkdir -v $LFS/usr
mount -v -t ext4 /dev/<yyy> $LFS/usr
注意,请将
/dev/<xxx> /mnt/lfs ext4 defaults 1 1
如果你还需使用其他可选分区,那就确保将这些可选分区也一并添加。 如果你要使用 swap 分区,请使用 swapon 命令确保它是可用的:
/sbin/swapon -v /dev/<zzz>
请将