chroot,即 change root,用于改变当前进程及其子进程的根目录到指定的目录,创建一个隔离的环境。这是我们脱离宿主系统的第一步,后续所有的操作都在 chroot 环境下进行。从现在开始,所有的命令都要以 root 身份执行,所以必须要小心,否则可能对 LFS 系统造成损害。
chroot 前准备
第一步是改变所有者。目前,$LFS 目录树的所有者都是 lfs,这个用户只存在于宿主系统,并不存在于 LFS 系统。为此,我们要将 $LFS/* 目录的所有者都改为 root:
chown --from lfs -R root:root $LFS/{usr,var,etc,tools}
case $(uname -m) in
x86_64) chown --from lfs -R root:root $LFS/lib64 ;;
esac
Linux 延续了 UNIX 一切皆文件的设计哲学,根目录下有一些目录是用来和内核进行通信的,比如 /dev、/proc 等,它们被称作虚拟内核文件系统(Virtual Kernel File Systems)。在 chroot 环境中构建系统时,应用程序需要这些接口来获取硬件信息或向内核发送指令,所以必须先把它们挂载到 $LFS 目录树中。
先创建挂载点:
mkdir -pv $LFS/{dev,proc,sys,run}
对于/dev 的挂载,在正常的引导过程中,内核会自动挂载 devtmpfs 到 /dev,LFS 官方书考虑到一些内核可能不支持 devtmpfs,选择绑定挂载宿主系统的 /dev 目录:
mount -v --bind /dev $LFS/dev
接下来挂载其余的虚拟内核文件系统:
mount -vt devpts devpts -o gid=5,mode=0620 $LFS/dev/pts
其中 gid=5 使所有通过 devpts 文件系统创建的设备节点属于编号为 5 的组,mode=0620 使得所有通过 devpts 创建的设备节点的权限模式为 0620,即所属用户可读写,所属组可写。这两个选项共同保证 devpts 创建的设备节点符合 grantpt() 函数的要求,这样就不需要 Glibc 的 pt_chown 辅助程序。
mount -vt proc proc $LFS/proc
mount -vt sysfs sysfs $LFS/sys
mount -vt tmpfs tmpfs $LFS/run
进入 chroot 环境
现在正式 chroot:
chroot "$LFS" /usr/bin/env -i \
HOME=/root \
TERM="$TERM" \
PS1='(lfs chroot) \u:\w\$ ' \
PATH=/usr/bin:/usr/sbin \
MAKEFLAGS="-j$(nproc)" \
TESTSUITEFLAGS="-j$(nproc)" \
/bin/bash --login
chroot "$LFS":将根目录切换至$LFS变量所指的路径,使该路径在当前会话中表现为系统的根目录/。/usr/bin/env -i:运行env程序;-i参数表示忽略当前宿主机的环境变量,创建一个全新的空白环境。HOME=/root:在 chroot 环境中设置 root 用户的 HOME 目录为/root。TERM="$TERM":将宿主机的终端类型(如xterm-256color)传递进去,确保在 chroot 中使用vim或top等工具时能正确渲染显示。PS1='(lfs chroot) \u:\w\$ ':定义 shell 的提示符格式,显示当前用户、当前路径。PATH=/usr/bin:/usr/sbin:重置可执行文件的搜索路径,确保只使用新系统中已安装的工具。MAKEFLAGS="-j$(nproc)":设置make编译时的并行作业数为宿主机的 CPU 核心数。TESTSUITEFLAGS="-j$(nproc)":设置运行软件包的测试套件(Check/Test)时启用多线程并行测试。/bin/bash --login:在 chroot 环境中启动 Bash;带--login参数确保读取/etc/profile等登录配置文件,使环境完整生效。
此时我们会进入 chroot 后的 Bash,其提示符为 I have no name!,由于我们还没有创建 /etc/passwd 文件,这是正常的。
接下来基于 FHS 创建一些必要的目录:
mkdir -pv /{boot,home,mnt,opt,srv}
mkdir -pv /etc/{opt,sysconfig}
mkdir -pv /lib/firmware
mkdir -pv /media/{floppy,cdrom}
mkdir -pv /usr/{,local/}{include,src}
mkdir -pv /usr/lib/locale
mkdir -pv /usr/local/{bin,lib,sbin}
mkdir -pv /usr/{,local/}share/{color,dict,doc,info,locale,man}
mkdir -pv /usr/{,local/}share/{misc,terminfo,zoneinfo}
mkdir -pv /usr/{,local/}share/man/man{1..8}
mkdir -pv /var/{cache,local,log,mail,opt,spool}
mkdir -pv /var/lib/{color,misc,locate}
ln -sfv /run /var/run
ln -sfv /run/lock /var/lock
install -dv -m 0750 /root
install -dv -m 1777 /tmp /var/tmp
默认情况下所有新建目录的权限都是 755,除了最后的 /root 和 /tmp、/var/tmp。/root 被设置为了 0750,即仅限 root 及其组访问;/tmp 和 /var/tmp 被设置为了 1777,即所有人可写,但只能删除自己的文件。
必要的文件和符号链接
Linux 过去在 /etc/mtab 维护挂载文件系统列表,现代 Linux 直接在内核中维护,为了满足一些仍然使用 /etc/mtab 的工具,就要创建符号链接:
ln -sv /proc/self/mounts /etc/mtab
接下来创建一个 hosts 文件,映射 localhost 到 127.0.0.1:
cat > /etc/hosts << EOF
127.0.0.1 localhost $(hostname)
::1 localhost
EOF
为了使 root 能正常登录,需要 /etc/passwd 文件:
cat > /etc/passwd << "EOF"
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/dev/null:/usr/bin/false
daemon:x:6:6:Daemon User:/dev/null:/usr/bin/false
messagebus:x:18:18:D-Bus Message Daemon User:/run/dbus:/usr/bin/false
systemd-journal-gateway:x:73:73:systemd Journal Gateway:/:/usr/bin/false
systemd-journal-remote:x:74:74:systemd Journal Remote:/:/usr/bin/false
systemd-journal-upload:x:75:75:systemd Journal Upload:/:/usr/bin/false
systemd-network:x:76:76:systemd Network Management:/:/usr/bin/false
systemd-resolve:x:77:77:systemd Resolver:/:/usr/bin/false
systemd-timesync:x:78:78:systemd Time Synchronization:/:/usr/bin/false
systemd-coredump:x:79:79:systemd Core Dumper:/:/usr/bin/false
uuidd:x:80:80:UUID Generation Daemon User:/dev/null:/usr/bin/false
systemd-oom:x:81:81:systemd Out Of Memory Daemon:/:/usr/bin/false
nobody:x:65534:65534:Unprivileged User:/dev/null:/usr/bin/false
EOF
其中每一行代表一个用户。拿第一行的 root 来说:
- 用户名 (
root):登录时输入的名称。 - 密码占位符 (
x):表示密码加密后存储在/etc/shadow文件中。 - 用户 ID (
0):UID。系统识别用户的唯一数字。0 永远属于 root。 - 组 ID (
0):GID。该用户所属主要组的 ID。 - 备注/全名 (
root):对用户的描述信息。 - HOME 目录 (
/root):用户登录后的起始目录。 - 登录 Shell (
/bin/bash):用户登录后启动的程序。
至于密码,后续会设置。
除了 /etc/passwd 之外,Linux 用户管理需要的另一个重要文件就是定义组的 /etc/group:
cat > /etc/group << "EOF"
root:x:0:
bin:x:1:daemon
sys:x:2:
kmem:x:3:
tape:x:4:
tty:x:5:
daemon:x:6:
floppy:x:7:
disk:x:8:
lp:x:9:
dialout:x:10:
audio:x:11:
video:x:12:
utmp:x:13:
clock:x:14:
cdrom:x:15:
adm:x:16:
messagebus:x:18:
systemd-journal:x:23:
input:x:24:
mail:x:34:
kvm:x:61:
systemd-journal-gateway:x:73:
systemd-journal-remote:x:74:
systemd-journal-upload:x:75:
systemd-network:x:76:
systemd-resolve:x:77:
systemd-timesync:x:78:
systemd-coredump:x:79:
uuidd:x:80:
systemd-oom:x:81:
wheel:x:97:
users:x:999:
nogroup:x:65534:
EOF
其中每一行代表一个组。拿第一行的 root 来说:
- 组名 (
root): 组的名字 - 密码占位符 (
x): 现代系统通常不会把组密码直接存放在/etc/group中,这里一般只是占位符。 - 组 ID (
0): 组的 GID。 - 成员列表: 这里为空。对于第二行的 daemon 来说,表示 daemon 除了自己的初始组外,还属于 bin 组。
GID 65534 被内核用于 NFS 和用户命名空间,以表示未映射的用户或组。我们把它分配给 nobody 和 nogroup,但是其他发行版可能用不同方式处理这个编号。
后续的一些测试需要用到一个非 root 用户,在这里创建一个,用完后再删除:
echo "tester:x:101:101::/home/tester:/bin/bash" >> /etc/passwd
echo "tester:x:101:" >> /etc/group
install -o tester -d /home/tester
重新启动 Bash,移除 I have no name! 提示符:
exec /usr/bin/bash --login
安装基本系统软件
接下来是漫长的编译软件包环节,由于编译过程都是完全一样的,这里就略过了。所有的软件包及其作用都可以在官方书的 Chapter 8 找到。
我在编译所有软件包时都用了 -march=native 参数,官方书说这样的行为是为定义的,但对于我的机器来说结果很成功。虽然性能提升就那么一点,但足以让人很兴奋了。
编译内核
现在进入最具挑战性的环节——编译内核。
内核配置有 12000 多个选项,非常复杂。LFS 官方的 Kernel configuration 介绍了内核配置的常识。
编译内核分为三步:配置、编译、安装
配置
在配置之前,先确保内核源码树是干净的:
make mrproper
在源码树 make 一个 menuconfig 即可进入配置页面:
make menuconfig
这会默认启动 ncurses 驱动的页面:

LFS 官方对于内核编译做如下配置:
General setup --->
[ ] Compile the kernel with warnings as errors [WERROR]
CPU/Task time and stats accounting --->
[*] Pressure stall information tracking [PSI]
[ ] Require boot parameter to enable pressure stall information tracking
... [PSI_DEFAULT_DISABLED]
< > Enable kernel headers through /sys/kernel/kheaders.tar.xz [IKHEADERS]
[*] Control Group support ---> [CGROUPS]
[*] Memory controller [MEMCG]
[ /*] CPU controller ---> [CGROUP_SCHED]
# This may cause some systemd features malfunction:
[ ] Group scheduling for SCHED_RR/FIFO [RT_GROUP_SCHED]
[ ] Configure standard kernel features (expert users) ---> [EXPERT]
Processor type and features --->
[*] Build a relocatable kernel [RELOCATABLE]
[*] Randomize the address of the kernel image (KASLR) [RANDOMIZE_BASE]
General architecture-dependent options --->
[*] Stack Protector buffer overflow detection [STACKPROTECTOR]
[*] Strong Stack Protector [STACKPROTECTOR_STRONG]
[*] Networking support ---> [NET]
Networking options --->
[*] TCP/IP networking [INET]
<*> The IPv6 protocol ---> [IPV6]
Device Drivers --->
Generic Driver Options --->
[ ] Support for uevent helper [UEVENT_HELPER]
[*] Maintain a devtmpfs filesystem to mount at /dev [DEVTMPFS]
[*] Automount devtmpfs at /dev, after the kernel mounted the rootfs
... [DEVTMPFS_MOUNT]
Firmware loader --->
< /*> Firmware loading facility [FW_LOADER]
[ ] Enable the firmware sysfs fallback mechanism [FW_LOADER_USER_HELPER]
Firmware Drivers --->
[*] Export DMI identification via sysfs to userspace [DMIID]
[*] Mark VGA/VBE/EFI FB as generic system framebuffer [SYSFB_SIMPLEFB]
Graphics support --->
<*> Direct Rendering Manager (XFree86 4.1.0 and higher DRI support) --->
... [DRM]
[*] Display a user-friendly message when a kernel panic occurs
... [DRM_PANIC]
(kmsg) Panic screen formatter [DRM_PANIC_SCREEN]
Supported DRM clients --->
[*] Enable legacy fbdev support for your modesetting driver
... [DRM_FBDEV_EMULATION]
Drivers for system framebuffers --->
<*> Simple framebuffer driver [DRM_SIMPLEDRM]
Console display driver support --->
[*] Framebuffer Console support [FRAMEBUFFER_CONSOLE]
File systems --->
[*] Inotify support for userspace [INOTIFY_USER]
Pseudo filesystems --->
[*] Tmpfs virtual memory file system support (former shm fs) [TMPFS]
[*] Tmpfs POSIX Access Control Lists [TMPFS_POSIX_ACL]
对于 64 位系统,启用 CONFIG_PCI_MSI 和 CONFIG_IRQ_REMAP:
Processor type and features --->
[*] x2APIC interrupt controller architecture support [X86_X2APIC]
Device Drivers --->
[*] PCI support ---> [PCI]
[*] Message Signaled Interrupts (MSI and MSI-X) [PCI_MSI]
[*] IOMMU Hardware Support ---> [IOMMU_SUPPORT]
[*] Support for Interrupt Remapping [IRQ_REMAP]
对于 NVME 的 SSD,启用 NVME 支持:
Device Drivers --->
NVME Support --->
<*> NVM Express block device [BLK_DEV_NVME]
对于这些参数的作用,官方书 Kernel 章节做了详细的解释。
编译
第二步编译,就是简单的 make:
make
我花了 1 分 24 秒来编译内核。
安装
make 会编译出来一个叫做 bzImage 的内核二进制镜像,我们需要把它放到 Linux 启动文件目录 /boot 中,以便 Grub 找到内核:
cp -iv arch/x86/boot/bzImage /boot/vmlinuz-6.18.10-lfs-13.0-systemd
/boot 中镜像的名字可以改变,但开头一般都是 vmlinuz,其中 vm 表示 Virtual Memory(虚拟内存),linu 表示 Linux,z 表示Compressed(压缩)。
System.map 是内核符号文件,将内核 API 的每个函数入口点和运行时数据结构映射到它们的地址。也需要复制到 /boot 中:
cp -iv System.map /boot/System.map-6.18.10
安装 Linux 内核文档:
cp -r Documentation -T /usr/share/doc/linux-6.18.10
最后做一些收尾工作。比如创建一个 /etc/lsb-realse:
cat > /etc/lsb-release << "EOF"
DISTRIB_ID="Linux From Scratch"
DISTRIB_RELEASE="13.0-systemd"
DISTRIB_CODENAME="lnvitesace"
DISTRIB_DESCRIPTION="Linux From Scratch"
EOF
当然还有 os-release:
cat > /etc/os-release << "EOF"
NAME="Linux From Scratch"
VERSION="13.0-systemd"
ID=lfs
PRETTY_NAME="Linux From Scratch 13.0-systemd"
VERSION_CODENAME="lnvitesace"
HOME_URL="https://www.linuxfromscratch.org/lfs/"
RELEASE_TYPE="stable"
EOF
使 LFS 可引导
我的宿主机已经有 Grub,这里并不打算重新安装一遍,而是就用宿主机的 Grub 引导 LFS。
最简单的方式当然是 os-prober。在宿主机上更新 Grub:
sudo grub-mkconfig -o /boot/grub/grub.cfg
1
Generating grub configuration file ...
Found theme: /usr/share/grub/themes/vimix/theme.txt
Warning: os-prober will be executed to detect other bootable partitions.
Its output will be used to detect bootable binaries on them and create new boot entries.
Found Linux on /dev/nvme0n1p4
Found linux image: /boot/vmlinuz-linux
Found initrd image: /boot/intel-ucode.img /boot/initramfs-linux.img
Warning: os-prober will be executed to detect other bootable partitions.
Its output will be used to detect bootable binaries on them and create new boot entries.
Found Linux on /dev/nvme0n1p4
Adding boot menu entry for UEFI Firmware Settings ...
done
这说明 os-prober 成功识别到了位于 /dev/nvme0n1p4 的 Linux 系统,并为它生成了可供 Grub 使用的启动项。这里显示的是探测结果,不是直接在说 LFS 的内核镜像已经被精确匹配完成。接下来重启电脑,在宿主机的 Grub 中选择新加入的启动项即可。

启动成功!最后别忘记去官网的 lfscounter 注册一个自己的名字
