# Linux From Scratch 12.1 安装笔记

安装指南

版本 12.1-systemd-中文翻译版 发布于 2024 年 3 月 1 日

相关链接

准备工作

准备宿主机

硬件

LFS 编辑建议使用有四个以上 CPU 核心和至少 8 GB 内存的硬件进行构建。不满足上述条件的老旧系统仍能完成构建,但构建软件包所需的时间可能大大超过本书给出的估计。

本文提到的目前使用的电脑有12核CPU和40G内存,简直够用。

软件

根据LFS官方,宿主机需要有如下软件,并且版本不能低于要求。

  • Bash-3.2
  • Binutils-2.13.1
  • Bison-2.7
  • Coreutils-8.1
  • Diffutils-2.8.1
  • Findutils-4.2.31
  • Gawk-4.0.1
  • GCC-5.2
  • Grep-2.5.1a
  • Gzip-1.3.12
  • Linux Kernel-4.19
  • 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-5.0
  • Xz-5.0.0

根据手册,对部分软件有一些要求

  • /bin/sh 必须是到 bash 的符号链接或硬连接
  • 比 2.42 更新的版本未经测试,不推荐使用
  • /usr/bin/yacc 必须是到 bison 的链接,或者是一个执行 bison 的小脚本
  • /usr/bin/awk 必须是到 gawk 的链接
  • GCC 需要包括 C++ 编译器 g++ (比 13.2.0 更新的版本未经测试,不推荐使用)。C 和 C++ 标准库 (包括头文件) 也必须可用,这样 C++ 编译器才能构建宿主环境的程序

对于Linux Kernel的要求,LFS官方如是说

内核版本的要求是为了符合我们在第 5 章和第 8 章中编译 glibc 时的设置,这样可以禁用为旧版内核设计的变通措施,使得编译得到的 glibc 运行稍快,且占用空间稍小。截至 2024 年 2 月,4.19 是内核开发者仍然提供支持的最老内核版本。一些比 4.19 更老的内核可能被第三方团队支持,但它们不被视为上游正式发布的内核版本;详见 https://kernel.org/category/releases.html。

如果宿主内核比 4.19 更早,您需要将内核升级到较新的版本。升级内核有两种方法,如果您的发行版供应商提供了 4.19 或更新的内核软件包,您可以直接安装它。如果供应商没有提供一个足够新的内核包,或者您不想安装它,您可以自己编译内核。编译内核和配置启动引导器 (假设宿主使用 GRUB) 的步骤在第 10 章中。

我们需要宿主系统支持 UNIX 98 伪终端 (PTY)。这项功能在所有使用 Linux 4.19 或更新内核的桌面或服务器发行版中应该已经启用。如果您要构建自定义的宿主内核,需要确认在内核配置中 CONFIG_UNIX98_PTYS 选项被设为 y。

根据软件需求和后面的安装过程来看,最好在linux环境下安装。在windows下,可以使用WSL进行安装。

本文就是在windows中使用WSL进行安装的,使用的发行版为Ubuntu 22.04.3 LTS (jammy)。

根据手册,需要如下脚本查看系统是否符合要求。

#!/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

使用bash运行这个脚本,根据输出调整一下软件和版本。

准备硬盘

LFS并不能通过Live方式安装,必须要一个宿主机将LFS一点点安装到硬盘上。这包括

  • 为LFS系统分配硬盘空间
  • 为LFS准备系统引导
  • 为LFS安装必要的软件等

本文将LFS安装到一块全新的虚拟硬盘上。

创建硬盘

首先,使用windows自带的磁盘管理工具创建一块VHD

磁盘管理

然后设置磁盘大小为20G,这里设置的是动态扩展磁盘,可以节省空间。

创建VHD

创建好后,磁盘会自动挂在windows下,处于未分配状态,可以在“磁盘管理”中找到。

在PowerShell下使用GET-CimInstance -query "SELECT * from Win32_DiskDrive"列出windows下的可用磁盘。

可用磁盘

