进入 chroot 环境
chroot 指令可以分解成 Change 和 Root,非常直接的表明该指令的用途——用于改变根(/)目录。这里的特别标注是根(/)目录,而非根(/)目录下的 root 用户目录,请不要因为名字一样而混淆。
进入 chroot 环境也就是将$LFS 目录作为根目录,使用 3.2 和 3.3 在$LFS 目录中构建的环境完成之后的构建指令。
本章展示如何构建临时系统最后缺失的部分:首先,安装一些软件包的构建机制所必须的工具,然后安装三个用于运行测试的软件包。这样,就解决了所有的循环依赖问题,我们可以使用“chroot”环境进行构建,它与宿主系统除正在运行的内核外完全隔离。
为了隔离环境的正常工作,必须它与正在运行的内核之间建立一些通信机制。我们通过所谓的虚拟内核文件系统达成这一目的,它们必须在进入 chroot 环境时挂载。您可能希望用 findmnt 命令检查它们是否挂载好。
从现在开始,直到第 7.4 节 “进入 Chroot 环境”,所有命令必须以 root 用户身份执行,且 LFS 变量必须正确设定。在进入 chroot 之后,仍然以 root 身份执行所有命令,但幸运的是此时无法访问您构建 LFS 的计算机的宿主系统。不过仍然要小心,因为错误的命令很容易摧毁整个 LFS 系统。
3.4.1 改变$LFS 目录的所有者
在 2.3 最后的准备工作中,我们创建了 lfs 用户并将其变更为$LFS 目录的所有者。这样做的目的是安全,通过降低执行指令用户的权限来规避操作失误给宿主系统带来严重的后果。
创建 lfs 用户并变更目录归属,是为了更安全得在宿主机中编译软件包,但之后的编译工作将转换到 chroot 环境中进行,所以 lfs 用户已经完成了它的使命。同时,由于 lfs 这个用户仅仅存在于宿主系统中,如果不改变$LFS 目录的所属,那所有当前归属于 lfs 的目录和文件最后都会归属于当前 lfs 用户的 ID。这会带来潜在的风险,如果在新建普通用户的时候恰巧分配了这个 ID,那该用户将顺理成章得成为这些目录和文件的拥有者。普通用户拥有对于这些目录和文件的完全访问权是非常不安全。
为了避免这样的风险,我们需要为$LFS 目录下的文件夹再次变更归属。
chown -R root:root $LFS/{usr,lib,var,etc,bin,sbin,tools}
case $(uname -m) in
x86_64) chown -R root:root $LFS/lib64 ;;
esac
3.4.2 准备虚拟内核文件系统
内核对外提供了一些文件系统,以便自己和用户空间进行通信。它们是虚拟文件系统,并不占用磁盘空间,其内容保留在内存中。 首先创建这些文件系统的挂载点:
mkdir -pv $LFS/{dev,proc,sys,run}
在内核引导系统时,它需要一些设备节点,特别是 console 和 null 两个设备。它们需要创建在硬盘上,这样在内核填充/dev 前,或者 Linux 使用 init=/bin/bash 内核选项启动时,也能使用它们。运行以下命令创建它们:
mknod -m 600 $LFS/dev/console c 5 1
mknod -m 666 $LFS/dev/null c 1 3
用设备文件填充/dev 目录的推荐方法是挂载一个虚拟文件系统 (例如 tmpfs) 到 /dev,然后在设备被发现或访问时动态地创建设备文件。这个工作通常由 Udev 在系统引导时完成。然而,我们的新系统还没有 Udev,也没有被引导过,因此必须手工挂载和填充/dev。这可以通过绑定挂载宿主系统的/dev 目录就实现。绑定挂载是一种特殊挂载类型,它允许在另外的位置创建某个目录或挂载点的映像。运行以下命令进行绑定挂载:
mount -v --bind /dev $LFS/dev
现在挂载其余的虚拟内核文件系统:
mount -v --bind /dev/pts $LFS/dev/pts
mount -vt proc proc $LFS/proc
mount -vt sysfs sysfs $LFS/sys
mount -vt tmpfs tmpfs $LFS/run
在某些宿主系统上,/dev/shm 是一个指向/run/shm 的符号链接。我们已经在/run 下挂载了 tmpfs 文件系统,因此在这里只需要创建一个目录。
if [ -h $LFS/dev/shm ]; then
mkdir -pv $LFS/$(readlink $LFS/dev/shm)
fi
3.4.3 进入 chroot 环境
需要特别注意,整个构建 LFS 的过程中并没有考虑过中途中断的情况,日常构建中产生中断的可能性也不小。特别是构建 LFS 的时长会因为机器的配置和熟练程度有所差异,首次构建时很容易超过一天,甚至可能需要一周左右。所以在关机重启,或者有意无意的关闭终端后,重启构建作业前需要先确认当前的构建环境。如果终端点是进入 chroot 环境之后的构建工作,即 3.4 之后的内容,你可能需要执行 3.4.2 中的挂载 mount 指令和 3.4.3 中的 chroot 指令。
运行以下命令进入 chroot 环境:
chroot "$LFS" /usr/bin/env -i \
HOME=/root \
TERM="$TERM" \
PS1='(lfs chroot) \u:\w\$ ' \
PATH=/bin:/usr/bin:/sbin:/usr/sbin \
/bin/bash --login +h
| 参数 | 含义 |
|---|---|
/usr/bin/env -i | 清除 chroot 环境中的所有环境变量 |
HOME=/root TERM="$TERM" PS1='(lfs chroot) \u:\w\$ ' | 设置环境变量 HOME,TERM,PS1,以及 PATH。TERM="$TERM"是指将 chroot 环境中的 TERM 变量设为 chroot 环境外的值。 |
PATH=/bin:/usr/bin:/sbin:/usr/sbin | /tools/bin 不在 PATH 中。这意味着交叉工具链在 chroot 环境中不被再使用。 |
/bin/bash --login +h | 这里注意 +h 关闭散列功能 |
现在开始,就不再需要使用 LFS 环境变量,因为所有工作都被局限在 LFS 文件系统内。这是由于 bash 被告知$LFS 现在是根目录 (/)。
bash 的提示符会包含 I have no name!。这是正常的,因为现在还没有创建/etc/passwd 文件,我们将在 3.4.5 创建必要的文件和符号链接中解决这个问题。
3.4.4 创建目录
在第一章介绍过 FHS,为了 LFS 系统的 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/}{bin,include,lib,sbin,src}
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 的主目录,另一个是包含临时文件的目录。
第一个修改能保证不是所有人都能进入/root 一般用户也可以为他/她的主目录设置同样的 0750 权限码。第二个修改保证任何用户都可写入/tmp 和/var/tmp 目录,但不能从中删除其他用户的文件,因为所谓的 “粘滞位” (sticky bit),即八进制权限码 1777 的最高位 (1) 阻止这样做。
3.4.5 创建必要的文件和符号链接
Linux 的正常运行不仅仅依靠安装的软件包,还有赖于一系列的配置文件来管理。本章需要创建一系列的配置文件以保持 chroot 环境的正常运行。
1. 创建/etc/mtab
etc/mtab 用于维护已经挂载的文件系统的列表。现代内核在内部维护该列表,并通过/proc 文件系统将它展示给用户。但不是所有的工具都已经适配/proc/mounts 了,为了满足那些需要/etc/mtab 的工具,执行以下命令并创建符号链接:
ln -sv /proc/self/mounts /etc/mtab
2. 创建/etc/hosts
创建一个简单的/etc/hosts 文件,之后的一些测试套件,以及 Perl 的一个配置文件将会用到。
echo "127.0.0.1 localhost $(hostname)" > /etc/hosts
3. 创建/etc/passwd、/etc/group
为了使得 root 能正常登录,而且用户名 “root” 能被正常识别,必须在文件 /etc/passwd 和 /etc/groups 中写入相应条目。 执行以下命令创建/etc/passwd 文件:
cat > /etc/passwd << "EOF"
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/dev/null:/bin/false
daemon:x:6:6:Daemon User:/dev/null:/bin/false
messagebus:x:18:18:D-Bus Message Daemon User:/var/run/dbus:/bin/false
nobody:x:99:99:Unprivileged User:/dev/null:/bin/false
EOF
root 用户的实际密码,目前不需要设置。
执行以下命令,创建/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:
usb:x:14:
cdrom:x:15:
adm:x:16:
messagebus:x:18:
input:x:24:
mail:x:34:
kvm:x:61:
wheel:x:97:
nogroup:x:99:
users:x:999:
EOF
这里创建的用户组并不属于任何标准,它们一部分是为了满足第 5 章中 udev 配置的需要,另一部分借鉴了一些 Linux 发行版的通用惯例。另外,某些测试套件需要特定的用户或组。Linux Standard Base (LSB,可以在 http://refspecs.linuxfoundation.org/lsb.shtml 查看) 标准只推荐以组 ID 0 创建用户组 root,以及以组 ID 1 创建用户组 bin,其他组名和组 ID 由系统管理员自由分配,因为程序一般使用的不会组 ID 数字,而是使用组名。
第 4 章中的一些测试需要使用一个普通用户。我们先创建一个临时用户,在那一章的末尾再删除该用户。
echo "tester:x:$(ls -n $(tty) | cut -d" " -f3):101::/home/tester:/bin/bash" >> /etc/passwd
echo "tester:x:101:" >> /etc/group
install -o tester -d /home/tester
目前 shell 的提示符还是 “I have no name!”吧?因为,之前 chroot 的时候还没有/etc/passwd 和/etc/group。打开一个新 shell 用户名和组名现在就可以正常解析了:
exec /bin/bash --login +h
特别注意这里使用的 +h 参数。告诉 bash 禁用内部的路径散列机制。该机制会记忆 bash 执行过程序的路径,再此调用的时候速度会很快,但并不是适合在构建的时候使用。我们希望安装新编译好的程序后能够马上使用,而非使用上次的宿主机的程序,所以在本章和下一章中使用的都是加 +h 参数的,chroot 的时候要注意。
4. 初始化日志文件
login、agetty 和 init 等程序使用一些日志文件,以记录登录系统的用户和登录时间等信息。然而,这些程序不会创建不存在的日志文件。初始化日志文件,并为它们设置合适的访问权限:
touch /var/log/{btmp,lastlog,faillog,wtmp}
chgrp -v utmp /var/log/lastlog
chmod -v 664 /var/log/lastlog
chmod -v 600 /var/log/btmp
- /var/log/wtmp 记录所有的登录和登出;
- /var/log/lastlog 记录每个用户最后登录的时间;
- /var/log/faillog 记录所有失败的登录尝试;
- /var/log/btmp 记录所有错误的登录尝试;
- /run/utmp 记录当前登录的用户,它由引导脚本动态创建。