然后使用wsl --mount \\.\PHYSICALDRIVE2wsl --mount \\.\PHYSICALDRIVE2 --bare将硬盘挂载到wsl中。根据MSDN,前者挂载硬盘,而后者挂载分区,实际使用时发现后者也可以挂载硬盘,暂时没有发现两者的区别。

然后,在WSL中就可以看到我们刚刚挂载的硬盘了。

格式化硬盘

在WSL中,使用fdisk或者cfdisk为挂载硬盘进行分区。

由于需要从这块硬盘启动,并且使用UEFI,所以本文先将硬盘设置为GPT分区,然后分为两个分区:
分区

第一个分区用于boot,第二个分区为系统分区。

分区完成后,使用mkfs进行格式化。

使用mkfs.vfat/dev/sdc1格式化为FAT32。

使用mkfs.ext4/dev/sdc2格式化为EXT4。

挂载硬盘

接下来将格式化好的硬盘挂载到文件夹上。

这之后的内容都需要root权限,可以先使用sudo -i进入管理员账户。、

使用mkdir /mnt/lfs创建LFS根目录。

然后,先使用mount /dev/sdc2 /mnt/lfs,将/dev/sdc2挂载到/mnt/lfs,也就是LFS根目录下。

然后在LFS根目录下创建efi目录,并使用mount /dev/sdc1 /mnt/lfs/efi将boot分区挂载到新系统的efi目录下。

此时,新系统的目录结构已经大致搭建好了。

接下来创建根目录下的其他目录。

由于以后可能常用/mnt/lfs这个目录,所以我们将其分配一个环境变量export LFS=/mnt/lfs。可以将这一句添加到~/.bashrc等文件中,以后可以方便地进入LFS根目录。

后文都将使用$LFS代表LFS系统根目录。

# 创建boot分区efi目录
mkdir -v efi/efi

# 创建linux系统常用目录
mkdir -v {etc,home,opt,tmp,usr,var}
mkdir -v usr/{bin,lib,sbin}

# 创建符号链接
for i in bin lib sbin; do
	ln -sv usr/$i $i
done

# 为64位系统创建/lib64
case $(uname -m) in
	x86_64) mkdir -v lib64 ;;
esac

然后就可以得到这样的目录

root dirs

对于需要swap分区的用户来说,创建和格式化swap分区可以分别使用fdisk和mkfs完成,与在其他linux机器上添加swap分区的步骤相同,LFS官方文档也有相应的提示。另外,因为本机内存足够,不需要swap分区,所以这部分就不赘述。

下载软件

首先创建$LFS/sources目录,用于存储需要的软件包。

下面为该目录添加写入权限和 sticky 标志。“Sticky” 标志使得即使有多个用户对该目录有写入权限,也只有文件所有者能够删除其中的文件。输入chmod -v a+wt $LFS/sources,启用写入权限和 sticky 标志。

下载文件有几种方式:

如果要使用 wget-list-systemd 作为 wget 命令的输入,以下载所有软件包和补丁,使用命令:

wget --input-file=wget-list-systemd --continue --directory-prefix=$LFS/sources

也可以从镜像文件站下载文件,对于版本12.1,官方提供https://www.linuxfromscratch.org/mirrors.html#files 中的网站作为镜像站。

对国内用户,需要上那个网才能较快地下载软件包,因此不推荐使用第一种方法。本文使用wsl,但wsl上上那个网存在一些问题,所以通过 https://www.linuxfromscratch.org/mirrors.html#files 中的 香港源 下载。下载香港源中的 lfs-packages-12.1.tar 文件后将其拷贝到$LFS下解压,将其中的文件移动至$LFS/sources/

根据手册

在第 6 章中,会使用交叉编译器编译程序 (更多细节可以在工具链技术说明一节找到)。这个交叉编译器会被安装到一个专用的目录中,从而将其和其他程序分离。

所以我们创建一个目录mkdir -pv $LFS/tools

添加LFS用户

根据LFS手册所说

在作为 root 用户登录时,一个微小的错误就可能损坏甚至摧毁整个系统。因此,我们建议在后续两章中,以非特权用户身份编译软件包。您或许可以使用自己的系统用户,但为了更容易地建立一个干净的工作环境,我们将创建一个名为 lfs 的新用户,以及它从属于的一个新组 (组名也是 lfs),并在安装过程中以 lfs 身份执行命令。

小心一点确实不会产生什么影响,但是我们是第一次安装LFS,所以尽量全部跟着教程走。

使用以下命令创建新用户:

groupadd lfs
useradd -s /bin/bash -g lfs -m -k /dev/null lfs

lfs用户设置密码

passwd 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

启动一个以 lfs 身份运行的 shell

su - lfs

配置环境

为了配置一个良好的工作环境,创建一个新的 .bash_profile:

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

从手册得知这样做的意义

在以 lfs 用户登录或从其他用户使用带 “-” 选项的 su 命令切换到 lfs 用户时,初始的 shell 是一个登录 shell。它读取宿主系统的 /etc/profile 文件 (可能包含一些设置和环境变量),然后读取 .bash_profile。我们在 .bash_profile 中使用 exec env -i.../bin/bash 命令,新建一个除了 HOME, TERM 以及 PS1 外没有任何环境变量的 shell 并替换当前 shell。这可以防止宿主环境中不需要和有潜在风险的环境变量进入构建环境。

新的 shell 实例是 非登录 shell,它不会读取和执行 /etc/profile 或者 .bash_profile 的内容,而是读取并执行 .bashrc 文件。现在我们创建一个 .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

后续构件中需要使用make,为了在构建第 5 章和第 6 章 中的软件包时使用所有可用的逻辑 CPU 核心,现在将 MAKEFLAGS 的设置写入 .bashrc 中:

cat >> ~/.bashrc << "EOF"
export MAKEFLAGS=-j$(nproc)
EOF

如果要限制使用的核心数,将$(nproc)更改为对应的数字即可。

开始构建

构建过程概述

对于本LFS系统来说

构建过程是基于交叉编译过程的。交叉编译通常被用于为一台与本机完全不同的计算机构建编译器及其工具链。这对于 LFS 并不严格必要,因为新系统运行的机器就是构建它时使用的。但是,交叉编译拥有一项重要优势:任何交叉编译产生的程序都不可能依赖于宿主环境。

首先我们定义讨论交叉编译时常用的术语。

  • build
    指构建程序时使用的机器。注意在某些其他章节,这台机器被称为“host”(宿主)。

  • host
    指将来会运行被构建的程序的机器。注意这里说的“host”与其他章节使用的“宿主”(host) 一词不同。

  • target
    只有编译器使用这个术语。编译器为这台机器产生代码。它可能和 build 与 host 都不同。

在 LFS 的构建过程中,为了将本机伪装成交叉编译目标机器,我们在 LFS_TGT 变量中,将宿主系统三元组的 "vendor" 域修改为 "lfs"。我们还会在构建交叉链接器和交叉编译器时使用 --with-sysroot 选项,指定查找所需的 host 系统文件的位置。这保证在第 6 章中的其他程序在构建时不会链接到宿主 (build) 系统的库。前两个阶段是必要的,第三个阶段可以用于测试:

阶段 Build Host Target 操作描述
1 pc pc lfs 在 pc 上使用 cc-pc 构建交叉编译器 cc1
2 pc lfs lfs 在 pc 上使用 cc1 构建 cc-lfs
3 lfs lfs lfs 在 lfs 上使用 cc-lfs 重新构建并测试它本身

在上表中,“在 pc 上” 意味着命令在已经安装好的发行版中执行。“在 lfs 上” 意味着命令在 chroot 环境中执行。

构建 LFS 交叉工具链和临时工具

在LFS手册上这一部分被分为三个阶段:

  • 构建一个交叉编译器和与之相关的库
  • 使用这个交叉工具链构建一些工具,并使用保证它们和宿主系统分离的构建方法
  • 进入 chroot 环境并构建剩余的,在构建最终的系统时必须的工具

编译的程序会被安装在 $LFS/tools 目录中,以将它们和后续编译安装的文件分开。但是,编译的库会被安装到它们的最终位置,因为这些库在我们最终要构建的系统中也存在。

接下来,跟着LFS官方手册进行配置编译安装即可。

进入Chroot并构建其他工具

一些需要的软件包已经构建完成,接下来需要进入隔离的环境,即使用chroot环境进行构建。

准备虚拟文件系统

为了隔离环境的正常工作,必须它与正在运行的内核之间建立一些通信机制。这些通信机制通过所谓的虚拟内核文件系统实现,我们将在进入 chroot 环境前挂载它们。

用户态程序使用内核创建的一些文件系统和内核通信。这些文件系统是虚拟的:它们并不占用磁盘空间。它们的内容保留在内存中。必须将它们被挂载到 $LFS 目录树中,这样 chroot 环境中的程序才能找到它们。

首先创建这些文件系统的挂载点:

mkdir -pv $LFS/{dev,proc,sys,run}

为了在任何宿主系统上都能填充 $LFS/dev,只能绑定挂载宿主系统的 /dev 目录。绑定挂载是一种特殊挂载类型,它允许通过不同的位置访问一个目录树或一个文件。

使用以下命令挂载虚拟内核文件系统:

mount --types proc /proc $LFS/proc
mount --rbind /sys $LFS/sys
mount --make-rslave $LFS/sys0
mount --rbind /dev $LFS/dev
mount --make-rslave $LFS/dev
mount --bind /run $LFS/run
mount --make-slave $LFS/run

在某些宿主系统上,/dev/shm 是一个符号链接,通常指向 /run/shm 目录。我们已经在 /run 下挂载了 tmpfs 文件系统,因此在这里只需要创建一个访问权限符合要求的目录。

在其他宿主系统上,/dev/shm 是一个 tmpfs 的挂载点。此时,绑定挂载 /dev 只会在 chroot 环境中生成 /dev/shm 目录。这样,我们必须显式挂载一个 tmpfs:

if [ -h $LFS/dev/shm ]; then
  install -v -d -m 1777 $LFS$(realpath /dev/shm)
else
  mount -vt tmpfs -o nosuid,nodev tmpfs $LFS/dev/shm
fi

进入Chroot

现在已经准备好了所有继续构建其余工具时必要的软件包,可以进入 chroot 环境并完成临时工具的安装。在安装最终的系统时,会继续使用该 chroot 环境。以 root 用户身份,运行以下命令以进入当前只包含临时工具的 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、关闭宿主机等,需要使用这两部分内容重新挂载虚拟文件系统和进入Chroot环境后继续构建。

接下来根据LFS手册构建相应工具即可。

构建LFS系统

安装软件

重新构建完LFS系统必须的软件后,我们就可以正式开始构建LFS系统了。

这一部分依然是枯燥的编译和安装软件包,只需要跟着官方文档走就可以了。

系统配置

网络配置

没有特殊情况的话,这部分只需要配置DHCP。

# /etc/systemd/network/10-eth-dhcp.network
[Match]
Name=<网络设备名>

[Network]
DHCP=ipv4

[DHCPv4]
UseDomains=true

/etc/resolv.conf可以直接使用systemd-resolved进行配置,当然也可以直接复制宿主机的文件。

然后,给机器取个名,写入/etc/hostname即可。引导过程中可能会用到这个文件。

然后再创建一下/etc/hosts文件,用于映射ip和hostname.

接着再配置一下时间和控制台即可。

不过我配置时间的时候发现/usr/share/zoneinfo/Asia/Shanghai文件不存在,所以没法设置为中国时区,将就使用一下UTC时间。

然后配置系统locale。由于中文无法在非图形界面显示,所以建议这里不要改为中文。

接着再根据手册配置systemd。这里我没有进行多余的配置,保持默认即可。

安装内核

这是最重要的一部分,关系到系统能否运行。

准备编译内核

make mrproper

该命令确保内核源代码树绝对干净,内核开发组建议在每次编译内核前运行该命令。尽管内核源代码树在解压后应该是干净的,但这并不完全可靠。

有多种配置内核选项的方法。例如,通常我们通过目录驱动的界面完成这一工作:

make menuconfig

接下来,一定要按照官方文档的要求配置内核。由于我需要编译一个支持UEFI启动的内核,所以我们还需要另一篇手册BLFS

这个手册只有英文,但我们也不需要看,找到选项配置好即可。

配置完成后,make一下。

然后使用make modules_install安装内核配置使用的模块。

指向内核映像的路径可能随机器平台的不同而变化。下面使用的文件名可以依照您的需要改变,但文件名的开头应该保持为 vmlinuz,以保证和下一节描述的引导过程自动设定相兼容。我们的机器是 x86_64 体系结构:

cp -iv arch/x86_64/boot/bzImage /boot/vmlinuz-6.7.4-lfs-12.1-systemd

如果内核架构有误,是无法启动的。

System.map 是内核符号文件,它将内核 API 的每个函数入口点和运行时数据结构映射到它们的地址。它被用于调查分析内核可能出现的问题。执行以下命令安装该文件:

cp -iv System.map /boot/System.map-6.7.4

内核配置文件 .config 由上述的 make menuconfig 步骤生成,包含编译好的内核的所有配置选项。最好能将它保留下来以供日后参考:

cp -iv .config /boot/config-6.7.4

安装 Linux 内核文档:

cp -r Documentation -T /usr/share/doc/linux-6.7.4

接着,我们配置一下内核模块加载顺序,跟着手册走即可。

安装引导

安装引导前,需要先配置/etc/fstab文件。如果有多个磁盘,使用blkid找到磁盘的UUID并写入文件即可,很简单。

我在安装引导时,遇到了一些问题。

由于我打算使用UEFI启动,所以需要使用grub-install --target=x86_64-efi进行安装。但是在wsl上,使用该命令会出现‘efibootmgr找不到文件或文件夹’的错误。这使我一度放弃安装LFS。

后来尝试安装Gentoo,在virtual box中安装时,发现手动递归挂载了/sys,查看后发现该目录下存在efivars目录。后来突然想到,是因为没有/sys/firmware/efi/efivars目录,导致grub-install调用eifbootmgr出现错误,所以无法安装efi版本的grub引导。

于是,接下来我将装有该系统的VHD虚拟磁盘加载到virtual box中,由于我还没有安装其他虚拟机,于是先用Gentoo的liveCD凑合一下,毕竟只需要一个有/sys/firmware/efi/efivars目录的宿主机即可。

接下来就是使用grub-install --target=x86_64-efi --efi-directory=/efi安装grub引导了。

--efi-directory选项设置了grub安装的位置,在该目录下也会寻找grub/grub.cfg文件,并根据其中的配置进行安装。

grub.cfg的默认配置中,vmlinuz-xxx文件在主分区的/boot目录下,这里建议将boot分区/dev/sdx1挂载到/efi下,而/boot中保存vmlinuz-xxx等文件。

# /boot/grub/grub.cfg
set default=0
set timeout=5

insmod part_gpt
insmod ext2
set root=(hd0,2)

menuentry "GNU/Linux, Linux 6.7.4-lfs-12.1-systemd" {
        linux   /boot/vmlinuz-6.7.4-lfs-12.1-systemd root=/dev/sda2 ro
}

如果使用了单独的boot分区,并将vmlinuz-xxx等文件保存在其中,则需要从上面的 linux 行删除/boot,然后修改set root行,指向boot分区,这里一般是(hd0,1)。

有多系统的情况下增加 menuentry 即可。

一切顺利的话,就可以重启,然后尝试引导启动我们自己的LFS了。

posted @ 2024-04-18 13:15  Nomaldisk  阅读(9)  评论(0编辑  收藏  举报