介绍
本章将讲述 Linux 系统从 UNIX 开始,经历 GNU 工程的发展,直至 Linux 内核的出现让系统成型的始末。介绍 Linux 的一种特殊的发行版 Linux From Scratch ,并说明自制 Linux 的目的、过程和最终呈现的效果。
Linux 系统的始末
笔者每每跟随最新版的 Linux From Scratch 走构建流程的时候就会想起下面的三个场景:R 站在旁边看着 K 坐在 PDP-7 前工作,RMS 用着 HHKB 敲代码,以及 Linus 日常竖起的中指。前者是初识计算机时的经典老照片,中间是学习 Linux 到疯魔时候的力量源泉,后者则是同学同事之间日常提及的名场面。无疑是这些人为 Linux 系统如今的发展奠定了基础,非常巧合的是,他们可以串联起概括 Linux 系统发展过程中最重要的三个阶段。
1.1.1 梦的缘起 —— UNIX
说起 Linux 那就不由让人想起 UNIX,作为 Linux 系统的起源,可以说是这个梦开始的地方。正如读者所想,UNIX 的开发者便是自制了一套系统。 1969 年,AT&T 旗下著名的贝尔实验室迫于开发速度过慢退出了 Multics 的开发计划。Multics 是一个 1964 年开始的项目,早期由通用电气和麻省理工学院合作计划,打算打造出一套多用户、多任务、多层次的 Multics 操作系统。贝尔实验室于 1965 年加入该项目,经过了 4 年的努力,这个糅合了太多的特性的 Multics 虽然发布了一些产品,但是性能都很低,AT&T 最终选择撤出了投入 Multics 项目的资源,退出这项合作计划。
虽然 Multics 计划已然行将就木,但被用于开发 Multics 项目的大型主机 GE-645 仍然保留在贝尔实验室。这可能就是命运,此时一个刚刚被招募加入该计划的年轻人在 GE-645 上,写出了一个仿真器,可以让文件系统与内存分页机制运作起来。他同时也写了一个程序语言 Bon,并用其编写了一个太空旅行游戏。在 GE-645 被搬走后,他在实验室中寻找没人使用的机器,并在长他两岁的同事丹尼斯·里奇 1 的帮助下,成功的在 PDP-7 上用汇编语言重写了这个游戏。他进入 Multics 计划不久,计划就中止了,但为数不多的项目经验促使着他在 PDP-7 上继续研究如何开发系统。熟悉这段历史的人应该知道,这位年轻人就是大名鼎鼎的肯·汤普逊 2。

在肯·汤普逊和丹尼斯·里奇的主导下,加上近乎 Multics 计划的原班人马的不懈努力,UNIX 先后经历三个版本,终于在 1973 年用 C 语言重写的第三版中稳定下来。用 C 语言编写的 UNIX 代码简洁紧凑、易移植、易读、易修改,为此后 UNIX 的发展奠定了坚实基础。
1.1.2 梦的延续 —— GNU 工程
理查德·马修·斯托曼(Richard Matthew Stallman),同时也是自由软件运动的推动者,唤吾大 RMS,江湖上无人不知无人不晓,就是好名声多还是坏名声多就不知道了。不过有一说一 GNU 工程对于 Linux 系统的影响是不能被磨灭的,整本书大半构建的都是 GNU 工程的产物,但凡看到 g 开头的软件包,脑海里都是 RMS。

1971 年,彼时的 RMS 刚刚在 MIT 开始他的职业生涯。那个时代是甜蜜的时代,当时的计算机公司都经常发布自由软件,程序员们可以自由地相互合作。上一个故事的 UNIX 也属于这个时代,并在这样的环境中茁壮成长。但随着时间来到 20 世纪 80 年代,放眼望去,几乎所有的软件都是专属软件了。专属软件的出现意味着软件有了拥有者,不再共享源码甚至随意使用,它们是属于个人或者组织的私有财产。这种商业形式摧毁了之前十余年的合作模式,这导致给予他人帮助变得越来越难,社区也开始日益萎缩。
当时摆在 RMS 面前的选择有很多,以他的才华随便找一家专属软件的公司或者自立门户做软件生意都是不错的选择,值得庆幸的是他选择了坚守自己的本心。为了重建社区,首当其冲的就是他和他的社区需要一个操作系统,一个自由的操作系统。为了致敬 UNIX,他将着手的系统命名为 GNU,意为 GNU's Not UNIX。而 GNU 工程也就是开发 GNU 系统的工程。
GNU 操作系统的开发进度是很快的,从 1984 年 RMS 离开 MIT 算起,直到 1990 年 GNU 系统除了内核,其他的部分基本上都完成了。当时决定采用的内核是 Hurd,但最后的结果显而易见,如果成功了显然本书的标题就要改了。
GNU 工程的努力和成果是值得铭记的,我们构建的过程中会逐渐理解到 GNU 工程的伟大,硕果累累的 GNU 工程留下来大量自由软件,这些软件是 Linux 的基石,也是我们构建过程的重点。每个在构建过程中涉及到的自由软件,或多或少会有介绍。你也可以访问 GNU 的官网 https://www.gnu.org/home.zh-cn.html 获取到相关的信息。
1.1.3 注入灵魂 —— Linux 内核
1991 年,Linus Torvalds 开发了一个和 UNIX 兼容的内核,并称之为 Linux。一开始 Linux 是专有软件,但是在 1992 年,Linus 将 Linux 变成了自由软件。把 Linux 和还不完整的 GNU 系统结合在一起就形成了一个完整的自由软件操作系统。
关于这个完整的自由软件操作系统的称呼其实还有 GNU/Linux 这种叫法。GNU 工程为了这个系统努力多年,最后因为使用了 Linux 内核而把自己的名字也丢了,这点确实让人难以接受,所以他们提出用 GNU/Linux 这一提议也是情理之中。有很多人表示支持,GNU 也确实配的上。不过事与愿违,不仅有不少的一部分认为系统的关键是内核,并不认同这一提议,而且大众更喜欢使用 Linux 操作系统,因为这样更顺口。GNU 工程的贡献是值得铭记的,不过本书采用的还是“Linux 系统”这种容易理解的称呼,或者省去系统使“用 Linux”来指代“Linux 系统”或 GNU/Linux,若单指内核会采用“Linux 内核”加以区分。
Linux 内核之所以称之为内核,它所要完成的任务是实现多任务处理、虚拟内存、共享库、请求分页、共享写时复制可执行体、内存管理以及线程等操作系统具体的核心功能。从实现的功能就可以看得出,一个内核对于系统的重要性,就相当于是在在凹凸不平的路面上铺上一层平坦的水泥,这样程序才能在你的计算机里有序的运行。所以 Linux 内核之于 Linux 系统,便如同注入灵魂。决定了系统的性格,也决定了系统的内在基因。
Linux 内核对于 Linux 系统来说固然重要的,但是要做到让 Linux 系统持续保持流行,仅仅是这一点显然不够。Linux 是一个宏内核架构,与之相对的是微内核架构。使用此架构的内核是一个单一的二进制可执行文件,在内核态以监管者模式运行。其优点是内核之中的通信成本很小,内核可以直接调用内核态内的函数,因此性能很好。20 世纪 80 年代前的所有操作系统都采用这种方式实现,也包括 UNIX。但是也有一个很大的缺点,就是移植性不好。
这一点 Linus 自己也承认,他的初衷也并不是使 Linux 成为一个可移植的操作系统。不过有趣的是,事与愿违,Linux 是世界上被移植的最广泛的操作系统内核。服务器、手机、游戏机,即使是被 Windows 制霸的桌面端,相信本书的读者中也有很大一部分用的是 Linux 的某个发行版。
Linux 内核之所以能在如此漫长的岁月中稳步发展,有很大一部分原因是它的开发模式。这种人人都有机会贡献出自己能量的开发模式,吸引到了众多的开发者加入到完善 Linux 内核的过程中。在书写本小节的同时我又重看了一边 Linus 在 2017 年北京的 LC3 的采访。当时笔者也在会场,他说的有些话至今记忆犹新,其中有一些特别适合放在这,这里摘抄两段:
“我觉得有趣的是我认为已经稳定的代码仍然在不断的得到改进,有些东西我们已经很多年没有碰了,然后有人来改进了它们,或者在我以为根本就不会有人用的东西上提交了 Bug 报告。我们有了新的硬件,开发了新的功能,但是 25 年后,我们仍然有老的、非常基础的东西,并且人们依然在关心和改善着它们。”
“当我坐下来开始写 Git,一个首要的原则就是你应该能 fork 并且在此基础上做你自己的事情。如果你有友好的 fork(能证明我错了,并且能够改进内核),在这种情况下,人们可以回来说我们实际上改进了内核,这没有什么不好的感觉。我会采纳你的改进并且将其合并进来。这就是为什么你应该鼓励 fork。你也想让良好的回馈变得很简单。”
采访的视频链接:https://linux.cn/article-8638-1.html
从 Linus 的话中,我们可以看出他对于开发维护 Linux 内核的态度。真的十分感谢 Linus、内核的开发维护人员以及为 Linux 内核提交过贡献的人们。没有他们,Linux 不可能发展的如此迅速,也不可能在特性稳定之后仍能持续完善。

1.1.4 Linux 系统的发行版
说起 Linux 系统的发行版,那可真的是百花齐放,自由软件和开源软件的特性就传播门槛低,Linux 又是其中的明星项目,分支不可谓不多,由于原图过大,书中图片 3 截取的是分支中比较小众的一部分作为展示。

Linux 系统的发行版是通常指的是由一些组织、团体、公司或者个人制作并发行的。Linux 内核主要作为发行版系统的一部分而使用。 根据软件包管理方式进行分类的话,流行的发行版有:
- 基于 RPM(Red Hat 系)的 RHEL(Red Hat Enterprise Linux)、Fedora、CentOS 和 OpenSUSE 等;
- 基于 Dpkg(Debian 系)的 Debian、Ubuntu 和 Linux Mint 等;
- 不同于以上两类的 ArchLinux、Gentoo 和 Slackware 等。
通常来讲,一个 Linux 发行版包括以下部分。
- Linux 内核;
- 将整个软件安装到计算机上的一套安装工具;
- 各种 GNU 软件以及其他的一些自由软件和开源软件;
- 在一些 Linux 发行版中可能会包含一些专有软件。
自此为止,对于如何自制 Linux 系统这个问题,已经解答了一半了。那便是要构建出如同 Linux 系统的发行版一样,包含以上要件的 Linux 系统。
全名丹尼斯·麦卡利斯泰尔·里奇,英文名为 Dennis MacAlistair Ritchie。
全名肯尼斯·蓝·汤普逊,英文名为 Kenneth Lane Thompson。
源自 https://upload.wikimedia.org/wikipedia/commons/1/1b/Linux_Distribution_Timeline.svg。
Linux From Scratch 项目
在 Linux 的众多发行版中有一股清流,与当前快速无脑搭建系统环境相反,什么软件包都要构建者自行下载编译,设置开始构建前自己还需要有一台 Linux 作为宿主的 Linux From Scratch 项目。
Linux From Scratch(简称 LFS,下同)项目是由 Gerard Beekman 等人发起,通过撰写手册方式发布发行版系统构建流程的独特项目,目前手册由 Bruce Dubbs 维护,保持每半年更新一版的速度。
LFS 是所有发行版中比较特殊的发行版,当然是否属于 Linux 的发行版这件事也存疑。笔者确实在 distrowatch 上找到了关于 LFS 的发行版详细信息,但结合 LFS 项目首页的说明、以及本身的发布、构建方式来看,笔者更倾向于将其定位为一本分步构建 Linux 系统的手册,以及维护手册的项目。
自制 Linux 显然完全没有必要从每个组成部件的代码入手自制,那样太没有效率,顶级的程序员和无数的开发者换来的功能,非一人一力可以匹敌,也不是一本书写得完的。所以 LFS 的构建流程,以软件包为单位,从头至尾如同搭积木一样 DIY 一个 Linux 系统出来。
1.2.1 遵循的标准
首先,明确构建 LFS 的目标,完善 1.1.4 中提到的 Linux 系统所需要包含的要件,并使其基本遵循 POSIX.1-2008.、FHS 3.0 和 LSB 5.0 这三个标准。
需要声明的是 LFS 并没有完全遵守标准,其他的 Linux 发行版或多或少也存在不遵守的地方。而且标准随着行业发展会逐渐进化,并不是一成不变的。由于 LFS 的安装方式是纯手动编译的方式,在构建过一次两次 LFS 后,读者完全可以放飞自己,无需拘泥于所谓的标准。遵守标准的好处是系统的通用性和扩展性会更好,无论是构建者本人或是其他使用者,在通晓 Linux 常用知识后能简单上手使用;需要扩展安装其他软件包时出现的问题的概率会更小。
1. 可移植操作系统接口(POSIX)
POSIX(Portable Operating System Interface,可移植操作系统接口)这个名称是由 RMS 应 IEEE 的要求而提议的一个易于记忆的名称。它基本上是首字母缩写,而 X 则表明其对 UNIX API 的传承。Linux 基本上逐步实现了 POSIX 兼容,但并没有参加正式的 POSIX 认证。同时,Linux 的发展也影响到了 POSIX 后续的发展。
由于是直接构建软件包,不必直面标准的内容,就不多做介绍了。非常感谢 Austin Group 一直以来对于标准的维护,让他一直在迭代。如果对此感兴趣或是想要深入了解的话可以移步 Austin Group 的主页(https://www.opengroup.org/austin/),或者 POSIX.1-2017 的主页(https://pubs.opengroup.org/onlinepubs/9699919799/)。如果对 2008 版更有兴趣的话,也可以在 2017 的主页链接中找到以往的版本。
2. 文件系统层次结构标准(FHS)
FHS(Filesystem Hierarchy Standard,文件系统层次结构标准)定义了 Linux 操作系统中的主要目录及目录内容。FHS 由 Linux 基金会维护。 当前版本为 3.0 版,于 2015 年发布。多数 Linux 发行版遵从 FHS 标准并且声明其自身政策以维护 FHS 的要求。
这个标准用于规范 Linux 下各个目录的命名和功能。比如:/bin 是用于存放二进制文的位置,/home 是用于存放每个用户个人目录的位置。在构建的过程中会专门创建这些目录和文件以满足该标准。
3. Linux 标准规范(LSB)
LSB(Linux Standard Base,Linux 标准库)是由 Linux 基金会组织结构下的几个 Linux 发行版组成的联合项目,用于标准化软件系统结构,包括 Linux 内核中使用的文件系统层次结构标准。LSB 基于 POSIX 规范,单 UNIX 规范(SUS)和其他几个开放标准,但在某些领域进行了扩展。
LSB 的目标是开发和推广一系列开放标准,这些标准将提高 Linux 发行版之间的兼容性,并使软件应用程序能够在任何兼容系统上运行。LSB 合规性可通过认证程序对产品进行认证。
LSB 有 4 个独立的标准:核心(Core)、桌面(Desktop)、运行时语言(Runtime Languages)和成像(Imaging)。除了通用要求,还有架构特定要求。此外还有两个领域在试行:分别是 Gtk3 和图形(Graphics)。仅构建 LFS 并不能创建一个完全通过 LSB 认证测试的完整系统,需要很多 LFS 范围之外的额外软件包。在 BLFS 中有这些额外软件包的安装说明。
由 LFS 提供,用于满足 LSB 要求的软件包:
- LSB 核心:bash, bc, binutils, coreutils, diffutils, file, findutils, gawk, grep, gzip, m4, man-db, ncurses, procps, psmisc, sed, shadow, tar, util-linux, zlib
- LSB 桌面:无
- LSB 运行时语言:PERL
- LSB 成像:无
- LSB GTK3 和 LSB 图形(试用):无
由 BLFS 提供,用于满足 LSB 要求的软件包:
- LSB 核心:at, Batch (at 的一部分), cpio, ed, fcrontab, initd-tools, lsb_release, NSPR, NSS, PAM, pax, Sendmail (或 Postfix 或 Exim), time
- LSB 桌面:ALSA, ATK, cairo, desktop-file-utils, freetype, fontconfig, gdk-pixbuf, glib2, GTK+2, Icon-naming-utils, libjpeg-turbo, libpng, libtiff, libxml2, MesaLib, Pango, xdg-utils, Xorg
- LSB 运行时语言:python, libxml2, libxslt
- LSB 成像:CUPS, cups-filters, ghostscript, SANE
- LSB GTK3 和 LSB 图形(试用):GTK+3
LFS 和 BLFS 没有提供,用于满足 LSB 要求的软件包:
- LSB 核心:无
- LSB 桌面:Qt4(以及 Qt5 除外)
- LSB 运行时语言:无
- LSB 成像:无
- LSB GTK3 和 LSB 图形(试行):无
构建流程和分章结构
在内容方面,本书在 LFS 的基础上进行编排,重新划分了章节,并确立了每个章节需要完成的任务。
第 2 章宿主机和准备工作中,旨在解决构建 LFS 时的基本要求,完善一个符合要求的 Linux 系统作为宿主系统。对于还未安装 Linux 系统的读作给出了 Windows 虚拟机安装宿主 Linux 系统的步骤。
第 3 章构建交叉编译工具链及临时工具,旨在解决 LFS 系统和宿主系统的独立性问题。LFS 系统虽然脱胎于宿主系统,但却不能直接生长在宿主系统上。就如同人类生育一样,母亲和胎儿之间需要一根脐带维系着胎儿和母亲的关联,脐带深深扎根在母亲的子宫内,在保证营养输送的同时保证胎儿与母亲的相对独立。第 3 章的内容就类似于母亲和孩子之间的脐带,是保持两个系统独立的必要步骤。
第 4 章安装基础系统软件,在这个阶段,GNU 软件和应用软件将尽数完成构建,1.1.4 中提到的要件将只剩下 Linux 内核。仿佛一个装修完的房子等待主人入住。

第 5 章编译内核并配置系统,如果将第 4 章最后的产物比喻成建造毛坯房,那么第 5 章需要做的就是让内核住进去,然后再置办些小物件,1.1.3 中提到 Linux 内核之于 Linux 系统仿佛是在注入灵魂。而编译内核并完成一系列配置后这个 LFS 系统便可以自行独立运行了。
在重启正常进入 LFS 系统后,整个构建流程便结束。第 6 章会对整体做个总结并提及一些扩展知识。
注意事项
1.LFS 版本说明
本书采用 LFS10.0 手册的构建流程,纸质书的出版可能无法追上 LFS 版本的半年更新,实际构建的时候可以采用最新版本的 LFS 构建流程。若希望采用本书流程构建,请保持软件包版本与手册一致,不然可能会产生难以预料的问题,自行解决花费的时间成本很高。
2.常见问题的检索和避免
本书整体参考 LFS 的构建顺序,对构建指令也并未做出改动。如果在构建的途中出现一些问题,可以善用搜索引擎快速解决这些问题。一般常见的问题也会在 LFS 的后续的版本中解决和特别说明。 在实际构建中也可以打开 LFS 电子版直接复制其中的指令,以避免输入错误产生的问题。
3.LFS 的目标架构
LFS 当前主要支持 AMD/Intel 的 x86(32 位)和 x86_64(64 位)构架的 CPU。另外,也有一些办法,可以帮助 LFS 顺利地在 Power PC 和 ARM CPU 上运行。 跟随流程构建,只会构建 32 位或 64 位系统中的其中一种,64 位系统并不会兼容 32 位程序。如果有构建兼容 32 位程序的 64 位系统的需求,很多程序需要自行编译两次。
4.SysVinit 和 systemd 的区别
systemd 是 Linux 计算机操作系统之下的一套中央化系统及设置管理程序(init),包括有守护进程、程序库以及应用软件,由 Lennart Poettering 带头开发。其开发目标是提供更优秀的框架以表示系统服务间的依赖关系,并依此实现系统初始化时服务的并行启动,同时达到降低 Shell 的系统开销的效果,最终代替现在常用的 System V 与 BSD 风格 init 程序。
systemd 这一名字源于 UNIX 中的一个惯例:在 UNIX 中常以“d”作为系统守护进程(daemon,亦称后台进程)的后缀标识。除此以外,systemd 亦是借代英文术语 D 体系,而这一术语即是用于描述一个人具有快速地适应环境并解决困难的能力。
当前绝大多数的 Linux 发行版都已采用 systemd 代替原来的 System V。LFS 也不例外,提供了两个版本的构建流程 sysv 对应 System V,systemd 对应 systemd。经过几个版本的演变,这两个版本已经很像了,特别是构建过程,只有临近结束的几个软件包的构建顺序有些差异,其他软件包的构建几乎不受影响。不过在配置的时候差距还是很大的,所以 System V 和 systemd 之间的比较,会留到配置阶段结合实践说明。
下面开始构建工作,其中所有的命令以及参数,笔者会在初次遇到的时候详细的介绍一番,在第二次出现的时候就不再作过多解释了。如果你对这方面知识不是特别了解,建议慢慢看下去,如果后面有什么不理解的话就往前面翻翻。如果你觉得这部分很简单,你也可以跳过,直接运行指令即可,指令已经写的非常完善了,在途中不中断的情况下,直接运行指令即可完成构建工作。不过有一点需要注意,本书为了兼顾 LFS 的 System V 和 systemd 两个版本,两个版本本身是不能共存的,所以在某些软件包的构建和个别软件包的构建顺序上会存在些许差异,这些地方需要读者自己考虑,自己抉择。
宿主系统和准备工作
在构建 LFS 系统前,还需要一些准备工作。首先需要确保磁盘上有足够的空间来容纳 LFS 系统,其次需要一个 Linux 系统作为宿主系统帮助完成一部分前期的构建任务,当然这个 Linux 本身也需要满足一定的要求来保证构建能够正常实施。当然即使没有可用的 Linux 系统也没关系,在实际构建时会简单介绍如何在 Windows 上使用虚拟机构建宿主系统。除此之外,在构建 LFS 系统时需要编译的软件包、补丁,以及用于测试及相关依赖的软件包也需要在本章内完成下载并放置到指定位置。最后,保险起见,为了防止因第三章的操作失误导致宿主系统发生问题,需要创建一个临时用户并未其设置环境。
准备宿主系统
准备宿主系统是为了让一个已经存在的 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>
请将
软件包和补丁
本章列出了一张软件包的清单,你需要下载它们来构建一个基础的 Linux 系统。列出的软件版本号便是该软件经过确认可以正常工作的版本,也是成书时笔者使用的版本。强烈建议不要使用更新的版本,因为某个版本的编译指令并不一定适用于更新版本。最新的软件包会包含许多问题,需要特别对应。
笔者无法保证下载的地址是一直有效的。如果在本书发布后下载地址变了,大部分软件包可以用 Google(http://www.google.com/)解决。如果连搜索也失败了,那不妨试一试 http://www.linuxfromscratch.org/lfs/packages.html#packages 中提到的其他下载地址。
下载好的软件包和补丁最好保存在整个构建过程中都能便捷访问的地方。另外需要一个工作目录来解压并构建源码。$LFS/sources 一个不错的选择。可以同时存放软件包和补丁,并担任工作目录的角色。在 LFS 分区中创建这个目录,并将所有构建中需要的元素存储于该目录中,以保证这些元素在整个构建过程中都可以访问。
从本节开始,随后的命令皆须以 root 用户完成。在构建过程中中断,并重新登录后,需要检查当前用户是否为 root,环境变量$LFS 是否正确设置。
在开始下载任务之前,先用 root 用户执行下面的命令,创建这个目录:
mkdir -v $LFS/sources
设置目录的写权限和粘滞模式。“粘滞模式”是指即便多个用户对粘滞目录有写权限,但仅文件的所有者,拥有在粘滞目录中删除该文件的权限。运行以下命令更改目录的写权限和粘滞模式:
chmod -v a+wt $LFS/sources
使用 wget-list 作为 wget 的输入可以批量下载所有软件包和补丁。 可以下载官方提供的 wget-list,不过使用该 wget-list 实测下载的速度很慢:
wget https://lctt.github.io/LFS-BOOK/lfs-sysv/wget-list
sysv 和 systemd 版本的的 wget-list 完全一致,不必纠结上述下载的是 sysv 中的 wget-list。 鉴于国内如果使用这份 wget-list 下载,时间会比较长,推荐下载中科大镜像源打包好的 tar 文件:
wget http://mirrors.ustc.edu.cn/lfs/lfs-packages/lfs-packages-10.0.tar
tar -xf lfs-packages-10.0.tar
mv 10.0/* sources/
解压后便包含 md5sum 文件,确认下载软件包和补丁的校验和:
pushd $LFS/sources
md5sum -c md5sums
popd
输出的全是 OK 就可以进行下一步了。如果有软件包没下载或者校验和不对,只需要重新下载有问题的软件包即可。
最后的准备工作
在构建 LFS 之前还有一些准备工作需要完成:为第三章构建的临时系统创建目录结构、添加 lfs 用户、设置环境。这些准备工作将在本节说明。
2.3.1 创建目录
我们需要创建在第三章构架临时环境时需要的所有目录。先创建符合 FHS 标准的这些目录,它们在构建临时环境后依然需要为第四章或者说最后的 LFS 系统服务。
以 root 用户运行以下的命令来创建需要的文件夹:
mkdir -pv $LFS/{bin,etc,lib,sbin,usr,var}
case $(uname -m) in
x86_64) mkdir -pv $LFS/lib64 ;;
esac
然后创建$LFS/tools 目录,该目录只是用于存放交叉编译的中间产物的目录,在完成构建后应该和宿主系统一样可以和 LFS 系统完全分离。
mkdir -pv $LFS/tools
2.3.2 添加 lfs 用户
使用 root 实施构建操作是存在风险的,犯一个小错误可能会破坏或摧毁整个系统,这里主要指宿主系统。因此,建议在第 3 章中以非特权用户编译软件包。使用原本的用户名也是可以的,如果需要新建一个用户的话,可以以 root 用户运行以下命令来添加新的 lfs 用户和 lfs 用户组。
groupadd lfs
useradd -s /bin/bash -g lfs -m -k /dev/null lfs
命令 useradd 的参数列表如表 2-4 所示。
| 参数 | 描述 |
|---|---|
| -s /bin/bash | 将 bash 设置为 lfs 用户的默认 shell |
| -g lfs | 这个选项将用户 lfs 添加到组 lfs 中 |
| -m | 为 lfs 用户创建主目录 |
| -k /dev/null | 这个参数通过改变输入位置为特殊的空(null)设备,以防止可能从框架目录(默认是 /etc/skel)复制文件 |
| lfs | 这是创建的组和用户的实际名称 |
为 lfs 用户设置密码:
passwd 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
如果你按照建议创建了单独的工作目录,那给 lfs 用户赋予这个目录的所有权:
chown -v lfs $LFS/sources
下一步,以 lfs 用户身份登录。可以重启一个虚拟控制台、显示控制器,或者直接使用命令切换用户:
su - lfs
命令符 - 表示 su 启动登录(login)shell,而非采用非登录(non-login)shell。关于这两种 shell 类型的区别,其实主要是登录时读取的配置文件有所不同,详细的内容可以在 man 手册 bash(1) 和 info bash 中查看详细详情。
2.3.3 设置环境
本节的设置与 2.1.3 节类似,不过这次针对的是 lfs 用户的.bash_profile 和.bashrc,设置的也不只是$LFS。运行以下命令创建一个新的.bash_profile 文件:
cat > ~/.bash_profile << "EOF"
exec env -i HOME=$HOME TERM=$TERM PS1='\u:\w\$ ' /bin/bash
EOF
当以 lfs 用户身份登录时,初始 shell 通常是一个登录 shell,它先读取宿主机的/etc/profile 文件(很可能包括一些设置和环境变量),然后是.bash_profile 文件。添加到.bash_profile 中的 exec 命令使用了新的 shell(除了 HOME、TERM 和 PS1 变量外,其他环境变量完全为空),以代替运行中的 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
下面逐条讲解每个指令地作用。
- set+h 命令关闭了 bash 的散列(hash)功能。通常散列是个不错的功能——bash 用一个散列表来记录可执行文件的完整路径,以规避对同一个可执行文件的重复寻找,节省检索 PATH 的时间。然而,新工具在安装后,我们希望它能够马上替换上次检索的结果。通过关闭散列功能,程序执行的时候就会一直检索 PATH。如此,新编译的工具一旦可用,shell 便能马上在$LFS/tools 目录中找到它们,而不是去使用存在于宿主机不同地方的旧版该程序。
- 设置用户文件新建时的掩码(umask)为 022,以确保新建的文件和目录只有其所有者(owner)可写,但任何人都可读可执行(假设系统调用的 open(2) 使用的是默认模式,新文件将使用 664 权限模式、文件夹为 755 模式)。
- LFS 变量应设置成选定的挂载点。
- LC_ALL 变量控制某些程序的本地化,使它们的消息遵循特定国家的惯例。设置 LC_ALL 为 POSIX 或 C(两者是等价的),确保在 chroot 环境中一切能如期望的那样进行。
- LFS_TGT 变量设置了一个虽非默认,但在构建交叉编译器、连接器和交叉编译临时工作链时,用得上到的兼容的机器说明。3.1 节工具链技术说明中包含更多信息。
- 设置 PATH 变量。一般来说设置/usr/bin 即可,如果/bin 不是符号链接,则也必须将其添加到 PATH 变量中。然后,通过把/tools/bin 放在常规 PATH 的前面,使得所有在第 3 章中安装的程序,一经安装 shell 便能马上使用。与之配合的关闭散列功能,能在第 3 章环境中的程序在可用的情况下,限制使用宿主机中旧程序的风险。
- 设置 CONFIG_SITE。在构建 LFS 的过程中配置文件可能会尝试加载/usr/share/config.site,导致覆盖宿主系统的问题。为防止该问题,将其设置为$LFS/usr/share/config.site。
- export 用于显示这些设置过后的变量,可以做好备份。
一些发行版可能会使用/etc/bash.bashrc。该文件可能会影响 LFS 软件包构建的方式修改 lfs 用户的环境,需要将其移开,以 root 用户运行:
[ ! -e /etc/bash.bashrc ] || mv -v /etc/bash.bashrc /etc/bash.bashrc.NOUSE
最后,启用刚才创建的用户配置:
source ~/.bash_profile
2.3.4 软件包构建时间 SBU
在着手之前,不少人想知道构建和安装一个软件包到底需要多长的时间。因为 Linux From Scratch 能够运行于众多的硬件上,所以具体的构建时间无法一概而论。举一个简单的例子:在最快的系统上,构建最大的软件包 Glibc 需要约 20 分钟,但在慢的系统上有可能需要 3 天!所以,这里使用 SBU(Standard Build Unit,标准构建单元)来代替具体的构建时间。
我们构建的第一个软件包是 Binutils(第 3 章需要用到的包)。将这个软件包在构建时所需要的时间作为 SBU。其他软件的构建时间,都以其为标准进行比较。举个例子,假如构建某个软件耗时 4.5SBU。这意味着如果这个系统,在构建 Binutils 需要 10 分钟,大概需要约 45 分钟来构建此软件包。很幸运,大部分的系统在生成 Binutils 时花的时间都要比这个系统要短。
一般来说,SBU 的结果并不完全准确,因为影响构建的因素太多,包括宿主机系统中 GCC 的版本也会有影响。所以更多的时候,这仅仅是提供一个构建和安装时间的预估。然而,在某些情况下,这个数字可能偏差约十几分钟。
对于大多数带有多个处理器(或内核)的现代操作系统而言,可以通过设置环境变量或者是告知 make 程序具体可用的处理器数目,通过“并行 make”来减少构建的时间。例如,对于 Core2Duo 可以通过以下参数实现两个处理器同时构建:
export MAKEFLAGS='-j 2'
或者直接这样来构建:
make -j2
以该方式使用多处理器时,SBU 值可能比书中的正常值还要大。某些情况下,make 过程仅仅是简单的就失败了。分析错误日志也十分困难:因为不同处理器之间的执行路线是交错的。如果你在构建过程中遇到了问题,为了正确分析错误信息,最好返回单处理器执行构建。
本章小结
本章我们根据 LFS 对于宿主系统的要求,构建并完善了宿主机,创建了 lfs 用户和必要的目录,下载软件包和补丁,完成了构建 LFS 前的所有准备工作。接下来我们即将进入第 3 章为 LFS 构建临时系统。
构建工具链和临时工具
在第 2 章中笔者介绍了 LFS 构建前的准备工作,详细介绍了 LFS 构建的要求,并且分步演示了在虚拟机上构建 Linux 宿主系统的步骤。而在第 3 章中,需要完成的任务是构建独立的构建环境,以保证第 4 章的构建过程可以脱离宿主系统进行。为保证第 4 章的构建操作可以脱离宿主系统独立,需要先构建交叉编译工具链,然后用工具链交叉编译基本工具,最后在进入 chroot 环境中构建临时工具。
LFS 10.0 相对于之前的版本,对于工具链这部分有比较大的修改,除了本章节的 Binutils 和 GCC 会被安装至$LFS/tools 目录,整个第 3 章其他的软件包都将直接安装至$LFS/usr。10.0 之前,会将整个第 3 章的软件包都装在$LFS/tools 目录中。所以整体的结构和命令由很大的不同之处,选择构建 10.0 之前版本 LFS 的读者需要注意。
软件包构建注意事项
在第 2 章跟随小白构建宿主机的读者应该已经安装过软件包了,但是从第 3 章开始,我们无法使用软件包管理器安装软件,所有的软件需要手动编译。为此一些注意事项,请务必遵守。
3.1.1 通用编译指南
首先,对于每一个软件包的构建顺序大致如下:
- 使用 tar 指令,解压软件包 1。
- 进入解压得到的目录中。
- 根据操作指导和说明进行编译。
- 返回/mnt/lfs/sources/目录。
- 删除解压得到的目录。
不过实际软件包的版本会随着 LFS 的版本而升级,所以读者如果实际构建的 LFS 版本高于本书的版本时,请注意随机应变。
还有几点需要注意的,在开始前再自检一下:
- 当前宿主环境已满足 2.1.2 节中对于宿主系统的要求。
- 所有软件包的源文件和补丁存放在 chroot 环境可访问的目录。例如,/mnt/lfs/sources/。
- 输入 echo $LFS 检查一下环境变量 LFS。如果你完全按照本书指导操作,正确的输出结果应该是/mnt/lfs。
3.1.2 软件包构建时间单位 SBU
在着手构建软件包之前,不少人想知道构建和安装一个软件包到底需要多长的时间。不过由于硬件的差异,会指令执行效率不尽相同。就好比,i3 和 i7 其性能本身存在差异,所以显然不能使用绝对时间作为度量单位。由此便诞生了 SBU(Standard Build Unit,标准构建单元)作为相对的时间度量值来统计构建所需耗费的时间。将第 3 章第一个构建的软件包 Binutils 构建的时间作为一个一个标准构建单元,即 1 SBU。而其他软件包的构建耗时也只需要按照本章节的构建时间推算即可获得。 举个例子,如果你在本节的构建工程中消耗了 1 分钟,那么下节的交叉编译的 GCC 将需要 11SBU,也就是 11 分钟。第 3 章的 SBU 值和磁盘空间均是不包含测试套件的数值,后续章节如包含必要的测试,会另行标注测试需要花费的时间。
3.1.3 关于测试套件
很多软件包都提供相应的测试套件。为新构建的软件包运行测试套件是非常好的习惯,因为这样做可以“保证”所有功能都已编译正确。经由一系列的测试,套件往往能够检查出软件包的功能是否都如开发人员预想的那样。然而,这并不能保证所测试的软件包万无一失。
有一些相较而言更为重要的测试套件。例如,核心工具链软件包 GCC、Binutils 和 Glibc 对于一个系统的正常运转起到至关重要的作用。即使这些软件包的测试套件需要花费比较长的时间,特别是对于硬件比较慢的设备来说可能会很难熬,但还是强烈推荐完成这些测试!
除此之外,并不推荐在第 3 章中运行测试套件。因为就是宿主机或多或少会对测试产生影响,经常导致一些令人摸不着头脑的错误信息,而这个的现实无法回避。而在第 3 章中构建的这些工具只是临时的,构建完成的 LFS 并不需要它们,所以并不推荐普通读者在第 3 章中运行测试套件。虽然为测试者和开发者提供了测试套件的说明,但是这依旧是可选项。
有一部分测试套件运行的错误,是 LFS 维护人员知道但觉得不重要的。可以查看 http://www.linuxfromscratch.org/lfs/build-logs/10.0/中的日志,以确认这些失败信息是否是这些处于意料之中的错误。链接地址存放的错误消息涉及的内容基本涵盖了全书所有的测试,包括后续章节的测试结果。
在本章中,确保你运行解压指令的时候,使用的是 lfs 用户。
构建交叉编译工具链
本章节,将安装交叉编译器也就是 Binutils、GCC 这两个包到独立的的$LFS/tools 目录,然后将系统 C 库和标准 C++ 库安装至$LFS/usr,构成整个工具链。
3.2.1 安装 Binutils:二进制工具的集合
Binutils,其完整的名字应该是 GNU Binutils,是一个二进制工具集。其中主要包含 GNU 的链接器 ld 和汇编器 as,以及 addr2line、gold、objdump 等二进制工具。
- 大致构建用时: 1 SBU
- 所需磁盘空间: 617 MB
首先进入$LFS/sources:
cd $LFS/sources
解压并进入软件包:
tar -xf binutils-2.35.tar.xz
cd binutils-2.35
Binutils 手册中建议,在源码目录之外一个专门的编译目录里面编译 Binutils:
mkdir -v build
cd build
现在准备编译 Binutils:
../configure \
--prefix=/tools \
--with-sysroot=$LFS \
--target=$LFS_TGT \
--disable-nls \
--disable-werror
配置选项的含义如表 3-1 所示。
| 参数 | 描述 |
|---|---|
| --prefix=/tools | 告诉配置脚本将 Binutils 程序安装到 /tools 文件夹 |
| --with-sysroot=$LFS | 用于交叉编译,告诉编译系统在 $LFS 中查找所需的目标系统库 |
| --target=$LFS_TGT | 仅在建立交叉编译环境的时候使用,构建一个新的用于编译运行在其他 target 指定系统上的程序的编译器。 |
| --disable-nls | 禁止国际化(i18n),因为国际化对临时工具来说没有必要 |
| --disable-werror | 防止来自宿主编译器的警告事件导致编译停止 |
构建软件包:
make
安装软件包:
make install
在 3.1.2 软件包构建时间单位 SBU 已经介绍了作为衡量软件包构建速度度量值的 SBU,该值的正是以当前软件包 我们要测量一下这个软件包从配置到包括第一次安装在内的编译时间。为了轻松的做到这点,会用类似time { ./configure ... && ... && make install; } 的方式将命令包裹在 time 命令中,具体如下:
time {
../configure \
--prefix=/tools \
--with-sysroot=$LFS \
--target=$LFS_TGT \
--disable-nls \
--disable-werror \
&& make \
&& make install
}
如果运行了上述指令,那后续到 make install 为止的指令就不必执行了,那些只是以上指令的拆解。为了方便在不需要执行的命令的描述前加了*作为区别。
real 4m38.087s
user 3m47.317s
sys 0m38.048s
退出并清理软件包:
cd ../..
rm -rf binutils-2.35
按照 3.1 节构建顺序中的第 4、5 步,在构建完成后我们需要回到/mnt/lfs/sources/并删除构建完的软件包目录。在整个构建过程中很多在本章出现的软件包需要构建两次,Binutils 更是要构建四次,而我们每次构建的指令都不尽相同,不能直接在上次构建的目录里重新构建。
3.2.2 安装 GCC:GNU 编译器套件
GCC 是 GNU Compiler Collection 的首字母缩写,是一套由 GNU 开发的编程语言编译器,包含 C、C++、Objective-C、Fortran、Ada、Go 和 D 的前端和库。GCC 最初是作为 GNU 操作系统的编译器编写的,目前在类 Linux 系统上使用最为广泛的编译器套件。
- 大致构建用时: 11 SBU
- 所需磁盘空间: 3.8 GB
首先,解压并进入软件包:
tar -xf gcc-10.2.0.tar.xz
cd gcc-10.2.0
然后,解压 GMP、MPFR 和 MPC 这三个软件包,并将解压后的目录重命名。属主系统中未必包含这三个软件包,而 GCC 又依赖它们的精度运算函数。在构建临时系统时,我们没有必要花时间构建这三个软件包,只需要将它们解压到 GCC 的目录下,GCC 在构建的过程中即可直接使用。
tar -xf ../mpfr-4.1.0.tar.xz
mv -v mpfr-4.1.0 mpfr
tar -xf ../gmp-6.2.0.tar.xz
mv -v gmp-6.2.0 gmp
tar -xf ../mpc-1.1.0.tar.gz
mv -v mpc-1.1.0 mpc
将 x86_64 的主机上,设置 64 位的库的默认目录为 lib:
case $(uname -m) in
x86_64)
sed -e '/m64=/s/lib64/lib/' \
-i.orig gcc/config/i386/t-linux64
;;
esac
GCC 安装手册建议在软件包目录之外另外创建一个专门用于构建 GCC 的目录。其原因是 GCC 的开发者使用的是这种方式,测试也以此为标准,所以构建的过程有保证。直接在软件包目录或者在软件包目录创建子目录构建,其实也无不可。这里采用的就是在软件包中创建子目录构建的方式,经笔者测试构建过程中没有发生问题。有一点需要注意,我们执行的下一个命令是在软件包的子目录中运行的,如果你试图按照 GCC 的推荐在软件包的同级创建目录,需要对稍后的命令稍加更改。
mkdir -v build
cd build
准备编译 GCC:
../configure \
--target=$LFS_TGT \
--prefix=$LFS/tools \
--with-glibc-version=2.11 \
--with-sysroot=$LFS \
--with-newlib \
--without-headers \
--enable-initfini-array \
--disable-nls \
--disable-shared \
--disable-multilib \
--disable-decimal-float \
--disable-threads \
--disable-libatomic \
--disable-libgomp \
--disable-libquadmath \
--disable-libssp \
--disable-libvtv \
--disable-libstdcxx \
--enable-languages=c,c++
下面来分析下上面 GCC 编译时的配置选项的含义:
| 参数 | 描述 |
|---|---|
--with-glibc-version=2.11 | 这里填写的 glibc 版本与宿主机系统需求相同。设置该参数的理由是为了让 GCC 与宿主机中的 glibc 兼容保持兼容。 |
--with-newlib | 将“newlib” 指定为目标 C 库。 该参数确保编译 libgcc 时定义了常数 inhibit_libc。由于还没有可用的 C 库,这样做可以防止编译任何需要 libc 支持的代码 |
--without-headers | 防止 GCC 查找不需要的头文件 在创建完整的交叉编译器时,GCC 要求标准头文件和目标系统兼容。对于我们的目的来说,不需要这些头文件。 |
--enable-initfini-array | 一些必须的内部数据结构无法被交叉编译器识别,所以需要手动开启 |
--disable-shared | 这个选项强制 GCC 静态链接到它的内部库。我们这样做是为了避免主机系统可能出现的问题 |
--disable-multilib | 不去生成编译为其他平台可执行代码的交叉编译器 在 x86_64 机器上,LFS 还不支持 multilib 配置,使用 64 位主机的读者不应该输入这个选项。这个选项对 x86 来说无害 |
--disable-decimal-float --disable-threads --disable-libatomic --disable-libgomp --disable-libquadmath --disable-libssp --disable-libvtv --disable-libstdcxx | 这些选项取消了对十进制浮点数扩展、线程化、libatomic、libgomp、libquadmath、libssp、libvtv、libcilkrts 和 C++ 标准库的支持。这些功能在编译交叉编译器的时候会导致编译失败,对于交叉编译临时的 libc 来说也没有必要 |
--enable-languages=c,c++ | 这个选项用于指定构建的编译器和运行时库,我们只选择目前我们需要的 C 和 C++ |
运行命令编译 GCC:
make
在 make 完成后,通常需要运行测试套件来检查 make 是否出错,但正如前面提到的,测试套件框架还没有准备好。在此进行测试的并没有太多好处,因为第一遍编译的程序很快会被取代。所以可以直接安装刚才 make 的软件包:
make install
构建 GCC 的过程中会安装若干系统头文件,其中就包含 limits.h。在构建 GCC 的时候,使用的是 GCC 内部的 limits.h,虽然缺少系统头文件的扩展特性,但对于构建临时的 libc 已经足够,不过在后续的构建工作中需要的是完整的 limits.h,所以需要运行以下命令生成该文件:
cd ..
cat gcc/limitx.h gcc/glimits.h gcc/limity.h > \
`dirname $($LFS_TGT-gcc-print-libgcc-file-name)`/install-tools/include/limits.h
安装后用以下命令退出并清理软件包:
cd ../..
rm -rf gcc-10.2.0
如果你不是在软件包内部创建的构建目录,请自行删除构建目录。清楚构建过的软件包的作用在之前已经说明了,之后将不再说明。
3.2.3 安装 Linux API 头文件
Linux 内核是最后安装的一个关键软件包,但下一章的 Glibc 需要 Linux 内核提供的 API,所以需要预先安装内核的 API 头文件。
- 大致构建用时: 0.2 SBU
- 所需磁盘空间: 3.8 GB 头文件也包含在 Linux 内核的软件包内,所以解压 linux-5.8.3.tar.xz 并进入目录 linux-5.8.3:
tar -xf linux-5.8.3.tar.xz
cd linux-5.8.3
编译内核之前养成习惯,运行以下命令确保没有陈旧的文件影响本次构建。其实解压后第一次使用无需执行此命令,如果是多次使用,务必在使用前运行此命令。
make mrproper
从源代码中提取用户可见的内核头文件。头文件起初会被存放在./usr 中,然后再复制到相应的位置。不推荐使用 header_install,那样需要依赖 rsync,在当前环境中可能无法使用。
make headers
find usr/include -name '.*' -delete
rm usr/include/Makefile
cp -rv usr/include $LFS/usr
之后退出并清理软件包:
cd ..
rm -rf linux-5.8.3
3.2.4 安装 Glibc:GNU C 库
Glibc 便是 GNU C 库项目为 GNU 系统和 GNU / Linux 系统以及许多其他使用 Linux 作为内核的系统提供了核心 C 库。任何 Unix-like 操作系统都会需要一个 C 库。该库需要涵盖系统调用和基础功能等一系列的函数,例如常用的 printf、malloc 等。LFS 也将安装其作为使用的系统 C 库。
- 大致构建用时: 4.6 SBU
- 所需磁盘空间: 762 MB 解压并进入软件包:
tar -xf glibc-2.32.tar.xz
cd glibc-2.32
首先,创建 lsb 的符号链接,对于 x86_64
case $(uname -m) in
i?86) ln -sfv ld-linux.so.2 $LFS/lib/ld-lsb.so.3
;;
x86_64) ln -sfv ../lib/ld-linux-x86-64.so.2 $LFS/lib64
ln -sfv ../lib/ld-linux-x86-64.so.2 $LFS/lib64/ld-lsb-x86-64.so.3
;;
esac
patch -Np1 -i ../glibc-2.32-fhs-1.patch
与 GCC 一样,根据 Glibc 手册建议,需要在源文件夹之外的一个专用文件夹中编译 Glibc:
mkdir -v build
cd build
下一步,准备编译 Glibc:
../configure \
--prefix=/tools \
--host=$LFS_TGT \
--build=$(../scripts/config.guess) \
--enable-kernel=3.2 \
--with-headers=/tools/include \
libc_cv_slibdir=/lib
配置选项的含义如表 3-3 所示。
| 参数 | 描述 |
|---|---|
--host=$LFS_TGT--build=$(../scripts/config.guess) | 这些选项的组合效果是让 Glibc 的构建系统配置它自己使用 /tools 里面中的交叉链接器和交叉编译器交叉编译自己其自身。 |
--enable-kernel=3.23.2 | 这告诉 Glibc 编译能支持 3.23.2 以及之以后的内核库。更早的内核版本不受支持。 |
--with-headers=/tools/include | 告诉 Glibc 利用刚刚安装在 tools 文件夹中的头文件编译自身,此这样能够根据内核的具体特性提供更好的优化。 |
libc_cv_slibdir=/lib | 启用/lib 中的库,以替代/lib64 中默认的 64 位库 |
运行本指令,可能会产生以下警告:
configure: WARNING:
*** These auxiliary programs are missing or
*** incompatible versions: msgfmt
*** some features will be disabled.
*** Check the INSTALL file for required versions.
警告意为 msgfmt 缺失,或版本不兼容。Gettext 软件包中包含 msgfmt 程序,不过当前该程序应该由宿主机提供,用于将文本翻译描述生成为二进制消息目录。不过不必担心,此警告是可以忽略的。
在 make 并行构建这个软件包时,可能失败。为避免该情况发生,使用-j1 参数强制使用单核构建。
编译软件包:
make -j1
安装软件包:
在安装前需要注意命令中的 LFS 变量是否已经正确设置。倘若此时您使用的是 root 用户,或者 LFS 变量没有正确设置,以下命令极有可能将新构建的 Glibc 安装至宿主系统中,如此一来,可能会导致宿主机无法正常使用。
make DESTDIR=$LFS install
DESTDIR=$LFS
DESTDIR 参数在大多数软件包中用于指定安装位置。如果此处不加指定,那么安装位置竟会是默认的/目录。这里将其设置为 LFS,也就是在 Chroot 环境后的/目录。
在安装完之后,接下来必须要做的便是确认新工具链的基本功能,即编译和链接功能,是否都如预期的那样可以正常工作。执行以下命令进行检查:
echo 'int main(){}' > dummy.c
$LFS_TGT-gcc dummy.c
readelf -l a.out | grep ': /tools'
如果一切工作正常的话,应该不会出现错误消息,输出形式将如下所示:
[Requesting program interpreter: /tools/lib64/ld-linux-x86-64.so.2]
此处实例的输出结果是 64 位的解释器,32 位机器上对应的解释器,应为/tools/lib/ld-linux.so.2。
如果输出结果错误,或者没有输出结果,那便表明在先前构建的某些地方应该出错了。请回溯并查看构建步骤,找出问题所在,并加以改正。在继续构建工作前,务必解决问题,使得此处能够输出正确结果。
在后续的 4.3.6 节编译 Binutils 时会再一次检查工具链是否正确编译。如果 binutils 编译失败,说明之前安装 binutils、GCC,或者 Glibc 时某些地方出现了错误。
退出并清理软件包:
cd ../..
rm -rf glibc-2.32
3.2.5 安装 Libstdc++-标准的 C++ 库
Libstdc++ 是 GCC 的一部分,是 GNU 标准 C++ 库,许多软件包需要用到 C++ 库。
- 大致构建用时: 0.5 SBU
- 所需磁盘空间: 954 MB Libstdc++ 包含在 GCC 的软件包中。所以解压 gcc-10.2.0.tar.xz 并进入目录 gcc-10.2.0。
tar -xf gcc-10.2.0.tar.xz
cd gcc-10.2.0
为 Libstdc ++ 另外创建一个用于构建的文件夹并进入该文件夹:
mkdir -v build
cd build
准备编译 Libstdc++:
../libstdc++-v3/configure \
--host=$LFS_TGT \
--build=$(../config.guess) \
--prefix=/usr \
--disable-multilib \
--disable-nls \
--disable-libstdcxx-pch \
--with-gxx-include-dir=/tools/$LFS_TGT/include/c++/10.2.0
配置选项的含义如表 3-4 所示。
| 参数 | 作用描述 |
|---|---|
| --host=... | 指示使用我们刚才编译的交叉编译器,而不是/usr/bin 中的。 |
| --host=$LFS_TGT | 替代/usr/bin 中默认的交叉编译器,将其指定为刚才构建的交叉编译器。 |
| --disable-libstdcxx-pch | 此选项防止预编译 include 文件的安装。,在当前阶段并不需安装这些。 |
| --with-gxx-include-dir=/tools/$LFS_TGT/include/c++/10.2.0 | 指定的是 C++ 编译器搜索标准 include 文件的位置。在一般的编译中默认情况,这个该位置信息会从顶层目录自动传入 libstdc++ 的 configure 选项,在但目前我们还没有完善的目录结构,所以的例子中,必须明确指定该信息。 |
之后编译 Libstdc ++:
make
编译后进行库的安装:
make DESTDIR=$LFS install
退出并清理软件包:
cd ../..
rm -rf gcc-10.2.0
交叉编译基本工具
接下来将使用刚刚构建的交叉工具链交叉对基本工具进行交叉编译。自 10.0 开始这些工具会直接安装到最终它们应该被安装到的位置,而非 10.0 之前的版本会先安装到 tool 下。不过效果是一样的,即使构建完成,但还是不能马上使用的状态,实际的操作依赖宿主系统,只是在链接的时候使用的会是它们而已。
在这个时候率先交叉编译它们的主要原因是为了调整依赖关系,交叉编译仍然使用的是宿主系统中的对应依赖。而最后的 LFS 系统需要独立于宿主机运行,那就不得不将依赖转移到其内部。基本工具的一个特点就是被依赖的多,且本身编译器就需要依赖它们。为了构建 LFS 的时候,可以少依赖甚至不依赖宿主系统的环境,需要交叉编译这些基本工具。
3.3.1 安装 M4:宏处理器
M4 是 POSIX 标准中的一部分,对于 Unix 宏处理器的一种实现。很多软件包需求,其中 Autoconf 对其极度依赖。
- 大致构建用时: 0.1 SBU
- 所需磁盘空间: 22 MB 解压并进入软件包:
tar -xf m4-1.4.18.tar.xz
cd m4-1.4.18
对应 glibc-2.28 的需求做一些修复:
sed -i 's/IO_ftrylockfile/IO_EOF_SEEN/' lib/*.c
echo "#define _IO_IN_BACKUP 0x100" >> lib/stdio-impl.h
配置 M4 准备编译:
./configure --prefix=/tools
编译软件包:
make
安装软件包:
make DESTDIR=$LFS install
退出并清理软件包:
cd ..
rm -rf m4-1.4.18
3.3.2 安装 Ncurses:字符终端处理库
GNU Guile Ncurses 是 Guile Scheme 解释器的库,提供创建文本用户界面的函数。
- 大致构建用时: 0.7 SBU
- 所需磁盘空间: 48 MB 解压并进入软件包:
tar -xf ncurses-6.2.tar.gz
cd ncurses-6.2
首先,执行以下命令,确保在配置文件中 gawk 将会被率先找到:
sed -i s/mawk// configure
然后,在宿主系统构建 tic 程序:
mkdir build
pushd build
../configure
make -C include
make -C progs tic
popd
配置 Ncurses 准备编译:
./configure \
--prefix=/usr \
--host=$LFS_TGT \
--build=$(./config.guess) \
--mandir=/usr/share/man \
--with-manpage-format=normal \
--with-shared \
--without-debug \
--without-ada \
--without-normal \
--enable-widec
配置选项的含义:
| 参数 | 描述 |
|---|---|
--with-manpage-format=normal | 避免安装压缩的 man 手册(若宿主发行版使用压缩手册,可能会安装) |
--without-ada | 这个选项会保证禁止构建 Ada 编译器 |
--enable-widec | 这个选项会控制编译使用宽字符库(比如,libncursesw.so.6.1)而不是默认的普通库(比如,libncurses.so.6.1 )进行构建。这些宽字符库在多字节和传统的 8 位环境下都可以使用,而普通库只能用于 8 位环境。宽字符库和普通库的源代码是兼容的,但并不是二进制兼容。 |
--without-normal | 禁止多数静态库的安装 |
编译软件包:
make
安装软件包:
make DESTDIR=$LFS TIC_PATH=$(pwd)/build/progs/tic install
echo "INPUT(-lncursesw)" > $LFS/usr/lib/libncurses.so
TIC_PATH=$(pwd)/build/progs/tic
我们需要传递刚刚构建的,可以在宿主系统运行的 tic 程序的路径,以保证正确创建终端数据库。
echo "INPUT(-lncursesw)" > $LFS/usr/lib/libncurses.so
我们很快将会构建一些需要 libncurses.so 库的软件包。创建这个简短的链接脚本,正如我们在第 8 章中将要做的那样。
将共享库移动到它们应该位于的 /lib 目录中:
mv -v $LFS/usr/lib/libncursesw.so.6* $LFS/lib
由于库文件被移动到其他位置,一个符号链接现在指向不存在的文件。重新生成它:
ln -sfv ../../lib/$(readlink $LFS/usr/lib/libncursesw.so) $LFS/usr/lib/libncursesw.so
退出并清理软件包:
cd ..
rm -rf ncurses-6.2
3.3.3 安装 Bash–最常用的 shell
Bash 是 GNU 项目的 shell。这是一个与 sh 兼容的 shell,它包含了 kornshell(ksh)和 csh(csh)的有用特性。它符合 IEEE POSIX P1003.2/ISO 9945.2 外壳和工具标准。它为编程和交互使用提供了比 sh 更好的功能。此外,大多数 sh 脚本都可以由 Bash 运行而无需修改。
- 大致构建用时: 0.4 SBU
- 所需磁盘空间: 64 MB 解压并进入软件包:
tar -xf bash-5.0.tar.gz
cd bash-5.0
配置 Bash 准备编译:
./configure \
--prefix=/usr \
--build=$(support/config.guess) \
--host=$LFS_TGT \
--without-bash-malloc
配置选项的含义:
| 参数 | 描述 |
|---|---|
--without-bash-malloc | 这个选项会禁用 bash 的内存分配功能(malloc),禁用这个功能后,bash 将使用 glibc 的 malloc 函数,这样会更稳定 |
编译软件包:
make
安装软件包:
make DESTDIR=$LFS install
将可执行文件移动到正确位置:
mv $LFS/usr/bin/bash $LFS/bin/bash
为使用 sh 终端的程序创建一个软链接:
ln -sv bash $LFS/bin/sh
退出并清理软件包:
cd ..
rm -rf bash-5.0
3.3.4 安装 Coreutils–GNU 核心工具组
非常直白的命名方式 Core utils,也就是核心工具组或者工具集。
- 大致构建用时: 0.6 SBU
- 所需磁盘空间: 170 MB 解压并进入软件包:
tar -xf coreutils-8.32.tar.xz
cd coreutils-8.32
配置 Coreutils 准备编译:
./configure \
--prefix=/usr \
--host=$LFS_TGT \
--build=$(build-aux/config.guess) \
--enable-install-program=hostname \
--enable-no-install-program=kill,uptime
配置选项的含义:
| 参数 | 描述 |
|---|---|
--enable-install-program=hostname | 这个选项会允许构建和、安装 hostname 程序,默认是不安装的,但是 Perl 的测试套件需要 |
编译软件包:
make
安装软件包:
make DESTDIR=$LFS install
将程序移动到它们最终安装时的正确位置。尽管在临时环境中这不必要,但我们必须这样做,因为一些程序会硬编码它们的位置:
mv -v $LFS/usr/bin/{cat,chgrp,chmod,chown,cp,date,dd,df,echo} $LFS/bin
mv -v $LFS/usr/bin/{false,ln,ls,mkdir,mknod,mv,pwd,rm} $LFS/bin
mv -v $LFS/usr/bin/{rmdir,stty,sync,true,uname} $LFS/bin
mv -v $LFS/usr/bin/{head,nice,sleep,touch} $LFS/bin
mv -v $LFS/usr/bin/chroot $LFS/usr/sbin
mkdir -pv $LFS/usr/share/man/man8
mv -v $LFS/usr/share/man/man1/chroot.1 $LFS/usr/share/man/man8/chroot.8
sed -i 's/"1"/"8"/' $LFS/usr/share/man/man8/chroot.8
退出并清理软件包:
cd ..
rm -rf coreutils-8.32
3.3.5 安装 Diffutils:一组用于显示文本文件之间的差异实用程序
GNU Diffutils 一个包含若干关于比较文件区别程序的软件包,其中包含诸如“diff”、“cmp”等常用命令。
- 大致构建用时: 0.2 SBU
- 所需磁盘空间: 26 MB 解压并进入软件包:
tar -xf diffutils-3.7.tar.xz
cd diffutils-3.7
配置 Diffutils 准备编译:
./configure --prefix=/usr --host=$LFS_TGT
编译软件包:
make
安装软件包:
make DESTDIR=$LFS install
退出并清理软件包:
cd ..
rm -rf diffutils-3.7
3.3.6 安装 File-用于判断给定文件类型的工具
用于判断给定文件类型的工具。构建后主要包含两个文件:用于区分文件类型的 file 和给 file 提供幻数(magic number)识别例程的 libmagic
- 大致构建用时: 0.1 SBU
- 所需磁盘空间: 21 MB 解压并进入软件包:
tar -xf file-5.39.tar.gz
cd file-5.37
配置 File 准备编译:
./configure --prefix=/usr --host=$LFS_TGT
编译软件包:
make
安装软件包:
make DESTDIR=$LFS install
退出并清理软件包:
cd ..
rm -rf file-5.39
3.3.7 安装 Findutils-GNU 操作系统的基本目录搜索应用程序
GNU Find Utilities 目录检索基本工具。通常会与其他命令一起使用,为其他命令提供模块化的、强大的目录搜索和文件定位功能。
- 大致构建用时: 0.2 SBU
- 所需磁盘空间: 40 MB 解压并进入软件包:
tar -xf findutils-4.7.0.tar.gz
cd findutils-4.7.0
配置 Findutils 准备编译:
./configure \
--prefix=/usr \
--host=$LFS_TGT \
--build=$(build-aux/config.guess)
编译软件包:
make
安装软件包:
make DESTDIR=$LFS install
将可执行文件移动到最终安装时的正确位置:
mv -v $LFS/usr/bin/find $LFS/bin
sed -i 's|find:=${BINDIR}|find:=/bin|' $LFS/usr/bin/updated
退出并清理软件包:
cd ..
rm -rf findutils-4.7.0
3.3.8 安装 Gawk-一种优秀的文本处理工具
- 大致构建用时: 0.2 SBU
- 所需磁盘空间: 46 MB 解压并进入软件包:
tar -xf gawk-5.1.0.tar.xz
cd gawk-5.1.0
首先,确保不要安装一些没有必要的文件:
sed -i 's/extras//' Makefile.in
配置 Gawk 准备编译:
./configure \
--prefix=/usr \
--host=$LFS_TGT \
--build=$(./config.guess)
编译软件包:
make
安装软件包:
make DESTDIR=$LFS install
退出并清理软件包:
cd ..
rm -rf gawk-5.1.0
3.3.9 安装 Grep-用于搜索与正则表达式匹配的行的纯文本数据集
Grep 是一个用于检索一个或多个输入文件的工具,按行匹配,并且会默认的输出符合条件的行。
- 大致构建用时: 0.2 SBU
- 所需磁盘空间: 26 MB 解压并进入软件包:
tar -xf grep-3.4.tar.xz
cd grep-3.4
配置 Grep 准备编译:
./configure \
--prefix=/usr \
--host=$LFS_TGT \
--bindir=/bin
编译软件包:
make
安装软件包:
make DESTDIR=$LFS install
退出并清理软件包:
cd ..
rm -rf grep-3.4
3.3.10 安装 Gzip-GNU 文件压缩程序
- 大致构建用时: 0.1 SBU
- 所需磁盘空间: 10 MB 解压并进入软件包:
tar -xf gzip-1.10.tar.xz
cd gzip-1.10
配置 Gzip 准备编译:
./configure --prefix=/usr --host=$LFS_TGT
编译软件包:
make
安装软件包:
make DESTDIR=$LFS install
将可执行文件移动到最终安装时的正确位置:
mv -v $LFS/usr/bin/gzip $LFS/bin
退出并清理软件包:
cd ..
rm -rf gzip-1.10
3.3.11 安装 make-自动化构建软件
Make 可以自动确认大型程序需要重新编译的部分,并发出指令重新编译这些部分。
- 大致构建用时: 0.1 SBU
- 所需磁盘空间: 16 MB 解压并进入软件包:
tar -xf make-4.3.tar.bz2
cd make-4.3
配置 make 准备编译:
./configure \
--prefix=/usr \
--without-guile \
--host=$LFS_TGT \
--build=$(build-aux/config.guess)
配置选项的含义:
| 参数 | 描述 |
|---|---|
--without-guile | 这个选项会保证 Make-4.2.1 不会去链接宿主系统上可能存在的 Guile 库,避免构建的环境而在下一章里通过 chroot 切换环境后便不再有效了不能正常使用了 |
编译软件包:
make
安装软件包:
make DESTDIR=$LFS install
退出并清理软件包:
cd ..
rm -rf make-4.3
3.3.12 安装 Patch-根据补丁文件中包含的指令更新文本文件
- 大致构建用时: 0.1 SBU
- 所需磁盘空间: 13 MB 解压并进入软件包:
tar -xf patch-2.7.6.tar.xz
cd patch-2.7.6
配置 Patch 准备编译:
./configure \
--prefix=/usr \
--host=$LFS_TGT \
--build=$(build-aux/config.guess)
编译软件包:
make
安装软件包:
make DESTDIR=$LFS install
退出并清理软件包:
cd ..
rm -rf patch-2.7.6
3.3.13 安装 Sed-流编辑器
sed(streameditor)是一个非交互式的命令行文本编辑器。
- 大致构建用时: 0.1 SBU
- 所需磁盘空间: 21 MB 解压并进入软件包:
tar -xf sed-4.8.tar.xz
cd sed-4.8
配置 Sed 准备编译:
./configure \
--prefix=/usr \
--host=$LFS_TGT \
--bindir=/bin
编译软件包:
make
安装软件包:
make DESTDIR=$LFS install
退出并清理软件包:
cd ..
rm -rf sed-4.8
3.3.14 安装 Tar-GNU Tar 提供了 tar 文件的创建和管理功能
文件归档工具,常与 gzip、bzip 等压缩软件一起使用。
- 大致构建用时: 0.2 SBU
- 所需磁盘空间: 39 MB 解压并进入软件包:
tar -xf tar-1.32.tar.xz
cd tar-1.32
配置 Tar 准备编译:
./configure \
--prefix=/usr \
--host=$LFS_TGT \
--build=$(build-aux/config.guess) \
--bindir=/bin
编译软件包:
make
安装软件包:
make DESTDIR=$LFS install
退出并清理软件包:
cd ..
rm -rf tar-1.32
3.3.15 安装 xz-免费的通用数据压缩软件
- 大致构建用时: 0.1 SBU
- 所需磁盘空间: 16 MB 解压并进入软件包:
tar -xf xz-5.2.5.tar.xz
cd xz-5.2.5
配置 Xz 准备编译:
./configure \
--prefix=/usr \
--host=$LFS_TGT \
--build=$(build-aux/config.guess \
--disable-static \
--docdir=/usr/share/doc/xz-5.2.5
编译软件包:
make
安装软件包:
make DESTDIR=$LFS install
确保所有关键文件位于正确的目录中:
mv -v $LFS/usr/bin/{lzma,unlzma,lzcat,xz,unxz,xzcat} $LFS/bin
mv -v $LFS/usr/lib/liblzma.so.* $LFS/lib
ln -svf ../../lib/$(readlink $LFS/usr/lib/liblzma.so) \
$LFS/usr/lib/liblzma.so
退出并清理软件包:
cd ..
rm -rf xz-5.2.5
3.3.16 第二次安装 Binutils
- 大致构建用时: 1.3 SBU
- 所需磁盘空间: 497 MB 解压并进入软件包:
tar -xf binutils-2.35.tar.xz
cd binutils-2.35
再次新建一个单独的编译文件夹:
mkdir -v build
cd build
准备编译 Binutils:
../configure \
--prefix=/usr \
--build=$(../config.guess) \
--host=$LFS_TGT \
--disable-nls \
--enable-shared \
--disable-werror \
--enable-64-bit-bfd
配置选项的含义,之后的含意解释中不会重复之前解释过的内容,如表 3-5 所示。
| 参数 | 作用描述 |
|---|---|
| --enable-shared | 将 libbfd 构建为共享库。 |
| --enable-64-bit-bfd | 启用 64 位支持 (在那些字长较短的平台上)。在 64 位系统上可能并不需要,但无害。 |
编译软件包:
make
安装软件包:
make DESTDIR=$LFS install
退出并清理软件包:
cd ../..
rm -rf binutils-2.35
3.3.17 第二次安装 GCC
- 大致构建用时: 12 SBU
- 所需磁盘空间: 3.7 GB 解压并进入软件包:
tar -xf gcc-10.2.0.tar.xz
cd gcc-10.2.0
与第一次编译 GCC 相同,需求 GMP、MPFR 和 MPC 软件包。解压 tar 包并把它们重名为到所需的文件夹名称:
tar -xf ../mpfr-4.0.2.tar.xz
mv -v mpfr-4.0.2 mpfr
tar -xf ../gmp-6.1.2.tar.xz
mv -v gmp-6.1.2 gmp
tar -xf ../mpc-1.1.0.tar.gz
mv -v mpc-1.1.0 mpc
如果是在 x86_64 环境上构建,设置 64 位的库的默认目录为 lib:
case $(uname -m) in
x86_64)
sed -e '/m64=/s/lib64/lib/' -i.orig gcc/config/i386/t-linux64
;;
esac
再次创建独立的编译文件夹:
mkdir -v build
cd build
创建一个符号链接,以允许 libgcc 在构建时启用 POSIX 线程支持:
mkdir -pv $LFS_TGT/libgcc
ln -s ../../../libgcc/gthr-posix.h $LFS_TGT/libgcc/gthr-default.h
在开始构建 GCC 前,记得清除所有覆盖默认优化开关的环境变量。 准备编译 GCC:
../configure \
--build=$(../config.guess) \
--host=$LFS_TGT \
--prefix=/usr \
CC_FOR_TARGET=$LFS_TGT-gcc \
--with-build-sysroot=$LFS \
--enable-initfini-array \
--disable-nls \
--disable-multilib \
--disable-decimal-float \
--disable-libatomic \
--disable-libgomp \
--disable-libquadmath \
--disable-libssp \
--disable-libvtv \
--disable-libstdcxx \
--enable-languages=c,c++
配置选项的含义:
| 参数 | 描述 |
|---|---|
| --enable-languages=c,c++ | 这个选项确保编译了 C 和 C++ 编译器 |
| --with-build-sysroot=$LFS | 通常,使用 --host 即可保证使用交叉编译器构建 GCC,这个交叉编译器知道它应该在 $LFS 中查找头文件和库。但是,GCC 构建系统使用其他一些工具,它们不知道这个位置。因此需要该选项告诉它们在 $LFS 中查找需要的文件,而不是在宿主系统中查找。 |
| --enable-initfini-array | 该选项在使用 x86 本地编译器构建另一个本地编译器时自动启用。然而我们使用交叉编译器进行编译,因此必须显式启用它。 |
编译软件包:
make
安装软件包:
make DESTDIR=$LFS install
这里创建一个符号链接,作为画龙点睛的一笔。很多程序和脚本执行 cc 而不是 gcc 来保证程序的通用性,并且在所有的 Unix 类型的系统上都能用,而非仅局限于安装了 GCC 的 Unix 类型的系统。运行 cc 使得系统管理员无需考虑要安装那种 C 编译器:
ln -sv gcc $LFS/usr/bin/cc
进入 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 记录当前登录的用户,它由引导脚本动态创建。
构建其他临时工具
由于已经进入了 chroot 环境,从现在开始的构建不会影响宿主系统,但是仍然需要注意 chroot 之后,仍然是以 root 身份执行所有命令,错误的命令很容易摧毁构建中的 LFS 系统。
3.5.1 第二次安装 Libstdc++
- 大致构建用时: 0.8 SBU
- 所需磁盘空间: 1.1 GB Libstdc++ 是 GCC 的一部分。首先应该解压 GCC 的压缩包,然后进入 gcc-10.2.0 文件夹。
tar -xf gcc-10.2.0.tar.xz
cd gcc-10.2.0
创建一个符号链接,允许在 GCC 源码树中构建 Libstdc++:
ln -s gthr-posix.h libgcc/gthr-default.h
为 Libstdc ++ 另外创建一个用于构建的文件夹并进入该文件夹:
mkdir -v build
cd build
准备编译 Libstdc++:
../libstdc++-v3/configure \
CXXFLAGS="-g -O2 -D_GNU_SOURCE" \
--prefix=/usr \
--disable-multilib \
--disable-nls \
--host=$(uname -m)-lfs-linux-gnu\
--disable-libstdcxx-pch
配置选项的含义如表 3-4 所示。
| **参数 ** | 作用描述 |
|---|---|
| --host=... | 指示使用我们刚才编译的交叉编译器,而不是/usr/bin 中的。 |
| CXXFLAGS="-g -O2 -D_GNU_SOURCE" | 这些编译选项在构建完整的 GCC 时,由顶层目录 Makefile 传递。 |
| --host=$(uname -m)-lfs-linux-gnu | 我们需要模拟该软件包作为完整编译器的一部分构建时发生的过程。在完整构建 GCC 构建时,系统会传递该选项。 |
| --disable-libstdcxx-pch | 防止安装预编译包含文件,它在当前阶段没有必要。。 |
之后编译 Libstdc ++:
make
编译后进行库的安装:
make install
退出并清理软件包:
cd ../..
rm -rf gcc-10.2.0
3.5.2 安装 Gettext-一种国际化和本地化(i18n)系统
这个软件包为程序员、翻译人员甚至用户提供了一套集成良好的工具和文档。
- 大致构建用时: 1.9 SBU
- 所需磁盘空间: 310 MB 解压并进入软件包:
tar -xf gettext-0.21.tar.xz
cd gettext-0.21
对于我们这次用到的临时工具集,我们只需要编译安装 Gettext 软件包里的 3 个程序。 配置 Gettext 准备编译:
./configure --disable-shared
配置选项的含义:
| 参数 | 描述 |
|---|---|
| --disable-shared | 不需要安装任何的 Gettext 动态库,所以不需要编译。 |
编译软件包:
make
安装 msgfmt、msgmerge 和 xgettext 程序:
cp -v gettext-tools/src/{msgfmt,msgmerge,xgettext} /usr/bin
退出并清理软件包:
cd ../..
rm -rf gettext-0.21
3.5.3 安装 Bison-语法生成器
GNU Bison 实际上是使用最广泛的 Yacc-like 分析器生成器,使用它可以生成解释器、编译器、协议实现等多种程序。不但与 Yacc 兼容还具有许多 Yacc 不具备的特性。
- 大致构建用时: 0.3 SBU
- 所需磁盘空间: 52 MB 解压并进入软件包:
tar -xf bison-3.7.1.tar.xz
cd bison-3.7.1
配置 Bison 准备编译:
./configure \
--prefix=/usr \
--docdir=/usr/share/doc/bison-3.7.1
--docdir=/usr/share/doc/bison-3.7.1
该选项告诉构建系统将 Bison 文档安装到带有版本号的目录中。
编译软件包:
make
安装软件包:
make install
退出并清理软件包:
cd ..
rm -rf bison-3.7.1
3.5.4 安装 Perl-实用的提取和报告语言
Perl 是 Practical Extraction and Report Language 的缩写,可翻译为 “实用报表提取语言”。从最初是为文本处理而开发的,现在用于各种任务,包括系统管理,Web 开发,网络编程,GUI 开发等。
- 大致构建用时: 1.8 SBU
- 所需磁盘空间: 267 MB 解压并进入软件包:
tar -xf perl-5.32.0.tar.xz
cd perl-5.32.0
配置 Perl 准备编译:
sh Configure \
-des -Dprefix=/usr -Dvendorprefix=/usr \
-Dprivlib=/usr/lib/perl5/5.32/core_perl \
-Darchlib=/usr/lib/perl5/5.32/core_perl \
-Dsitelib=/usr/lib/perl5/5.32/site_perl \
-Dsitearch=/usr/lib/perl5/5.32/site_perl \
-Dvendorlib=/usr/lib/perl5/5.32/vendor_perl \
-Dvendorarch=/usr/lib/perl5/5.32/vendor_perl
配置选项的含义:
| -des | 这是三个选项地组合: -d 对所有选项使用默认值; -e 确保完成所有任务; -s 静默,不产生非必要输出 |
|---|
编译软件包:
make
安装软件包:
make install
退出并清理软件包:
cd ..
rm -rf perl-5.32.0
3.5.5 安装 Python
- 大致构建用时: 1.2 SBU
- 所需磁盘空间: 353 MB 解压并进入软件包:
tar -xf Python-3.8.5.tar.xz
cd Python-3.8.5
配置 Python 准备编译:
./configure \
--prefix=/usr \
--enable-shared \
--without-ensurepip
配置选项的含义:
| 选项 | 描述 |
|---|---|
| --enable-shared | 该选项防止安装静态库。 |
| --without-ensurepip | 该选项用于禁用现阶段好不需要的 Python 安装程序。不安装 pip,在临时系统中没必要安装 |
编译软件包:
make
安装软件包:
make install
退出并清理软件包:
cd ..
rm -rf Python-3.8.5
3.5.6 安装 Texinfo-一种排版语法
Texinfo 是 GNU 项目的官方文档格式。许多非 GNU 项目也广泛使用。Texinfo 使用单个源文件以多种格式生成输出,包括在线和打印(DVI、HTML、Info、PDF、XML 等)。
- 大致构建用时: 0.3 SBU
- 所需磁盘空间: 105 MB 解压并进入软件包:
tar -xf texinfo-6.7.tar.xz
cd texinfo-6.7
配置 Texinfo 准备编译:
./configure --prefix=/usr
作为配置过程的一部分,有一个测试会指出 TestXS_la-TestXS.lo 有一处错误。这与 LFS 没有关系,可以忽略。 编译软件包:
make
安装软件包:
make install
退出并清理软件包:
cd ..
rm -rf texinfo-6.7
3.5.7 安装 Util-Linux 内核组织分发的标准软件包
Linux 的基本工具套件。
- 大致构建用时: 0.8 SBU
- 所需磁盘空间: 133 MB 解压并进入软件包:
tar -xf util-linux-2.36.tar.xz
cd util-linux-2.36
首先创建一个目录,允许 hwclock 程序存储数据:
mkdir -pv /var/lib/hwclock
配置 Util-linux 准备编译:
./configure \
ADJTIME_PATH=/var/lib/hwclock/adjtime \
--docdir=/usr/share/doc/util-linux-2.36 \
--disable-chfn-chsh --disable-login \
--disable-nologin --disable-su \
--disable-setpriv --disable-runuser \
--disable-pylibmount --disable-static \
--without-python
配置选项的含义:
| 参数 | 描述 |
|---|---|
ADJTIME\_PATH=/var/lib/hwclock/adjtime | 该选项根据 FHS 的规则,设定硬件时钟信息记录文件的位置。对于临时工具,这并不是严格要求的,但是这样可以防止在其他位置创建该文件,导致这个文件在安装最终的 Util-linux 软件包时不被覆盖或移除。 |
--disable-* | 这些选项防止产生关于一些组件的警告,这些组件需要一些 LFS 之外,或当前尚未安装的软件包。 |
--without-python | 该选项禁用 Python,防止构建系统尝试构建不需要的语言绑定。 |
编译软件包:
make
安装软件包:
make install
退出并清理软件包:
cd ..
rm -rf util-linux-2.36
清理与备份
本节里的步骤是可选的,但如果你的 LFS 分区容量比较小,知道有些不必要的内容可以被删除也是挺好的。目前编译好的可执行文件和库大概会有 70MB 左右不需要的调试符号。可以通过下面的命令移除这些符号:
1. 清理.la 文件及文档
.la 文件,是 libtool 编译出的库文件,记录同名动态库和静态库的相关信息,仅在静态链接库时起作用,经常不但无用反而有害的一种文件,特别是在使用 autotools 以外的工具构建系统的时候。在 chroot 状态下,运行一下命令寻找并清除.la 文件。
find /usr/{lib,libexec} -name \*.la -delete
如果需要节省更多空间,还可以删除帮助文档。最终 LFS 系统中使用的帮助文档也是在第 4 章中构建的,本章节构建软件包的时候顺带安装的这些帮助文档,最后并不会有任何作用。
rm -rf /usr/share/{info,man,doc}/*
2. 退出 chroot 环境
后续的清理和备份步骤,需要在退出 chroot 的环境中进行。由于清理的是一些无关紧要的内容,备份也不是必须的,所以可以跳过并直接进入第 4 章的构建工作。
专门退出 chroot 环境的原因有两点:
- 可以保证操作中,操作的对象不会被使用;
- 便于将备份的内容转移到存储它的位置。(备份的文件应该存储在$LFS 以外的目录中,保证不会被构建失误所殃及)。
exit
umount $LFS/dev{/pts,}
umount $LFS/{sys,proc,run}
在退出后最好查看以下$LFS 变量是否正确。使用 root 身份在宿主系统执行错误命令是十分危险的操作。
3. 清理无用的调试符号文件
如果你的 LFS 分区容量比较小,执行本步骤可以帮你节约近 90MB 的磁盘空间。 运行以下命令删除调试符号的二进制文件:
strip --strip-debug $LFS/usr/lib/*
strip --strip-unneeded $LFS/usr/{,s}bin/*
strip --strip-unneeded $LFS/tools/bin/*
以上命令会跳过一些文件并报告称无法识别它们的格式。这些文件大多数都是脚本文件,而不是二进制文件,属于正常现象。
注意其中的第一个对 lib 目录的命令使用的是 --strip-debug 而非 --strip-unneeded。这是因为--strip-unneeded 这会损坏静态库,最终导致工具链软件包都要重新构建,所以千万不要弄错。
在执行完清理操作后,可以使用命令 df -h $LFS 查询磁盘可用空间。在这个阶段,应该保证 chroot 分区有至少 5 GB 的可用空间,来保证第 4 章构建和安装 Glibc 和 GCC 的时候,有充足的空间可以容纳,构建 Glibc 和 GCC 会产生的大量临时文件。由于正常步骤在构建后会删除软件包目录,此时临时文件也就一并被删除了,第 4 章构建 Glibc 和 GCC 的时候会是磁盘占用的峰值,磁盘容量分配的比较极限的情况下,需要在构建时
4. 备份
整个第 3 章应该花费了不少时间,不过第 4 章的构建更花时间,操作也会更多,为了避免失误导致重头再来,备份本章节构建的内容显然是更好的选择。当然使用虚拟机的读者完全可以跳过这个步骤,快照也可以完成同样的功效。 运行以下命令,打包并备份整个$LFS 目录,备份需要占用 600M 左右的存储空间。
cd $LFS &&
tar -cJpf $HOME/lfs-temp-tools-10.1.tar.xz .
5. 还原
同样的在确定需要从第 3 章结束重新开始第 4 章时,运行以下命令,删除错误构建的 LFS 系统,并还原备份文件。
cd $LFS &&
rm -rf ./* &&
tar -xpf $HOME/lfs-temp-tools-10.1.tar.xz
还原后记得重新 chroot 后在开始后续的构建步骤,进入步骤参考 3.4 进入 chroot 环境,这点使用虚拟机快照的读者也需要注意。
本章小结
本章作为正式构建 LFS 的前置章节,完成了构建 LFS 系统的所有前置准备工作,使用交叉编译的方式顺利的将后续软件包的依赖控制在 LFS 系统内部,保证了 LFS 的独立性。
安装基础系统软件
本章开始构建 LFS 系统的主体部分,区别于在第 3 章构建交叉编译工具链和交叉编译环境,本章需要构建 73 个软件包,其中也并不会出现临时不需要只
要安装一部分的情况,会耗费比较多的时间。
第 4 章将完成 Linux 内核以外所有软件包的构建。其内容主要分为四个部分:LFS 系统主体部分、Systemd 分支部分、System V 分支部分和调整工作。LFS 的 Systemd 和 System V 两个版本,在构建 Vim 编辑器前的部分相差无几,其中细微的差别会直接将在文中点出;而在 Vim 之后由于依赖原因,构建顺序有比较大的区别,为了避免混乱分成了两个分支部分。需要注意的是两个分支部分的内容相互独立,没有先后关系。实际操作的时候应该根据实际情况选择其中的一个分支实施。不要在 4.2 Systemd 分支构建完成后,直接进入 4.3 System V 分支的构建,而是应该选择其中的一个分支进行构建。
在安装指令之前,每个软件包的开头都提供了关于软件包的信息,包括构建需求的大致时间,以及在过程中需求磁盘空间的大小。在安装指令之后,是一个该软件包即将安装的程序和库(及概要说明)的列表。第 4 章中软件包的 SBU 数值和所需磁盘空间部分为包含了测试套件的数据。
LFS 系统主体部分
主体部分包含大部分软件包的构建,总计 67 个软件包,仅余 systemd 和 System V 相关的个别软件包在分支部分说明。LFS 构建顺序保证被依赖的软件包先构建,再构建需要依赖的软件包,所以很难划分章节,大致会在 binutil、glibc、gcc、bash,以及 PERL 和 Python 分别停一下做一个整理,但并不等同于所有 binutil 小节构建的所有软件包都是 binutil 依赖的软件包,其中有一部分是 gcc 的依赖。
虽然 LFS 手册的构建顺序并不是不可以更改,但考虑到变更顺序可能会导致难以预见的问题,为避免不必要的麻烦,笔者没有也不推荐变更构建顺序。目前的构建顺序至少能够保证每个软件包在构建前所依赖的软件包已被构建。如果发生问题也可以在 Google 和百度上查到类似的说明,极大的降低了风险和难度。大致来说,主体部分的三个重点软件包分别为 binutil、glibc、gcc 这三位老朋友。前半部分主要围绕它们展开构建,在构建完它们之后,后续的部分并没有特别的重点,主要还是为了完成 LSB 规范而构建的软件包。
4.1.1 用于测试 Tcl、Expect、DejaGNU 及其他无依赖软件包
安装 Tcl、Expect 和 DejaGNU 这三个软件包的主要目的是为了满足第 4 章中的测试需求,对于 Glibc、Binutil 和 GCC 的测试非常重要,不建议跳过,但如果明确没有在第四章中运行测试套件需求的话,也可以不用构建。
man-pages 和 iana-etc 和所有的软件包都没有依赖关系,并且构建十分简单,顺带一起构建。
1. 安装 Man-pages
软件包包含 2,200 多个 man 手册页面。帮助使用者了解命令,查询参数。
-
大致构建用时:少于 0.1 SBU
-
所需磁盘空间: 28 MB
解压并进入软件包:
tar -xf man-pages-5.08.tar.xz
cd man-pages-5.08
运行下面的命令安装 Man-pages:
make install
退出并清理软件包:
cd ..
rm -rf man-pages-5.08
2. 安装 Tcl
工具命令语言 (tool command language),一种可靠的通用脚本语言。 此软件包和后面两个包(Expect 和 DejaGNU)用来为 GCC 和 Binutils 还有其他的一些软件包的测试套件提供运行支持。
注意,这里的 Tcl 软件包用的是最小化安装的版本,仅仅是为了运行 LFS 测试。需要完整版的软件包,可参考 BLFS 的 Tcl 流程。
- 大致构建用时: 4.0 SBU
- 所需磁盘空间: 83 MB
解压并进入软件包:
tar -xf tcl8.6.10-src.tar.gz
cd tcl8.6.10
解压文档:
tar -xf ../tcl8.6.10-html.tar.gz --strip-components=1
配置 TCL 准备编译:
SRCDIR=$(pwd)
cd unix
./configure \
--prefix=/usr \
--mandir=/usr/share/man \
$([ "$(uname -m)" = x86_64 ] && echo --enable-64bit)
构建软件包:
make
sed -e "s|$SRCDIR/unix|/usr/lib|" \
-e "s|$SRCDIR|/usr/include|" \
-i tclConfig.sh
sed -e "s|$SRCDIR/unix/pkgs/tdbc1.1.1|/usr/lib/tdbc1.1.1|" \
-e "s|$SRCDIR/pkgs/tdbc1.1.1/generic|/usr/include|" \
-e "s|$SRCDIR/pkgs/tdbc1.1.1/library|/usr/lib/tcl8.6|" \
-e "s|$SRCDIR/pkgs/tdbc1.1.1|/usr/include|" \
-i pkgs/tdbc1.1.1/tdbcConfig.sh
sed -e "s|$SRCDIR/unix/pkgs/itcl4.2.0|/usr/lib/itcl4.2.0|" \
-e "s|$SRCDIR/pkgs/itcl4.2.0/generic|/usr/include|" \
-e "s|$SRCDIR/pkgs/itcl4.2.0|/usr/include|" \
-i pkgs/itcl4.2.0/itclConfig.sh
unset SRCDIR
测试编译结果:
make test
安装软件包:
make install
让安装的库文件可写,这样之后可以删除调试符号。
chmod -v u+w /tools/lib/libtcl8.6.so
安装 TCL 的头文件。后面的软件包 Expect 在编译的时候会用到。
make install-private-headers
现在创建几个必要的软链接:
ln -sv tclsh8.6 /tools/bin/tclsh
退出并清理软件包:
cd ../..
rm -rf tcl8.6.10
3. 安装 Expect
Expect 是类 Unix 系统中用来进行自动化控制和测试的软件工具,由 Tcl 脚本语言编写。
- 大致构建用时: 0.2 SBU
- 所需磁盘空间: 3.9 MB 解压并进入软件包:
tar -xf expect5.45.4.tar.gz
cd expect5.45.4
配置 Expect 准备编译:
./configure \
--prefix=/usr \
--with-tcl=/usr/lib \
--enable-shared \
--mandir=/usr/share/man \
--with-tclinclude=/usr/include
配置选项的含义:
| 参数 | 描述 |
|---|---|
| --with-tcl=/tools/lib | 保证 configure 配置脚本会从临时工具目录里找 tcl,而不是在宿主机系统中寻找 |
| --with-tclinclude=/tools/include | 指定 tcl 内部头文件的位置。通过这个选项可以避免 configure 脚本不能自动发现 tcl 头文件位置的情况 |
构建软件包:
make
测试编译结果:
make test
安装软件包:
make install
ln -svf expect5.45.4/libexpect5.45.4.so /usr/lib
退出并清理软件包:
cd ..
rm -rf expect5.45.4
4. 安装 DejaGNU
DejaGNU 是一个著名的测试框架,由 Expect 编写。
- 大致构建用时:少于 0.1 SBU
- 所需磁盘空间: 4.6 MB 解压并进入软件包:
tar -xf dejagnu-1.6.2.tar.gz
cd dejagnu-1.6.2
配置 DejaGNU 准备编译:
./configure --prefix=/usr
makeinfo --html --no-split -o doc/dejagnu.html doc/dejagnu.texi
makeinfo --plaintext -o doc/dejagnu.txt doc/dejagnu.texi
构建安装软件包:
make install
install -v -dm755 /usr/share/doc/dejagnu-1.6.2
install -v -m644 doc/dejagnu.{html,txt} /usr/share/doc/dejagnu-1.6.2
测试编译安装结果:
make check
退出并清理软件包:
cd ..
rm -rf dejagnu-1.6.2
- 安装 iana-etc iana-etc 中主要是/etc/protocols 和/etc/services 两个配置文件,提供了网络服务和协议的数据。
- 大致构建用时:少于 0.1 SBU
- 所需磁盘空间: 4.7 MB 解压并进入软件包:
tar -xf iana-etc-20200821.tar.gz
cd iana-etc-20200821
只需要将文件复制到 etc 目录下即可:
cp services protocols /etc
退出并清理软件包:
cd ..
rm -rf iana-etc-20200821
4.1.2 构建并配置 glibc
老朋友了,在第三章构建交叉编译工具链的时候已经构建过了,不过此次由于基本工具和临时工具已经就位,构建的时候便不用依赖宿主的软件包了。
1. 安装 glibc
- 大致构建用时: 20 SBU
- 所需磁盘空间: 2.4 GB 解压并进入软件包:
tar -xf glibc-2.32.tar.xz
cd glibc-2.32
glibc 构建系统是独立的,并且会完美地安装,即使编译器指定的文件和链接器仍然指向/tools。在 glibc 安装之前,不能调整指定的文件和链接器,因为 glibc 的 autoconf 测试会给出错误结果并阻碍目标实现干净的构建。 有些 glibc 程序会用到和 FHS 不兼容的/var/db 目录来存储它们的运行时数据。打上如下的补丁可以让这些程序在 FHS 兼容的位置存储它们的运行时数据。
patch -Np1 -i ../glibc-2.32-fhs-1.patch
glibc 文档里建议在 Glibc 特定编译目录下编译:
mkdir -v build
cd build
配置 glibc 准备编译:
../configure \
--prefix=/usr \
--disable-werror \
--enable-kernel=3.2 \
--enable-stack-protector=strong \
--with-headers=/usr/include \
libc_cv_slibdir=/lib
配置选项的含义如表 4-2 所示。
| 配置选项 | 含义 |
|---|---|
| --disable-werror | 禁用了传递给 GCC 的-werror 选项。为了测试套件,此项必不可少 |
| --enable-kernel=3.2 | 告诉系统 glibc 可能在 3.2 版本的内核上使用,避免在旧内核上调用新内核的功能 |
| --enable-stack-protector=strong | 通过添加额外代码检查缓冲区溢出(例如,堆栈粉碎攻击)来增强系统安全性 |
| --with-headers=/usr/include | 告诉构建系统在哪里可以找到内核 API 的头文件。默认情况下,会在 /tools/include 中查找头文件 |
| libc_cv_slibdir=/lib | 为所有系统设置正确的库。我们不希推荐用 lib64 |
编译软件包:
make
注意:运行 glibc 的测试套件是很关键的。在任何情况下都不要跳过这个测试。
通常会有一些测试不能通过。若是下面列出的这些失败,通常就可以安心地无视了。
case $(uname -m) in
i?86) ln -sfnv $PWD/elf/ld-linux.so.2 /lib ;;
x86_64) ln -sfnv $PWD/elf/ld-linux-x86-64.so.2 /lib ;;
esac
在 chroot 环境的这个阶段需要上面的符号链接以运行测试。在接下来的安装中,这些链接将会被覆盖。
make check
你可能会看到一些失败的测试项。glibc 的测试套件对宿主系统有一定的依赖。下面是一些 LFS 版本中最常见的问题。
- misc/tst-ttyname:已知会在 LFS 的 chroot 环境中失败。
- inet/tst-idna_name_classify:已知会在 LFS 的 chroot 环境中失败。
- posix/tst-getaddrinfo4 和 posix/tst-getaddrinfo5:可能在某些架构上失败。
- nss/tst-nss-files-hosts-multi:由于未知的原因,测试可能失败。
- rt/tst-cputimer{1,2,3}:测试取决于宿主机的系统内核。众所周知,内核版本为 4.14.91–4.14.96、4.19.13–4.19.18 和 4.20.0–4.20.5 会导致这些测试失败。
如果你系统的 CPU 不是相对较新的 Intel 或 AMD 处理器,数学运算测试有时候会失败。虽然这只是无关紧要的消息,在安装 glibc 时会报告找不到/etc/ld.so.conf 文件。下面的方式可以避免这个警告:
touch /etc/ld.so.conf
运行以下命令,修复生成的 Makefile 以跳过一个没有必要的完整性检查,该检查在部分 LFS 环境中会失败:
sed '/test-installation/s@$(PERL)@echo not running@' -i ../Makefile
安装软件包:
make install
为 nscd 安装配置文件并创建运行时目录:
cp -v ../nscd/nscd.conf /etc/nscd.conf
mkdir -pv /var/cache/nscd
为 nscd 安装系统支持文件:
install -v -Dm644 ../nscd/nscd.tmpfiles /usr/lib/tmpfiles.d/nscd.conf
install -v -Dm644 ../nscd/nscd.service /lib/systemd/system/nscd.service
接着,安装可以使系统响应不同语言的地域设置。没有一个地域是必需的,但是如果缺少了其中的某些地域,可能会导致在未来的软件包测试套件中,跳过了重要的测试项目。 单独的语言环境可以用 localedef 程序安装。例如,下面第一个 localedef 命令将 /usr/share/i18n/locales/cs_CZ 字符表定义组合在一起,并将结果附加到 /usr/share/i18n/charmaps/UTF-8.gz 文件末尾。下面的命令将安装能完美覆盖测试所需语言环境的最小集合:
mkdir -pv /usr/lib/locale
localedef -i POSIX -f UTF-8 C.UTF-8 2> /dev/null || true
localedef -i cs_CZ -f UTF-8 cs_CZ.UTF-8
localedef -i de_DE -f ISO-8859-1 de_DE
localedef -i de_DE@euro -f ISO-8859-15 de_DE@euro
localedef -i de_DE -f UTF-8 de_DE.UTF-8
localedef -i el_GR -f ISO-8859-7 el_GR
localedef -i en_GB -f UTF-8 en_GB.UTF-8
localedef -i en_HK -f ISO-8859-1 en_HK
localedef -i en_PH -f ISO-8859-1 en_PH
localedef -i en_US -f ISO-8859-1 en_US
localedef -i en_US -f UTF-8 en_US.UTF-8
localedef -i es_MX -f ISO-8859-1 es_MX
localedef -i fa_IR -f UTF-8 fa_IR
localedef -i fr_FR -f ISO-8859-1 fr_FR
localedef -i fr_FR@euro -f ISO-8859-15 fr_FR@euro
localedef -i fr_FR -f UTF-8 fr_FR.UTF-8
localedef -i it_IT -f ISO-8859-1 it_IT
localedef -i it_IT -f UTF-8 it_IT.UTF-8
localedef -i ja_JP -f EUC-JP ja_JP
localedef -i ja_JP -f SHIFT_JIS ja_JP.SIJS 2> /dev/null || true
localedef -i ja_JP -f UTF-8 ja_JP.UTF-8
localedef -i ru_RU -f KOI8-R ru_RU.KOI8-R
localedef -i ru_RU -f UTF-8 ru_RU.UTF-8
localedef -i tr_TR -f UTF-8 tr_TR.UTF-8
localedef -i zh_CN -f GB18030 zh_CN.GB18030
localedef -i zh_HK -f BIG5-HKSCS zh_HK.BIG5-HKSCS
或者,也可以一次性安装在 glibc-2.29/localedata/SUPPORTED 文件里列出的所有语言环境(包括以上列出的所有语言环境以及其他更多),执行下面这个非常耗时的命令:
make localedata/install-locales
你需要的语言环境几乎不太可能没有列在 glibc-2.29/localedata/SUPPORTED 文件中,但如果真的没有,可以使用 localedef 命令创建和安装。
当前,glibc 在解决国际化域名时使用 libidn2。此为运行时依赖。如果需要此功能,可以参考 BLFS libidn2 页相关的安装指令。
2. 配置 glibc
由于 glibc 的默认状态在网络环境下工作得并不好,所以需要创建/etc/nsswitch.conf 文件。 创建新的/etc/nsswitch.conf 通过以下命令:
cat > /etc/nsswitch.conf << "EOF"
# Begin /etc/nsswitch.conf
passwd: files
group: files
shadow: files
hosts: files dns
networks: files
protocols: files
services: files
ethers: files
rpc: files
# End /etc/nsswitch.conf
EOF
通过以下命令安装并启动时区数据:
tar -xf ../../tzdata2018i.tar.gz
ZONEINFO=/usr/share/zoneinfo
mkdir -pv $ZONEINFO/{posix,right}
for tz in etcetera southamerica northamerica europe africa antarctica \
asia australasia backward pacificnew systemv; do
zic -L /dev/null -d $ZONEINFO ${tz}
zic -L /dev/null -d $ZONEINFO/posix ${tz}
zic -L leapseconds -d $ZONEINFO/right ${tz}
done
cp -v zone.tab zone1970.tab iso3166.tab $ZONEINFO
zic -d $ZONEINFO -p America/New_York
unset ZONEINFO
zic 命令的含义如表 4-3 所示。
| 参数 | 含义 |
|---|---|
| zic -L /dev/null ... | 创建没有时间补偿的 posix 时区数据。一般将它们同时放在 zoneinfo 目录和 zoneinfo/posix 目录。需要将 POSIX 时区数据放到 zoneinfo 目录下,否则很多测试套件会报错。在嵌入式平台,如果存储空间紧张而且你也不准备更新时区,也可以不用 posix 目录,从而节省 1.9MB 空间,但是一些应用程序或测试套件也许会出错 |
| zic -L leapseconds ... | 创建包含时间补偿的 right 时区数据。在嵌入式平台,空间比较紧张而且你也不打算更新时区或者不需要准确时间,你可以忽略 right 目录,从而节省 1.9MB 空间 |
| zic ... -p ... | 创建 posixrules 文件。我们使用纽约是因为 POSIX 要求夏令时规则与 US 标准一致 |
运行以下脚本,确定本地时区:
tzselect
在询问了几个关于位置的问题后,脚本会输出所在时区的名字(比如 Asia/Shanghai)。在 /usr/share/zoneinfo 文件中也有其他一些可用时区,比如 Canada/Eastern 或 EST5EDT,这些时区并没有被脚本列出来,但也是可以使用的。 然后运行下面的命令创建/etc/localtime 文件:
ln -sfv /usr/share/zoneinfo/<xxx> /etc/localtime
将命令中的
cat > /etc/ld.so.conf << "EOF"
# Begin /etc/ld.so.conf
/usr/local/lib
/opt/lib
EOF
如果需要的话,动态库加载器也可以查找目录并包含里面配置文件的内容。通常在这个包含目录下的文件只有一行字指向库目录。运行下面的命令增加这个功能:
cat >> /etc/ld.so.conf << "EOF"
# Add an include directory
include /etc/ld.so.conf.d/*.conf
EOF
mkdir -pv /etc/ld.so.conf.d
退出并清理软件包:
cd ../..
rm -rf glibc-2.32
4.1.3 构建 binutil 和 binutil 依赖的软件包及其他
整理一下依赖关系,主要安装的依赖软件包是:File、Flex、zlib,其他的依赖在第三章已经构建了,而测试的依赖在 4.1.1 用于测试 Tcl、Expect、DejaGNU 及其他无依赖软件包已经构建。 File 依赖 Bzip2 和 xz,Flex 依赖 M4,所以各自先构建了所依赖的软件包。 zstd 是 gcc 的依赖,需要依赖 xz,可能自身比较小就顺手和 xz 一起构建了(推测)。 bc 理论只要在内核编译前编译即可,本身的依赖 bash, binutils, coreutils, gcc, glibc、grep、make 又在第三章已经构建,所以放在何时构建都没什么关系,排在 binutil 之前应该是字母排序的原因。
1. 安装 zlib:系统底层压缩算法支持
- 大致构建用时:少于 0.1 SBU
- 所需磁盘空间: 4.4 MB 解压并进入软件包:
tar -xf zlib-1.2.11.tar.xz
cd zlib-1.2.11
准备编译 Zlib:
./configure --prefix=/usr
编译软件包:
make
输入命令查看结果:
make check
安装软件包:
make install
共享库需要移动到/lib,因此需要重建.so 里面的/usr/lib 文件:
mv -v /usr/lib/libz.so.* /lib
ln -sfv ../../lib/$(readlink /usr/lib/libz.so) /usr/lib/libz.so
退出并清理软件包:
cd ..
rm -rf zlib-1.2.11
2. 安装 Bzip2:无损压缩软件
- 大致构建用时:少于 0.1 SBU
- 所需磁盘空间: 7.7 MB 解压并进入软件包:
tar -xf bzip2-1.0.8.tar.gz
cd bzip2-1.0.8
使用能为这个软件包安装帮助文档的补丁:
patch -Np1 -i ../bzip2-1.0.8-install_docs-1.patch
下面的命令确保安装的符号链接是相对链接:
sed -i 's@\(ln -s -f \)$(PREFIX)/bin/@\1@' Makefile
确认 man 页面安装到了正确的位置:
sed -i "s@(PREFIX)/man@(PREFIX)/share/man@g" Makefile
准备编译 Bzip:
make -f Makefile-libbz2_so
make clean
这会使用不同的 Makefile 文件编译 Bzip2,在这里是 Makefile-libbz2_so,它会创建动态 libbz2.so 库,并把它链接到 Bzip2 工具。 编译并测试软件包:
make
安装程序:
make PREFIX=/usr install
安装使用动态链接库的 bzip2 二进制文件到 /bin 目录,创建一些必须的符号链接并清理:
cp -v bzip2-shared /bin/bzip2
cp -av libbz2.so* /lib
ln -sv ../../lib/libbz2.so.1.0 /usr/lib/libbz2.so
rm -v /usr/bin/{bunzip2,bzcat,bzip2}
ln -sv bzip2 /bin/bunzip2
ln -sv bzip2 /bin/bzcat
退出并清理软件包:
cd ..
rm -rf bzip2-1.0.8
3. 安装 xz:通用数据压缩软件
- 大致构建用时: 0.2 SBU
- 所需磁盘空间: 15MB 解压并进入软件包:
tar -xf xz-5.2.5.tar.xz
cd xz-5.2.5
准备编译 xz:
./configure \
--prefix=/usr \
--disable-static \
--docdir=/usr/share/doc/xz-5.2.5
编译软件包:
make
用以下命令测试结果:
make check
安装软件包并确保所需的文件都在正确目录中:
make install
mv -v /usr/bin/{lzma,unlzma,lzcat,xz,unxz,xzcat} /bin
mv -v /usr/lib/liblzma.so.* /lib
ln -svf ../../lib/$(readlink /usr/lib/liblzma.so) /usr/lib/liblzma.so
退出并清理软件包:
cd ..
rm -rf xz-5.2.5
4. 安装 zstd
一种实时压缩算法,提供较高的压缩比,具有很宽的压缩/速度权衡范围,同时具有非常快速的解码器。
- 大致构建用时: 0.7 SBU
- 所需磁盘空间: 16MB 解压并进入软件包:
tar -xf zstd-1.4.5.tar.gz
cd zstd-1.4.5
构建软件包:
make
用以下命令测试结果:
make check
本软件包不包含测试套件。 安装软件包:
make prefix=/usr install
删除静态库,并将共享库移动到/lib。然后,重新创建指向/usr/lib 目录的.so 文件的符号:
rm -v /usr/lib/libzstd.a
mv -v /usr/lib/libzstd.so.* /lib
ln -sfv ../../lib/$(readlink /usr/lib/libzstd.so) /usr/lib/libzstd.so
退出并清理软件包:
cd ..
rm -rf zstd-1.4.5.tar.gz
- 安装 File:用于判断给定文件类型的工具
- 大致构建用时: 0.1 SBU
- 所需磁盘空间: 14 MB 解压并进入软件包:
tar -xf file-5.39.tar.gz
cd file-5.39
准备编译 File:
./configure --prefix=/usr
编译软件包:
make
输入命令检查结果:
make check
安装软件包:
make install
退出并清理软件包:
cd ..
rm -rf file-5.39
- 安装 Readline:为交互式程序提供行编辑和历史记录功能
- 大致构建用时: 0.1 SBU
- 所需磁盘空间: 15 MB 解压并进入软件包:
tar -xf readline-8.0.tar.gz
cd readline-8.0
重装 Readline 会使旧的库移动到
sed -i '/MV.*old/d' Makefile.in
sed -i '/{OLDSUFF}/c:' support/shlib-install
准备编译 Readline:
./configure \
--prefix=/usr \
--disable-static \
--with-curses \
--docdir=/usr/share/doc/readline-8.0
选项--with-curses 告诉 Readline 可以在 curses 库中查找 termcap 库函数,而非 termcap 库。以此保证生成正确的 readline.pc 文件。 编译软件包:
make SHLIB_LIBS="-lncursesw"
强制将 Readline 链接到 libncursesw 库。 该软件包没有测试套件,直接安装软件包:
make SHLIB_LIBS="-lncursesw" install
现在移动动态库到更合适它的位置/lib 中去,并修正一些文件权限和符号链接:
mv -v /usr/lib/lib{readline,history}.so.* /lib
chmod -v u+w /lib/lib{readline,history}.so.*
ln -sfv ../../lib/$(readlink /usr/lib/libreadline.so) /usr/lib/libreadline.so
ln -sfv ../../lib/$(readlink /usr/lib/libhistory.so ) /usr/lib/libhistory.so
如果需要的话,安装帮助文档:
install -v -m644 doc/*.{ps,pdf,html,dvi} /usr/share/doc/readline-8.0
退出并清理软件包:
cd ..
rm -rf readline-8.0
7. 安装 M4:宏处理器
- 大致构建用时: 0.4 SBU
- 所需磁盘空间: 31 MB 解压并进入软件包:
tar -xf m4-1.4.18.tar.xz
cd m4-1.4.18
对应 glibc-2.28 的需求做一些修复:
sed -i 's/IO_ftrylockfile/IO_EOF_SEEN/' lib/*.c
echo "#define _IO_IN_BACKUP 0x100" >> lib/stdio-impl.h
准备编译 M4:
./configure --prefix=/usr
编译软件包:
make
用以下命令测试结果:
make check
安装软件包:
make install
退出并清理软件包:
cd ..
rm -rf m4-1.4.18
8. 安装 bc:任意精度计算器语言
- 大致构建用时: 0.1 SBU
- 所需磁盘空间: 4.1 MB 解压并进入软件包:
tar -xf bc-3.1.5.tar.xz
cd bc-3.1.5
准备编译 bc:
PREFIX=/usr CC=gcc CFLAGS="-std=c99" ./configure.sh -G -O3
配置选项的含义:
| 参数 | 描述 |
|---|---|
| CC=gcc CFLAGS="-std=c99" | 这些参数指定要使用的编译器和 C 标准 |
| -O3 | 指定要使用的优化 |
| -G | 省略测试套件的一部分,如果没有 GNU bc,这些套件将无法使用 |
编译软件包:
make
测试 bc,运行:
make test
安装软件包:
make install
退出并清理软件包:
cd ..
rm -rf bc-3.1.5
9. 安装 Flex:词法分析器
- 大致构建用时: 0.4 SBU
- 所需磁盘空间: 35 MB 解压并进入软件包:
tar -xf flex-2.6.4.tar.gz
cd flex-2.6.4
准备编译 Flex:
./configure --prefix=/usr --docdir=/usr/share/doc/flex-2.6.4
编译软件包:
make
用以下命令测试结果(大约 0.5 SBU):
make check
安装软件包:
make install
对于一些程序来说,Flex 可能比较陌生,但如果提到 Flex 的先辈 lex 大家可能或多或少有些了解。为了支持这些程序,为 Flex 在 bin 目录下创建符号链接 lex:
ln -sv flex /usr/bin/lex
退出并清理软件包:
cd ..
rm -rf flex-2.6.4
- 安装 binutils:二进制工具的集合
- 大致构建用时: 6.5 SBU
- 所需磁盘空间: 4.8 GB 解压并进入软件包:
tar -xf binutils-2.35.tar.xz
cd binutils-2.35
通过一个简单测试验证在 chroot 环境下 PTY 工作正常:
expect -c "spawn ls"
这个命令应该输出以下内容:
spawn ls
假如输出包括下面的信息,那么表示没有为 PTY 操作设置好环境。在运行 Binutils 和 GCC 的测试套件之前需要解决这个问题:
The system has no more ptys.
Ask your system administrator to create more.
删除一项测试,以保证不会影响到测试的完成:
sed -i '/@\tincremental_copy/d' gold/testsuite/Makefile.in
按照 binutils 的文档建议,创建一个专门用于编译目录:
mkdir -v build
cd build
接下来,配置 binutils 准备编译:
../configure --prefix=/usr \
--enable-gold \
--enable-ld=default \
--enable-plugins \
--enable-shared \
--disable-werror \
--enable-64-bit-bfd \
--with-system-zlib
配置选项的含义如表 4-4 所示。
| 参数 | 含义 |
|---|---|
| --enable-gold | 构建 gold 链接器并将其安装为 ld.gold(紧挨着默认链接器) |
| --enable-ld=default | 构建原来的 bdf 链接器并将其安装为 ld(默认链接器)和 ld.bfd |
| --enable-plugins | 为链接器启用插件支持 |
| --enable-64-bit-bfd | 启用 64 位支持(针对字宽较窄的主机)。64 位系统可能没什么必要,但也不会有什么坏处 |
| --with-system-zlib | 使用安装的 zlib 库替代自带的版本构建 |
编译软件包:
make tooldir=/usr
一般来说,tooldir (最终存放可执行文件的目录) 设置为 $(exec_prefix)/$(target_alias)。例如,x86_64 机器会把它扩展为/usr/x86_64-unknown-linux-gnu。因为这是个自定制的系统,并不需要 /usr 中的特定目标目录。如果系统用于交叉编译(例如,在 Intel 机器上编译能生成在 PowerPC 机器上运行的代码的软件包)会使用$(exec_prefix)/$(target_alias)。
本节 binutils 测试套件至关重要,任何情况下都不能跳过。
查看测试结果:
make -k check
已知测试中的 debug_msg.sh 会失败,可以忽略。 安装软件包:
make tooldir=/usr install
退出并清理软件包:
cd ../..
rm -rf binutils-2.35
4.1.4 构建 gcc 和 gcc 依赖的软件包
最后一次安装 gcc,首先要装得自然还是 GMP、MPFR、MPC,区别于前两次只是将软件包解压并复制到 gcc 软件包目录中,这次是直接构建。这三个软件包的依赖已经全部在第 3 章交叉编译完了。只需要按照自身的依赖顺序构建即可。
gcc 的测试需要用到 Shadow,所以需要先行构建 Shadow 的依赖 acl、attr 以及 libcap,acl、attr 和 libcap 三者之间也存在依赖关系,所以要以 attr、acl、libcap 的顺序构建。
按理说 GCC 的构建,除了这三个软件包,在第 3 章中没有交叉编译的就只有 zstd 了,而在 Binutils 安装前,已经伴随着一众压缩相关的软件包,完成了安装任务。不过,GCC 的测试套件需要 Shadow,由此也就必须安装 Shadow 的依赖的软件包了。
4.1.4 构建 gcc 和 gcc 依赖的软件包
最后一次安装 gcc,首先要装得自然还是 GMP、MPFR、MPC,区别于前两次只是将软件包解压并复制到 gcc 软件包目录中,这次是直接构建。这三个软件包的依赖已经全部在第 3 章交叉编译完了。只需要按照自身的依赖顺序构建即可。
gcc 的测试需要用到 Shadow,所以需要先行构建 Shadow 的依赖 acl、attr 以及 libcap,acl、attr 和 libcap 三者之间也存在依赖关系,所以要以 attr、acl、libcap 的顺序构建。
按理说 GCC 的构建,除了这三个软件包,在第 3 章中没有交叉编译的就只有 zstd 了,而在 Binutils 安装前,已经伴随着一众压缩相关的软件包,完成了安装任务。不过,GCC 的测试套件需要 Shadow,由此也就必须安装 Shadow 的依赖的软件包了。

1. 安装 GMP:GNU 多重精度运算库
大致构建用时: 1.1 SBU 所需磁盘空间: 52 MB 解压并进入软件包: tar -xf gmp-6.2.0.tar.xz cd gmp-6.2.0 如果为 32 位的 x86 系统编译,但是 CPU 可以运行 64 位代码,并且环境中有指定的 CFLAGS,那么配置脚本会尝试配置为 64 位从而导致失败。用以下方式执行配置命令来避免这个问题: ABI=32 ./configure ... GMP 的默认设定会为主机的处理器优化库。如果你不需要完美符合主机 CPU 的库,可以通过下方命令创建通用库,这样的话契合度会差一些:
cp -v configfsf.guess config.guess
cp -v configfsf.sub config.sub
准备编译 GMP:
./configure --prefix=/usr \
--enable-cxx \
--disable-static \
--docdir=/usr/share/doc/gmp-6.1.2
配置选项的含义如表 4-6 所示。
| 配置选项 | 含义 |
|---|---|
| --enable-cxx | 这个参数启用 C++ 支持 |
| --docdir=/usr/share/doc/gmp-6.1.2 | 这个变量指定保存文档的正确位置 |
编译软件包并生成 HTML 文档:
make
make html
注意:该章节 GMP 的测试套件至关重要,任何情况下都不能跳过 查看结果:
make check 2>&1 | tee gmp-check-log
GMP 中的代码对于其构建的处理器进行了高度优化。有时检测处理器的代码会误认系统的功能,并在测试中报错,或在其他应用使用 GMP 库的时候显示消息「Illegal instruction(非法指令)」。在这种情况下,GMP 需要重新配置选项--build=x86_64-unknown-linux-gnu 并重新构建。 确认测试套件中所有的 190 个测试都通过了,并通过输入下面的命令检查结果:
awk '/# PASS:/{total+=$3} ; END{print total}' gmp-check-log
安装软件包和文档:
make install
make install-html
退出并清理软件包:
cd ..
rm -rf gmp-6.2.0
2. 安装 MPFR:多精度浮点可靠运算库
- 大致构建用时: 0.9 SBU
- 所需磁盘空间: 38 MB
解压并进入软件包:
tar -xf mpfr-4.1.0.tar.xz
cd mpfr-4.1.0
准备编译 MPFR:
./configure --prefix=/usr \
--disable-static \
--enable-thread-safe \
--docdir=/usr/share/doc/mpfr-4.1.0
编译软件包并生成 HTML 文档:
make
make html
该章节 MPFR 的测试套件至关重要,任何情况下都不能跳过。 检查结果确认通过了所有的测试:
make check
安装软件包以及文档:
make install
make install-html
退出并清理软件包:
cd ..
rm -rf mpfr-4.1.0
- 安装 MPC:高精度复数运算库
- 大致构建用时: 0.3 SBU
- 所需磁盘空间: 22 MB
解压并进入软件包:
tar -xf mpc-1.1.0.tar.gz
cd mpc-1.1.0
准备编译 MPC:
./configure --prefix=/usr \
--disable-static \
--docdir=/usr/share/doc/mpc-1.1.0
编译软件包并生成 HTML 文档:
make
make html
用以下命令检查结果:
make check
安装软件包及其帮助文档:
make install
make install-html
退出并清理软件包:
cd ..
rm -rf mpc-1.1.0
4. 安装 attr:用于管理文件系统扩展属性的实用程序
- 大致构建用时:少于 0.1 SBU
- 所需磁盘空间: 4.2 MB 解压并进入软件包:
tar -xf attr-2.4.48.tar.gz
cd attr-2.4.48
准备编译 Attr:
./configure --prefix=/usr \
--bindir=/bin \
--disable-static \
--sysconfdir=/etc \
--docdir=/usr/share/doc/attr-2.4.48
编译软件包:
make
测试需要在支持扩展属性的文件系统上运行,例如 ext2、ext3 或者 ext4。如果同时运行多个测试则会导致测试失败(-j 选项大于 1)。输入命令检查结果:
make check
安装软件包:
make install
需要移动共享库到/lib,因此需要重建.so 中的/usr/lib 文件:
mv -v /usr/lib/libattr.so.* /lib
ln -sfv ../../lib/$(readlink /usr/lib/libattr.so) /usr/lib/libattr.so
退出并清理软件包:
cd ..
rm -rf attr-2.4.48
- 安装 acl:访问控制列表 大致构建用时:少于 0.1 SBU 所需磁盘空间: 6.4 MB 解压并进入软件包:
tar -xf acl-2.2.53.tar.gz
cd acl-2.2.53
准备编译 Acl:
./configure --prefix=/usr \
--bindir=/bin \
--disable-static \
--libexecdir=/usr/lib \
--docdir=/usr/share/doc/acl-2.2.53
编译软件包:
make
在用 Acl 库构建 Coreutils 后,Acl 测试才能在支持访问控制的文件系统上运行。如果需要的话,可以在本章后面构建 Coreutils 之后回到这个软件包,并运行 make check 进行测试。 安装软件包:
make install
需要移动共享库到/lib,因此需要重建/usr/lib 中的.so 文件:
mv -v /usr/lib/libacl.so.* /lib
ln -sfv ../../lib/$(readlink /usr/lib/libacl.so) /usr/lib/libacl.so
退出并清理软件包:
cd ..
rm -rf acl-2.2.53
6. 安装 libcap
用于兼容 POSIX 1003.1e 的库。
- 大致构建用时:少于 0.1 SBU
- 所需磁盘空间: 11 MB 解压并进入软件包:
tar -xf libcap-2.42.tar.xz
cd libcap-2.42
防止安装静态库:
sed -i '/install -m.*STACAPLIBNAME/d' libcap/Makefile
编译软件包:
make lib=lib
用以下命令测试结果:
make test
安装软件包:
make lib=lib PKGCONFIGDIR=/usr/lib/pkgconfig install
chmod -v 755 /lib/libcap.so.2.42
mv -v /lib/libpsx.a /usr/lib
rm -v /lib/libcap.so
ln -sfv ../../lib/libcap.so.2 /usr/lib/libcap.so
退出并清理软件包:
cd ..
rm -rf libcap-2.42
7. 安装 Shadow:安全的密码管理程序
- 大致构建用时: 0.2 SBU
- 所需磁盘空间: 45 MB 解压并进入软件包:
tar -xf shadow-4.8.1.tar.xz
cd shadow-4.8.1
由于 Coreutils 已经提供了更好的版本,因此禁用对 groups 程序以及相应 man 手册的安装。同时避免安装第 4.2.2 节安装过的手册页:
sed -i 's/groups$(EXEEXT) //' src/Makefile.in
find man -name Makefile.in -exec sed -i 's/groups\.1 / /' {} \;
find man -name Makefile.in -exec sed -i 's/getspnam\.3 / /' {} \;
find man -name Makefile.in -exec sed -i 's/passwd\.5 / /' {} \;
比起默认的 crypt 方法,用更安全的 SHA-512 方法加密密码,它允许密码长度超过 8 个字符。也需要把 Shadow 默认使用的用户邮箱由陈旧的/var/spool/mail 位置改为正在使用的/var/mail 位置:
sed -e 's:#ENCRYPT_METHOD DES:ENCRYPT_METHOD SHA512:' \
-e 's:/var/spool/mail:/var/mail:' \
-i etc/login.defs
做一个小改动,用 useradd 1000 生成第一个组号:
sed -i 's/1000/999/' etc/useradd
准备编译 Shadow:
touch /usr/bin/passwd
./configure --sysconfdir=/etc \
--with-group-name-max-length=32
在上面的代码中,通过--with-group-name-max-length=32 将用户名和组名的长度限制在 32 个字符以下。 编译软件包:
make
安装软件包:
make install
8. 配置 Shadow
该软件包包含:① 增加、更改以及删除用户和组的工具;② 设置和修改密码;③ 执行其他特权级任务。软件包解压后的 doc/HOWTO 文件有关于 password shadowing 的完整解释。如果使用 Shadow 支持,记住需要验证密码(显示管理器、FTP 程序、pop3 守护进程等)的程序必须和 Shadow 兼容。也就是说,它们要能使用 Shadow 加密的密码。 运行下面的命令启用 Shadow 密码:
pwconv
运行下面的命令启用 shadow 组密码:
grpconv
设置 root 密码:
passwd root
退出并清理软件包:
cd ..
rm -rf shadow-4.8.1
9. 安装 gcc
- 大致构建用时: 102 SBU (包含测试)
- 所需磁盘空间: 4.6 GB 解压并进入软件包:
tar -xf gcc-10.2.0.tar.xz
cd gcc-10.2.0
如果是在 x86_64 上实施构建,更改 64 位库的默认目录名为 lib:
case $(uname -m) in
x86_64)
sed -e '/m64=/s/lib64/lib/' \
-i.orig gcc/config/i386/t-linux64
;;
esac
按照 gcc 的文档建议,创建一个专门用于编译目录:
mkdir -v build
cd build
准备编译 GCC:
../configure --prefix=/usr \
LD=ld \
--enable-languages=c,c++ \
--disable-multilib \
--disable-bootstrap \
--with-system-zlib
注意,对于其他的编程语言,现在还有一些前提条件没有准备好。可以查看 BLFS 手册了解如何编译 gcc 支持的所有语言的指令。
配置选项的含义,如表 4-7 所示。
| 配置选项 | 含义 |
|---|---|
| LD=ld | 指定使用第 4 章中安装的 ld,而非交叉编译中构建的 ld |
| --with-system-zlib | 告诉 GCC 链接系统安装的 Zlib 库,而不是它内部自带的库 |
编译软件包:
make
注意:gcc 的测试套件至关重要,任何情况下都不能跳过。 gcc 测试套件中一个测试集的会耗尽堆空间,因此运行测试之前要增加堆大小:
ulimit -s 32768
以非特权用户测试编译结果,不要因为出现错误就停下来:
chown -Rv tester .
su tester -c "PATH=$PATH make -k check"
要查看测试套件结果的概要,运行以下命令:
../contrib/test_summary
如果仅查看摘要,则使用管道 grep -A7 Summ 过滤输出内容:
../contrib/test_summary | grep -A7 Summ
以上的输出结果可以和 http://www.linuxfromscratch.org/lfs/build-logs/10.0/以及 http://gcc.gnu.org/ml/gcc-testresults/上的数据相比较,查看输出的差异。
已知与 get_time 相关的 6 个测试失败。这些显然与 en_HK 语言环境有关。
两个 experimental/net 的测试 lookup.cc 和 reverse.cc 已知会在 LFS chroot 环境失败,因为它们需要/etc /hosts 和 iana-etc。
两个测试 pr57193.c 和 pr90178.c 已知会失败。
一些意料之外的错误总是难以避免。gcc 开发者通常会意识到这些问题,但还没有解决。除非测试结果和上面 URL 中的相差很大,不然就可以安全继续。
在某些内核配置和 AMD 处理器组合的时候,肯能会在 gcc.target/i386/mpx 测试(旨在测试最新英特尔处理器上的 MPX 选项)中出现 1100 个失败。AMD 处理器的话可以安心的忽略这些。
安装软件包并删除不需要的目录:
make install
rm -rf /usr/lib/gcc/$(gcc -dumpmachine)/10.2.0/include-fixed/bits/
GCC 的构建目录现在属于 nobody,安装头文件(及其内容)目录的所有权不正确,因此需要将所有权更改为 root 用户和组:
chown -v -R root:root \
/usr/lib/gcc/*linux-gnu/10.2.0/include{,-fixed}
因为「历史」原因而需要创建 FHS 的软链接。
ln -sv ../usr/bin/cpp /lib
很多软件包用命令 cc 调用 C 编译器。为了满足这些软件包,创建一个符号链接:
ln -sv gcc /usr/bin/cc
增加一个兼容符号链接,启用编译程序时进行链接时间优化(Link Time Optimization,LTO):
install -v -dm755 /usr/lib/bfd-plugins
ln -sfv ../../libexec/gcc/$(gcc -dumpmachine)/8.2.0/liblto_plugin.so \
/usr/lib/bfd-plugins/
现在最终的工具链已经准备就绪了,再一次确认编译和链接都能正常工作,这很重要。我们通过做和前面章节做过的相同的完整性检查做到这点:
echo 'int main(){}' > dummy.c
cc dummy.c -v -Wl,--verbose &> dummy.log
readelf -l a.out | grep ': /lib'
如果没有任何错误,上条命令的输出(不同的平台上的动态链接器可能名字不同)应该如下:
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
现在确保已经设置好了启动文件:
grep -o '/usr/lib.*/crt[1in].*succeeded' dummy.log
上一条命令的输出应该是:
/usr/lib/gcc/x86_64-pc-linux-gnu/10.2.0/../../../../lib/crt1.o succeeded
/usr/lib/gcc/x86_64-pc-linux-gnu/10.2.0/../../../../lib/crti.o succeeded
/usr/lib/gcc/x86_64-pc-linux-gnu/10.2.0/../../../../lib/crtn.o succeeded
上面的结果可能有稍微不同,这取决于你使用的机器的架构,差异通常在于/usr/lib/gcc 后目录的名称。注意,gcc 能在/usr/lib 目录下找到所有的 3 个 crt*.o 文件。 确保链接器能找到正确的头文件:
grep -B4 '^ /usr/include' dummy.log
这条命令应该返回如下输出:
#include <...> search starts here:
/usr/lib/gcc/x86_64-pc-linux-gnu/10.2.0/include
/usr/local/include
/usr/lib/gcc/x86_64-pc-linux-gnu/10.2.0/include-fixed
/usr/include
同时,注意目标系统三段式后面的目录名称可能和上面的不同,这取决于你的架构。 接下来确认新的链接器已经在使用正确的搜索路径:
grep 'SEARCH.*/usr/lib' dummy.log |sed 's|; |\n|g'
应该忽略指向带有'-linux-gnu'的路径,上条命令的输出应该是:
SEARCH_DIR("/usr/x86_64-pc-linux-gnu/lib64")
SEARCH_DIR("/usr/local/lib64")
SEARCH_DIR("/lib64")
SEARCH_DIR("/usr/lib64")
SEARCH_DIR("/usr/x86_64-pc-linux-gnu/lib")
SEARCH_DIR("/usr/local/lib")
SEARCH_DIR("/lib")
SEARCH_DIR("/usr/lib");
32 位的系统可能有一些不同的目录。例如,下面是一台 i686 机器的输出:
SEARCH_DIR("/usr/i686-pc-linux-gnu/lib32")
SEARCH_DIR("/usr/local/lib32")
SEARCH_DIR("/lib32")
SEARCH_DIR("/usr/lib32")
SEARCH_DIR("/usr/i686-pc-linux-gnu/lib")
SEARCH_DIR("/usr/local/lib")
SEARCH_DIR("/lib")
SEARCH_DIR("/usr/lib");
然后要确定我们使用的是正确的 libc:
grep "/lib.*/libc.so.6 " dummy.log
上条命令的输出应该为:
attempt to open /lib/libc.so.6 succeeded
最后,确保 GCC 使用的是正确的动态链接器:
grep found dummy.log
上条命令的结果应该是(不同的平台上链接器名字可以不同):
found ld-linux-x86-64.so.2 at /lib/ld-linux-x86-64.so.2
如果显示的结果不一样或者根本没有显示,那就出了大问题。检查并回溯之前的步骤,找到出错的地方并改正。最有可能的原因是参数文件的调整出了问题。在进行下一步之前所有的问题都要解决。 移动放错位置的参数文件:
mkdir -pv /usr/share/gdb/auto-load/usr/lib
mv -v /usr/lib/*gdb.py /usr/share/gdb/auto-load/usr/lib
退出并清理软件包:
cd ../..
rm -rf gcc-10.2.0
4.1.5 bash
1. 安装 pkg-config:编译器选择
- 大致构建用时: 0.3 SBU
- 所需磁盘空间: 30 MB 解压并进入软件包:
tar -xf pkg-config-0.29.2.tar.gz
cd pkg-config-0.29.2
准备编译 Pkg-config:
./configure --prefix=/usr \
--with-internal-glib \
--disable-host-tool \
--docdir=/usr/share/doc/pkg-config-0.29.2
配置选项的含义如表 4-8 所示。
| 配置选项 | 含义 |
|---|---|
| --with-internal-glib | 让 pkg-config 使用它自己内部版本的 Glib,因为在 LFS 中没有可用的外部版本 |
| --disable-host-tool | 此选项取消创建到 pkg-config 程序的不必要的硬链接 |
编译软件包:
make
用以下命令检查结果:
make check
安装软件包:
make install
退出并清理软件包:
cd ..
rm -rf pkg-config-0.29.2
2. 安装 Ncurses:字符终端处理库
- 大致构建用时: 0.4 SBU
- 所需磁盘空间: 33 MB 解压并进入软件包:
tar -xf ncurses-6.2.tar.gz
cd ncurses-6.2
不要安装静态库,它不受配置控制:
sed -i '/LIBTOOL_INSTALL/d' c++/Makefile.in
准备编译 Ncurses:
./configure --prefix=/usr \
--mandir=/usr/share/man \
--with-shared \
--without-debug \
--without-normal \
--enable-pc-files \
--enable-widec
配置选项的含义,如表 4-9 所示。
| 配置选项 | 含义 |
|---|---|
| --enable-widec | 编译宽字符库(例如 libncursesw.so.6.1)来代替普通字符库(例如 libncurses.so.6.1)。宽字符库可用于多字节和传统 8 位本地字符,而常规的库只能用于 8 位本地字符。宽字符库和常规的库是源文件兼容的,而不是二进制文件兼容的 |
| --enable-pc-files | 为 pkg-config 生成和安装.pc 文件 |
| --without-normal | 用于禁用构建和安装大多数的静态库 |
编译软件包:
make
该软件包有个测试套件,但只能在安装完软件包后运行。测试程序在 test/目录中。查看该目录中的 README 文件获取更详细信息。 安装软件包:
make install
移动共享库到期望的/lib 文件夹:
mv -v /usr/lib/libncursesw.so.6* /lib
由于库已经被移走了,符号链接指向了一个不存在的文件。重建符号链接:
ln -sfv ../../lib/$(readlink /usr/lib/libncursesw.so) /usr/lib/libncursesw.so
很多应用程序仍然希望编辑器能找到非宽字符的 Ncurses 库,比如通过符号链接和链接器脚本欺骗应用程序链接到宽字符库:
for lib in ncurses form panel menu ; do
rm -vf /usr/lib/lib${lib}.so
echo "INPUT(-l${lib}w)" > /usr/lib/lib${lib}.so
ln -sfv ${lib}w.pc /usr/lib/pkgconfig/${lib}.pc
done
确保在编译时会查找-lcurses 的旧应用程序仍然可以编译:
rm -vf /usr/lib/libcursesw.so
echo "INPUT(-lncursesw)" > /usr/lib/libcursesw.so
ln -sfv libncurses.so /usr/lib/libcurses.so
如果需要的话,安装 Ncurses 的帮助文档:
mkdir -v /usr/share/doc/ncurses-6.2
cp -v -R doc/* /usr/share/doc/ncurses-6.2
上面的指令并不会创建非宽字符 Ncurses 库,因为没有从源文件中编译安装的软件包会在运行时再链接它们。然而,已知的仅有二进制应用程序并能链接到非等宽字符的库,需要第 5 版的支持。如果你由于一些仅有二进制的应用程序或要和 LSB 兼容而必须要有这样的库,用下面的命令重新编译软件包:
make distclean
./configure --prefix=/usr \
--with-shared \
--without-normal \
--without-debug \
--without-cxx-binding \
--with-abi-version=5
make sources libs
cp -av lib/lib*.so.5* /usr/lib
退出并清理软件包:
cd ..
rm -rf ncurses-6.2
3. 安装 Sed:流编辑器
- 大致构建用时: 0.3 SBU
- 所需磁盘空间: 32 MB 解压并进入软件包:
tar -xf sed-4.8
cd sed-4.8
准备编译 Sed:
./configure --prefix=/usr --bindir=/bin
编译软件包并生成 HTML 文档:
make
make html
用 tester 用户运行测试:
chown -Rv tester .
su tester -c "PATH=$PATH make check"v
安装软件包和它的文档:
make install
install -d -m755 /usr/share/doc/sed-4.8
install -m644 doc/sed.html /usr/share/doc/sed-4.8
退出并清理软件包:
cd ..
rm -rf sed-4.8
4. 安装 Psmisc:用于列出运行进程的信息
- 大致构建用时:少于 0.1 SBU
- 所需磁盘空间: 4.8 MB 解压并进入软件包:
tar -xf psmisc-23.3.tar.xz
cd psmisc-23.3
准备编译 Psmisc:
./configure --prefix=/usr
编译软件包:
make
该软件包没有测试套件。 安装软件包:
make install
将程序 killall 和 fuser 移动到 FHS 指定的位置:
mv -v /usr/bin/fuser /bin
mv -v /usr/bin/killall /bin
退出并清理软件包:
cd ..
rm -rf psmisc-23.3
5. 安装 Gettext:国际化和本地化(i18n)系统
- 大致构建用时: 3.2 SBU
- 所需磁盘空间: 240MB 解压并进入软件包:
tar -xf gettext-0.21.tar.xz
cd gettext-0.21
准备编译 Gettext:
./configure --prefix=/usr \
--disable-static \
--docdir=/usr/share/doc/gettext-0.21
编译软件包:
make
用以下命令测试结果(需要较长一段时间,大概 3 SBUs):
make check
安装软件包:
make install
chmod -v 0755 /usr/lib/preloadable_libintl.so
退出并清理软件包:
cd ..
rm -rf gettext-0.20.1
6. 安装 Bison:语法生成器
- 大致构建用时: 6.7 SBU
- 所需磁盘空间: 54 MB 解压并进入软件包:
tar -xf bison-3.7.1.tar.xz
cd bison-3.7.1
准备编译 Bison:
./configure --prefix=/usr --docdir=/usr/share/doc/bison-3.3.2
编译该软件包:
make
考虑到 Bison 和 Flex 的检查有循环依赖。如有需要,在下一节安装 Flex 之后,可以使用 make check 命令重新编译并检查 Bison 软件包。 测试可能会花费(大约 5.5 SBU):
make check
安装软件包:
make install
退出并清理软件包:
cd ..
rm -rf bison-3.7.1
7. 安装 Grep:搜索行的纯文本数据集
- 大致构建用时: 0.9 SBU
- 所需磁盘空间: 37 MB 解压并进入软件包:
tar -xf grep-3.4.tar.xz
cd grep-3.4
准备编译 Grep :
./configure --prefix=/usr --bindir=/bin
编译软件包:
make
用以下命令测试结果:
make -k check
安装软件包:
make install
退出并清理软件包:
cd ..
rm -rf grep-3.4
8. 安装 Bash:最常用的 shell
- 大致构建用时: 2.4 SBU
- 所需磁盘空间: 48 MB 解压并进入软件包:
tar -xf bash-5.0.tar.gz
cd bash-5.0
首先合并上游的修复:
patch -Np1 -i ../bash-5.0-upstream_fixes-1.patch
准备编译 Bash:
./configure --prefix=/usr \
--docdir=/usr/share/doc/bash-5.0 \
--without-bash-malloc \
--with-installed-readline
其中的新选项--with-installed-readline 用于告知 Bash 使用系统中已经安装的 readline 库,而不是使用自带的 readline 版本。 编译软件包:
make
如果不需要运行测试套件的话跳转到“安装软件包”。 准备测试,确保 tester 用户可以写源文件树:
chown -Rv tester .
现在,以 nobody 用户身份运行测试:
su tester << EOF
PATH=$PATH make tests < $(tty)
EOF
安装软件包并将主要的可执行文件移动至/bin:
make install
mv -vf /usr/bin/bash /bin
运行新编译的 Bash(替换正在运行的那个):
exec /bin/bash --login +h
参数使 bash 进程成为一个可交互的登录 shell 并停用散列使得新程序可用的时候就能发现。 退出并清理软件包:
cd ..
rm -rf bash-5.0
4.1.5 PERL 和 Python
1. 安装 Libtool:创建可移植编译库的工具
- 大致构建用时: 1.5 SBU
- 所需磁盘空间: 43 MB 解压并进入软件包:
tar -xf libtool-2.4.6.tar.xz
cd libtool-2.4.6
准备编译 Libtool:
./configure --prefix=/usr
编译软件包:
make
用以下命令测试结果:
make check
在具有多个内核的系统上,libtool 的测试时间可以显著削减。为此,请在上面那行命令中添加 TESTSUITEFLAGS=-j
make install
退出并清理软件包:
cd ..
rm -rf libtool-2.4.6
2. 安装 GDBM:数据库功能库
- 大致构建用时: 0.1 SBU
- 所需磁盘空间: 11 MB 解压并进入软件包:
tar -xf gdbm-1.18.1.tar.gz
cd gdbm-1.18.1
首先,修复 gcc 10 带来的问题:
sed -r -i '/^char.*parseopt_program_(doc|args)/d' src/parseopt.c
准备编译 GDBM:
./configure --prefix=/usr \
--disable-static \
--enable-libgdbm-compat
新选项--enable-libgdbm-compat 用于启用 libgdbm 兼容性库,因为一些 LFS 之外的软件包可能需要它提供的旧的 DBM 例程。 编译软件包:
make
用以下命令测试结果:
make check
安装软件包:
make install
退出并清理软件包:
cd ..
rm -rf gdbm-1.18.1
3. 安装 Gperf:完美哈希函数生成器
- 大致构建用时:少于 0.1 SBU
- 所需磁盘空间: 6.4 MB 解压并进入软件包:
tar -xf gperf-3.1.tar.gz
cd gperf-3.1
准备编译 Gperf:
./configure --prefix=/usr --docdir=/usr/share/doc/gperf-3.1
编译软件包:
make
该测试已知在运行多任务同时测试(即-j 选项大于 1)时会失败。用以下命令测试结果:
make -j1 check
安装软件包:
make install
退出并清理软件包:
cd ..
rm -rf gperf-3.1
4. 安装 Expat:面向流的 XML 1.0 解析器库
- 大致构建用时: 0.1 SBU
- 所需磁盘空间: 14 MB 解压并进入软件包:
tar -xf expat-2.2.9.tar.bz2
cd expat-2.2.9
准备编译 Expat:
./configure --prefix=/usr \
--disable-static \
--docdir=/usr/share/doc/expat-2.2.9
编译软件包:
make
用以下命令测试结果:
make check
安装软件包:
make install
如果需要的话,安装帮助文档:
install -v -m644 doc/*.{html,png,css} /usr/share/doc/expat-2.2.9
退出并清理软件包:
cd ..
rm -rf expat-2.2.9
5. 安装 Inetutils:GNU 网络实用程序
- 大致构建用时: 0.3 SBU
- 所需磁盘空间: 29 MB 解压并进入软件包:
tar -xf inetutils-1.9.4.tar.xz
cd inetutils-1.9.4
准备编译 Inetutils:
./configure --prefix=/usr \
--localstatedir=/var \
--disable-logger \
--disable-whois \
--disable-rcp \
--disable-rexec \
--disable-rlogin \
--disable-rsh \
--disable-servers
配置选项的含义,如表 4-11 所示。
| 配置选项 | 含义 |
|---|---|
| --disable-logger | 防止 Inetutils 安装 logger 程序,脚本使用该程序传递消息到系统日志守护进程。因为 Util-linux 安装了一个更新版本,因此不能安装这个 |
| --disable-whois | 禁用编译过时的 Inetutils whois 客户端。BLFS 指南中有更好的 whois 客户端说明 |
| --disable-r* | 为了安全,使编译过时的程序不能被使用。提供该功能的程序在手册 BLFS 中的 openssh 会有所提及 |
| --disable-servers | 禁用安装作为 Inetutils 软件包一部分的多种网络服务程序。这些服务程序被认为不适用于基础的 LFS 系统。其中的一些本来就不安全,或者说仅在可信网络中才被认为安全。注意这些服务程序有更好的可用替代品 |
编译软件包:
make
用以下命令测试结果:
make check
测试 libls.sh 可能会在初始的 chroot 环境中失败,但是在 LFS 系统构建完成后重新运行就会通过了。测试 ping-localhost.sh 会因为宿主系统不支持 IPv6 而失败。 安装软件包:
make install
移动一些程序使得/usr 在不可访问时仍保持可用:
mv -v /usr/bin/{hostname,ping,ping6,traceroute} /bin
mv -v /usr/bin/ifconfig /sbin
退出并清理软件包:
cd ..
rm -rf inetutils-1.9.4
6. 安装 PERL:实用的提取和报告语言
- 大致构建用时: 11 SBU
- 所需磁盘空间: 222 MB 解压并进入软件包:
tar -xf perl-5.32.0.tar.xz
cd perl-5.32.0
该版本的 Perl 会编译 Compress::Raw::Zlib 和 Compress::Raw::BZip2 模块。Perl 默认会使用内部的源码用于构建。用以下的命令使 Perl 使用系统中已安装的库:
export BUILD_ZLIB=False
export BUILD_BZIP2=0
为了能完全控制 Perl 的设置,你可以在下面的命令中移除【-des】选项并手动设置编译该软件包的方式。相应的,用下面的命令来使用 Perl 自动检测到的默认值:
sh Configure -des \
-Dprefix=/usr \
-Dvendorprefix=/usr \
-Dprivlib=/usr/lib/perl5/5.32/core_perl \
-Darchlib=/usr/lib/perl5/5.32/core_perl \
-Dsitelib=/usr/lib/perl5/5.32/site_perl \
-Dsitearch=/usr/lib/perl5/5.32/site_perl \
-Dvendorlib=/usr/lib/perl5/5.32/vendor_perl \
-Dvendorarch=/usr/lib/perl5/5.32/vendor_perl \
-Dman1dir=/usr/share/man/man1 \
-Dman3dir=/usr/share/man/man3 \
-Dpager="/usr/bin/less -isR" \
-Duseshrplib \
-Dusethreads
配置选项的含义:
| 配置选项 | 含义 |
|---|---|
| -Dvendorprefix=/usr | 这能确保 perl 知道,该如何告知软件包应该将它们的 perl 模块安装在哪里 |
| -Dpager="/usr/bin/less -isR" | 这能确保使用的是 less 而非 more |
| -Dman1dir=/usr/share/man/man1 -Dman3dir=/usr/share/man/man3 | 由于 Groff 还没有安装,Configure 会认为我们不希望为 Perl 安装 man 手册。用这些参数更改这个判断 |
| -Duseshrplib | 构建某些 perl 模块需要的共享 libperl |
| -Dusethreads | 构建支持线程的 perl |
编译软件包:
make
用以下命令测试结果(大概 11 SBU):
make test
安装软件包并清理:
make install
unset BUILD_ZLIB BUILD_BZIP2
退出并清理软件包:
cd ..
rm -rf perl-5.32.0
7. 安装 XML::Parser:解析 XML 文件的 Perl 模块
- 大致构建用时:少于 0.1 SBU
- 所需磁盘空间: 2.4 MB 解压并进入软件包:
tar -xf XML-Parser-2.46.tar.gz
cd XML-Parser-2.46
准备编译 XML::Parser:
perl Makefile.PL
编译软件包:
make
用以下命令测试结果:
make test
安装软件包:
make install
退出并清理软件包:
cd ..
rm -rf XML-Parser-2.46
8. 安装 Intltool:集中翻译工具
- 大致构建用时:少于 0.1 SBU
- 所需磁盘空间: 1.5 MB
解压并进入软件包:
tar -xf intltool-0.51.0.tar.gz
cd intltool-0.51.0
修复 perl-5.22 和其后版本导致的警告:
sed -i 's:\\\${:\\\$\\{:' intltool-update.in
准备编译 Intltool:
./configure --prefix=/usr
编译软件包:
make
用以下命令测试结果:
make check
安装软件包:
make install
install -v -Dm644 doc/I18N-HOWTO /usr/share/doc/intltool-0.51.0/I18N-HOWTO
退出并清理软件包:
cd ..
rm -rf intltool-0.51.0
9. 安装 Autoconf:用于生成自动配置脚本的程序
- 大致构建用时:少于 0.1 SBU(包含测试大于 3.5 SBU)
- 所需磁盘空间: 79 MB
解压并进入软件包:
tar -xf autoconf-2.69.tar.xz
cd autoconf-2.69
修复一个 Perl 5.28 引入的问题。
sed '361 s/{/\\{/' -i bin/autoscan.in
准备编译 Autoconf:
./configure --prefix=/usr
编译软件包:
make
由于 bash-5 和 libtool-2.4.3 的原因,测试套件当前无法使用。如果要强行测试,输入:
make check
安装软件包:
make install
退出并清理软件包:
cd ..
rm -rf autoconf-2.69
10. 安装 Automake:生成 make 文件的程序
大致构建用时:少于 0.1 SBU(包含测试大于 9.6SBU)
所需磁盘空间: 108 MB
解压并进入软件包:
tar -xf automake-1.16.2.tar.xz
cd automake-1.16.2
解决可能导致测试失败的问题:
sed -i "s/''/etags/" t/tags-lisp-space.sh
准备编译 Automake:
./configure --prefix=/usr --docdir=/usr/share/doc/automake-1.16.2
编译软件包:
make
因为各个测试之间存在内部延时,故建议就算是在单核处理器的设备上,也使用-j4 编译选项加速测试过程。用以下命令测试结果:
make -j4 check
已知 LFS 环境中 subobj.sh 测试会失败。 安装软件包:
make install
退出并清理软件包:
cd ..
rm -rf automake-1.16.2
11. 安装 kmod:加载内核模块的库和工具
- 大致构建用时: 0.1 SBU
- 所需磁盘空间: 13 MB
解压并进入软件包:
tar -xf kmod-27.tar.xz
cd kmod-27
准备编译 kmod:
./configure --prefix=/usr \
--bindir=/bin \
--sysconfdir=/etc \
--with-rootlibdir=/lib \
--with-xz \
--with-zlib
配置选项的含义:
| 配置项目 | 描述 |
|---|---|
| --with-xz, --with-zlib | 使 Kmod 能处理压缩的内核模块。 |
| --with-rootlibdir=/lib | 确保和不同库相关的文件放置到正确的目录。 |
编译软件包:
make
这个软件包没有附带可在 LFS chroot 环境中的运行测试套件。你至少需要 git 程序并进行一些测试以保证不会在 git 仓库外运行。 安装软件包并创建符号链接使兼容 Module-Init-Tools(之前处理 Linux 内核模块的软件包):
make install
for target in depmod insmod lsmod modinfo modprobe rmmod; do
ln -sfv ../bin/kmod /sbin/$target
done
ln -sfv kmod /bin/lsmod
退出并清理软件包:
cd ..
rm -rf kmod-27
12. 安装 libelf:处理 ELF 文件的库
- 大致构建用时: 1.3 SBU
- 所需磁盘空间: 105 MB 解压并进入软件包:
tar -xf elfutils-0.180.tar.bz2
cd elfutils-0.180
libelf 是软件包 elfutils-0.180 中的一部分。使用 elfutils-0.180.tar.bz2 作为源码包。 编译 libelf 前的准备:
./configure --prefix=/usr --disable-debuginfod --libdir=/lib
编译软件包:
make
用以下命令测试结果:
make check
仅安装 libelf:
make -C libelf install
install -vm644 config/libelf.pc /usr/lib/pkgconfig
rm /lib/libelf.a
退出并清理软件包:
cd ..
rm -rf elfutils-0.180
13. 安装 libffi:外部功能接口库
- 大致构建用时: 2.0 SBU
- 所需磁盘空间: 10 MB 解压并进入软件包:
tar -xf libffi-3.3.tar.gz
cd libffi-3.3
Libffi 与 GMP 相似,构建时会根据使用的处理器优化。如果需要构建的是另一个系统,设定 CFLAGS 和 CXXFLAGS 为你的架构指定成通用构建。如果不这样做,所有指向 Libffi 的链接将触发非法操作错误。 修改 Makefile 将头文件安装到标准的 /usr/include 目录,而非 /usr/lib/libffi-3.2.1/include。
sed -e '/^includesdir/ s/$(libdir).*$/$(includedir)/' \
-i include/Makefile.in
sed -e '/^includedir/ s/=.*$/=@includedir@/' \
-e 's/^Cflags: -I${includedir}/Cflags:/' \
-i libffi.pc.in
编译 Libffi 前的准备:
./configure --prefix=/usr --disable-static --with-gcc-arch=native
配置选项的含义:
| 配置选项 | 含义 |
|---|---|
| --with-gcc-arch=native | 用于确保 GCC 为当前系统进行优化 。如果没有指定则会进行猜测,这样生成的代码在某些系统上是不正确的。如果生成的代码想要从本机系统复制到功能较弱的系统,则应该以功能较弱的系统作为参数。有关替代系统类型的详细信息,请参阅 gcc 手册中的 x86 选项 |
编译软件包:
make
用以下命令测试结果:
make check
安装软件包:
make install
退出并清理软件包:
cd ..
rm -rf libffi-3.3
14. 安装 OpenSSL:用于实施安全通讯的软件包
- 大致构建用时: 2.1 SBU
- 所需磁盘空间: 150MB 解压并进入软件包:
tar -xf openssl-1.1.1g.tar.gz
cd openssl-1.1.1g
编译 OpenSSL 前的准备:
./config --prefix=/usr \
--openssldir=/etc/ssl \
--libdir=lib \
shared \
zlib-dynamic
编译软件包:
make
查看测试结果,输入:
make test
安装软件包:
sed -i '/INSTALL_LIBS/s/libcrypto.a libssl.a//' Makefile
make MANSUFFIX=ssl install
如果有需要,安装文档:
mv -v /usr/share/doc/openssl /usr/share/doc/openssl-1.1.1g
cp -vfr doc/* /usr/share/doc/openssl-1.1.1g
退出并清理软件包:
cd ..
rm -rf openssl-1.1.1g
15. 安装 Python:广泛使用的高级编程语言
- 大致构建用时: 1.3 SBU
- 所需磁盘空间: 248 MB 解压并进入软件包:
tar -xf Python-3.8.5.tar.xz
cd Python-3.8.5
编译 Python 前的准备:
./configure --prefix=/usr \
--enable-shared \
--with-system-expat \
--with-system-ffi \
--with-ensurepip=yes
配置选项的含义如表 4-14 所示。
| 选项 | 用途 |
|---|---|
| --with-system-expat | 用于启用 Expat 系统版本的链接。 |
| --with-system-ffi | 用于启用 libffi 系统版本的链接。 |
| --with-ensurepip=yes | 用于启用 pip 和 setuptools 打包程序的构建。 |
编译软件包:
make
测试套件需要 TK 和 X Windows 会话,直至 BLFS 中重新安装 Python 3 之前都执行不了。 安装软件包:
make install
chmod -v 755 /usr/lib/libpython3.7m.so
chmod -v 755 /usr/lib/libpython3.so
ln -sfv pip3.8 /usr/bin/pip3
安装后的 chmod 等指令是用于修复库的权限问题,同其他库保持一致。
如果需要,安装预格式化好的文档:
install -v -dm755 /usr/share/doc/python-3.8.5/html
tar --strip-components=1 \
--no-same-owner \
--no-same-permissions \
-C /usr/share/doc/python-3.8.5/html \
-xvf ../python-3.8.5-docs-html.tar.bz2
--no-same-owner 和 --no-same-permissions 用于确保安装文件的归属和权限是正确的。没有的话,运行 tar 时会以上游创建者的身份安装软件包内的文件。 退出并清理软件包:
cd ..
rm -rf Python-3.8.5
4.1.6 最后亿点点
1. 安装 Ninja:专注于速度的小型构件系统
- 大致构建用时: 0.2 SBU
- 所需磁盘空间: 78 MB 解压并进入软件包:
tar -xf ninja-1.10.0.tar.gz
cd ninja-1.10.0
在运行时,ninja 通常会并行最大数量的进程。默认值是系统的核心数乘以二。有些时候会导致 CPU 过热,或者内存容量不足。如果是命令行运行,通过传递 -jN 参数可以限制并行的进程数,但是有些软件包虽然潜入了 ninja 的执行却不会传递 -j 参数。 通过使用下方可选过程,让用户能够通过环境变量 NINJAJOBS 来限制并行进程的数量。例如, 设定:
export NINJAJOBS=4
将限制 Ninja 最多仅 4 个进程并行。 如果需要,运行以下命令以添加使用环境变量 NINJAJOBS 的功能:
sed -i '/int Guess/a \
int j = 0;\
char* jobs = getenv( "NINJAJOBS" );\
if ( jobs != NULL ) j = atoi( jobs );\
if ( j > 0 ) return j;\
' src/ninja.cc
构建 Ninja:
python3 configure.py --bootstrap
--bootstrap 迫使 Ninja 重新构建自身以适应当前系统。 查看测试结果,输入:
./ninja ninja_test
./ninja_test --gtest_filter=-SubprocessTest.SetWithLots
安装软件包:
install -vm755 ninja /usr/bin/
install -vDm644 misc/bash-completion /usr/share/bash-completion/completions/ninja
install -vDm644 misc/zsh-completion /usr/share/zsh/site-functions/_ninja
退出并清理软件包:
cd ..
rm -rf ninja-1.10.0
- 安装 Meson:软件自动化构建系统
- 大致构建用时:少于 0.1 SBU
- 所需磁盘空间: 34 MB 解压并进入软件包:
tar -xf meson-0.55.1.tar.gz
cd meson-0.55.1
通过下列命令编译 Meson:
python3 setup.py build
这个软件包还没有测试套件。 安装软件包:
python3 setup.py install --root=dest
cp -rv dest/* /
默认情况下执行命令 python3 setup.py install 会将所有文件(比如 man 手册)安装到 Python Eggs。指定根路径后 setup.py 则会将这些文件安装到标准层次目录。然后只需要复制层次目录,文件即处于标准位置。 退出并清理软件包:
cd ..
rm -rf meson-0.55.1
3. 安装 Coreutils:GNU 核心工具组
- 大致构建用时: 2.88 SBU
- 所需磁盘空间: 158 MB 解压并进入软件包:
tar -xf coreutils-8.32.tar.xz
cd coreutils-8.32
POSIX 要求 Coreutils 中的程序即使在多字节语言环境也能正确识别字符边界。下面的补丁修复这个不兼容性以及其他一些和国际化相关的错误。
patch -Np1 -i ../coreutils-8.32-i18n-1.patch
之前在这个补丁中发现了很多错误。当向 Coreutils 维护者报告新错误的时候,请先检查没有该补丁是否可以重现该错误。 抑制测试在某些机器上可能出现的无线循环:
sed -i '/test.lock/s/^/#/' gnulib-tests/gnulib.mk
现在准备编译 Coreutils:
autoreconf -fiv
FORCE_UNSAFE_CONFIGURE=1 ./configure \
--prefix=/usr \
--enable-no-install-program=kill,uptime
配置选项的含义:
| 选项 | 用途 |
|---|---|
| autoreconf | 更新已有的配置文件,以使其和 automake 最新生成的一致。 |
| FORCE_UNSAFE_CONFIGURE=1 | 允许以 root 用户权限编译软件包。 |
| --enable-no-install-program=kill,uptime | 的目的是防止 Coreutils 安装其他软件包后面会安装的二进制包。 |
编译软件包:
make
如果不运行测试套件的话跳到「安装软件包」 现在可以运行测试套件了。首先,运行需要以 root 用户运行的测试:
make NON_ROOT_USERNAME=nobody check-root
我们会以 nobody 用户运行剩下的测试。但是,一些测试要求用户属于多个组。为了不跳过这些测试,我们会添加一个临时的组并添加用户 nobody 作为它的成员:
echo "dummy:x:1000:nobody" >> /etc/group
修复一些权限问题以便非 root 用户可以编译和运行测试:
chown -Rv nobody .
现在运行测试。确保 su 环境中的 PATH 环境变量包含了 /tools/bin。
su nobody -s /bin/bash \
-c "PATH=$PATH make RUN_EXPENSIVE_TESTS=yes check"
已知测试程序 test-getlogin 在部分构建的系统环境(如 chroot 环境)中会失败,章节结束后运行便会通过。已知测试程序 tty.sh 也会失败。
移除临时组:
sed -i '/dummy/d' /etc/group
安装软件包:
make install
移动程序到 FHS 指定的位置:
mv -v /usr/bin/{cat,chgrp,chmod,chown,cp,date,dd,df,echo} /bin
mv -v /usr/bin/{false,ln,ls,mkdir,mknod,mv,pwd,rm} /bin
mv -v /usr/bin/{rmdir,stty,sync,true,uname} /bin
mv -v /usr/bin/chroot /usr/sbin
mv -v /usr/share/man/man1/chroot.1 /usr/share/man/man8/chroot.8
sed -i s/\"1\"/\"8\"/1 /usr/share/man/man8/chroot.8
mv -v /usr/bin/{head,nice,sleep,touch} /bin
退出并清理软件包:
cd ..
rm -rf coreutils-8.32
4. 安装 check:单元测试框架
- 大致构建用时: 0.1 SBU(包含测试大于 3.0 SBU)
- 所需磁盘空间: 12 MB 解压并进入软件包:
tar -xf check-0.15.2.tar.gz
cd check-0.15.2
配置 Check 准备编译:
./configure --prefix=/usr
构建软件包:
make
现在编译完成了。运行 Check 的测试套件,输入以下命令:
make check
注意,Check 的测试套件可能会占用挺长(上至 4 SBU)的时间。 安装软件包,并修复脚本:
make docdir=/usr/share/doc/check-0.15.2 install
sed -i '1 s/tools/usr/' /usr/bin/checkmk
退出并清理软件包:
cd ..
rm -rf check-0.15.2
5. 安装 Diffutils:一组用于显示文本文件之间的差异实用程序
- 大致构建用时: 0.4 SBU
- 所需磁盘空间: 33 MB 解压并进入软件包:
tar -xf diffutils-3.7.tar.xz
cd diffutils-3.7
准备编译 Diffutils:
./configure --prefix=/usr
编译软件包:
make
用以下命令测试结果:
make check
安装软件包:
make install
退出并清理软件包:
cd ..
rm -rf diffutils-3.7
6. 安装 gawk:一种优良的文本处理工具
- 大致构建用时: 0.5 SBU
- 所需磁盘空间: 43 MB 解压并进入软件包:
tar -xf gawk-5.1.0.tar.xz
cd gawk-5.1.0
确保哪些不需要的文件没有被安装:
sed -i 's/extras//' Makefile.in
准备编译 Gawk:
./configure --prefix=/usr
编译软件包:
make
用以下命令测试结果:
make check
安装软件包:
make install
如果需要的话,安装帮助文档:
mkdir -v /usr/share/doc/gawk-5.1.0
cp -v doc/{awkforai.txt,*.{eps,pdf,jpg}} /usr/share/doc/ gawk-5.1.0
退出并清理软件包:
cd ..
rm -rf gawk-5.1.0
7. 安装 gindutils:GNU 操作系统的基本目录搜索实用程序
- 大致构建用时: 0.8 SBU
- 所需磁盘空间: 52 MB 解压并进入软件包:
tar -xf findutils-4.7.0.tar.gz
cd findutils-4.7.0
准备编译 Findutils:
./configure --prefix=/usr --localstatedir=/var/lib/locate
配置选项的含义: --localstatedir 用于改变 locate 数据库的位置为 FHS 兼容的 /var/lib/locate。 编译软件包:
make
用以下命令测试结果:
chown -Rv tester .
su tester -c "PATH=$PATH make check"
安装软件包:
make install
一些 BLFS 上的软件包希望 find 命令在/bin,因此确保位置正确:
mv -v /usr/bin/find /bin
sed -i 's|find:=${BINDIR}|find:=/bin|' /usr/bin/updatedb
退出并清理软件包:
cd ..
rm -rf findutils-4.7.0
8. 安装 Groff:文档格式化系统
- 大致构建用时: 0.5 SBU
- 所需磁盘空间: 96 MB 解压并进入软件包:
tar -xf groff-1.22.4.tar.gz
cd groff-1.22.4
Groff 希望环境变量 PAGE 包含默认的页面大小,对于美国的用户,为 PAGE=letter,对于其他地方,PAGE=A4 更合适。尽管在编译的时候配置了默认页面大小,后面通过 echo「A4」或「letter」到 /etc/papersize 文件仍然可以修改。 准备编译 Groff:
PAGE=A4 ./configure --prefix=/usr
该软件不支持并行构建。编译软件包:
make -j1
该软件包没有测试套具。 安装软件包:
make install
退出并清理软件包:
cd ..
rm -rf groff-1.22.4
9. 安装 GRUB:GNU 项目的启动引导程序
- 大致构建用时: 0.8 SBU
- 所需磁盘空间: 154 MB 解压并进入软件包:
tar -xf grub-2.04.tar.xz
cd grub-2.04
准备编译 GRUB:
./configure --prefix=/usr \
--sbindir=/sbin \
--sysconfdir=/etc \
--disable-efiemu \
--disable-werror
新配置选项的含义:
| 选项 | 含义 |
|---|---|
| --disable-werror | 允许忽视有更新 Flex 版本提示的警告以完成构建。 |
| --disable-efiemu | 这些选项通过停用 LFS 不需要的功能和测试程序最小化构建。 |
编译软件包:
make
该软件包没有测试套件。 安装软件包:
make install
mv -v /etc/bash_completion.d/grub /usr/share/bash-completion/completions
会在 5.2.2 节使用 GRUB 设置启动过程中介绍 GRUB 的用法,让你通过 GRUB 启动 LFS 系统 退出并清理软件包:
cd ..
rm -rf grub-2.04
10. 安装 less:显示分页器
- 大致构建用时:少于 0.1 SBU
- 所需磁盘空间: 4.1 MB 解压并进入软件包:
tar -xf less-551.tar.gz
cd less-551
准备编译 Less:
./configure --prefix=/usr --sysconfdir=/etc
配置选项的含义: --sysconfdir=/etc 用于告知软件包创建的程序在 /etc 中查找配置文件。 编译软件包:
make
该软件包没有测试套件。 安装软件包:
make install
退出并清理软件包:
cd ..
rm -rf less-551
11. 安装 gzip:GNU 文件压缩程序
- 大致构建用时: 0.1 SBU
- 所需磁盘空间: 20 MB 解压并进入软件包:
tar -xf gzip-1.10.tar.xz
cd gzip-1.10
准备编译 Gzip:
./configure --prefix=/usr
编译软件包:
make
用以下命令测试结果:
make check
已知有两个测试在 LFS 环境中会失败:help-version 和 zmore。 安装软件包:
make install
移动一些需要放在根文件系统的程序:
mv -v /usr/bin/gzip /bin
退出并清理软件包:
cd ..
rm -rf gzip-1.10
12. 安装 IPRoute2:用于控制和监视 Linux 内核中网络的各个方面
- 大致构建用时: 0.2 SBU
- 所需磁盘空间: 14 MB 解压并进入软件包:
tar -xf iproute2-5.8.0.tar.xz
cd iproute2-5.8.0
此软件包中包含的 arpd 因为依赖于 Berkeley DB,但是此软件并没有包含于 LFS 中,所以将不会进行编译。但是 arpd 的目录依旧会被安装。运行以下的命令来阻止这一动作。如果需要 arpd 的二进制文件,请查看 BLFS Book 的网页 http://www.linuxfromscratch.org/blfs/view/8.4/server/databases.html#db 以了解编译 Berkeley DB 都需要哪些指令。
sed -i /ARPD/d Makefile
rm -fv man/man8/arpd.8
此外,还需要禁用两个模块,它依赖于 http://www.linuxfromscratch.org/blfs/view/10.0/postlfs/iptables.html.
sed -i 's/.m_ipt.o//' tc/Makefile
编译软件包:
make
此软件包不包含可用的测试套件。 安装软件包:
make DOCDIR=/usr/share/doc/iproute2-5.8.0 install
退出并清理软件包:
cd ..
rm -rf iproute2-5.8.0
13. 安装 Kbd:Linux 控制台字体和键表的实用程序
- 大致构建用时: 0.2 SBU
- 所需磁盘空间: 32 MB 解压并进入软件包:
tar -xf kbd-2.3.0.tar.xz
cd kbd-2.3.0
在 Kbd 软件包中退格键(Backspace)和删除键(Delete)的行为和键映射并不一致。下面的补丁修复了 i386 键映射中的这个问题:
patch -Np1 -i ../kbd-2.3.0-backspace-1.patch
打补丁后,退格键生成编码为 127 的字符,删除键会生成一个著名的转义序列。 移除冗余的 resizecons 程序(它要求功能不全的 svglib 提供视频模式文件——用于正常使用 setfont 设置控制台字体大小)以及帮助手册。
sed -i 's/\(RESIZECONS_PROGS=\)yes/\1no/g' configure
sed -i 's/resizecons.8 //' docs/man/man8/Makefile.in
准备编译 Kbd:
./configure --prefix=/usr --disable-vlock
--disable-vlock 用于防止编译 vlock 工具,因为它要求 chroot 环境中不可用的 PAM 库。 编译软件包:
make
用以下命令测试结果:
make check
安装软件包:
make install
由于通常使用的 CP1251 键映射假设使用 ISO-8859-5 编码,Kbd 软件包不能为某些语言(例如,白俄罗斯)提供可用的键映射。使用这样的语言需要单独下载能工作的键映射。 如果需要的话,安装帮助文档:
mkdir -v /usr/share/doc/kbd-2.3.0
cp -R -v docs/doc/* /usr/share/doc/kbd-2.3.0
退出并清理软件包:
cd ..
rm -rf kbd-2.3.0
14. 安装 libpipeline:管理管道的库
- 大致构建用时: 0.2 SBU
- 所需磁盘空间: 9.3 MB 解压并进入软件包:
tar -xf libpipeline-1.5.3.tar.gz
cd libpipeline-1.5.3
准备编译 Libpipeline:
./configure --prefix=/usr
编译软件包:
make
用以下命令测试结果:
make check
安装软件包:
make install
退出并清理软件包:
cd ..
rm -rf libpipeline-1.5.3
15. 安装 make:生成可执行文件和其他非源码程序
- 大致构建用时: 0.6 SBU
- 所需磁盘空间: 13 MB 解压并进入软件包:
tar -xf make-4.3.tar.bz2
cd make-4.3
准备编译 make:
./configure --prefix=/usr
编译软件包:
make
测试套件需要知道支持 perl 文件的位置。我们使用一个环境变量来达成这个目的。用以下命令测试结果:
make check
安装软件包:
make install
退出并清理软件包:
cd ..
rm -rf make-4.3
16. 安装 patch:更新文本文件
- 大致构建用时: 0.2 SBU
- 所需磁盘空间: 12 MB 解压并进入软件包:
tar -xf patch-2.7.6.tar.xz
cd patch-2.7.6
准备编译 Patch:
./configure --prefix=/usr
编译软件包:
make
用以下命令测试结果:
make check
安装软件包:
make install
退出并清理软件包:
cd ..
rm -rf patch-2.7.6
17. 安装 man-db:参考手册的数据库和浏览器
- 大致构建用时: 0.5 SBU
- 所需磁盘空间: 40 MB 解压并进入软件包:
tar -xf man-db-2.9.3.tar.xz
cd man-db-2.9.3
准备编译 Man-DB: System
sed -i '/find/s@/usr@@' init/systemd/man-db.service.in
./configure --prefix=/usr \
--docdir=/usr/share/doc/man-db-2.9.3 \
--sysconfdir=/etc \
--disable-setuid \
--enable-cache-owner=bin \
--with-browser=/usr/bin/lynx \
--with-vgrind=/usr/bin/vgrind \
--with-grap=/usr/bin/grap
sysv
./configure --prefix=/usr \
--docdir=/usr/share/doc/man-db-2.9.3 \
--sysconfdir=/etc \
--disable-setuid \
--enable-cache-owner=bin \
--with-browser=/usr/bin/lynx \
--with-vgrind=/usr/bin/vgrind \
--with-grap=/usr/bin/grap \
--with-systemdtmpfilesdir= \
--with-systemdsystemunitdir=
配置选项的含义如表 4-17 所示。
| 选项 | 含义 |
|---|
sed -i '/find/s@/usr@@' init/systemd/man-db.service.in | 将硬编码路径更改为安装在 /bin 中的 find 实用程序
--disable-setuid | 为用户 man 禁止 man
--enable-cache-owner=bin | 用于将系统范围内的 cache 文件的拥有者设置为 bin 用户
--with-... | 这些参数用于设置一些默认程序。lynx 是一个基于文本的网络浏览器(查看 BLFS 获取安装指令),vgrind 将程序源码转换为 Groff 输入,grap 在 Groof 文档排版图中非常有用。查看手册页通常并不需要 vgrind 和 grap 程序。它们并不是 LFS 或 BLFS 的一部分,但是如果需要的话你自己应该能够在完成 LFS 之后安装它们。
编译软件包:
make
用以下命令测试结果:
make check
安装软件包:
make install
LFS 中的非英语手册页
ls /usr/share/man/
| 语言 (缩写) | 编码 | 语言 (缩写) | 编码 |
|---|---|---|---|
| Danish (da) | ISO-8859-1 | Croatian (hr) | ISO-8859-2 |
| German (de) | ISO-8859-1 | Hungarian (hu) | ISO-8859-2 |
| English (en) | ISO-8859-1 | Japanese (ja) | EUC-JP |
| Spanish (es) | ISO-8859-1 | Korean (ko) | EUC-KR |
| Estonian (et) | ISO-8859-1 | Lithuanian (lt) | ISO-8859-13 |
| Finnish (fi) | ISO-8859-1 | Latvian (lv) | ISO-8859-13 |
| French (fr) | ISO-8859-1 | Macedonian (mk) | ISO-8859-5 |
| Irish (ga) | ISO-8859-1 | Polish (pl) | ISO-8859-2 |
| Galician (gl) | ISO-8859-1 | Romanian (ro) | ISO-8859-2 |
| Indonesian (id) | ISO-8859-1 | Russian (ru) | KOI8-R |
| Icelandic (is) | ISO-8859-1 | Slovak (sk) | ISO-8859-2 |
| Italian (it) | ISO-8859-1 | Slovenian (sl) | ISO-8859-2 |
| Norwegian Bokmal (nb) | ISO-8859-1 | Serbian Latin (sr@latin) | ISO-8859-2 |
| Dutch (nl) | ISO-8859-1 | Serbian (sr) | ISO-8859-5 |
| Norwegian Nynorsk (nn) | ISO-8859-1 | Turkish (tr) | ISO-8859-9 |
| Norwegian (no) | ISO-8859-1 | Ukrainian (uk) | KOI8-U |
| Portuguese (pt) | ISO-8859-1 | Vietnamese (vi) | TCVN5712-1 |
| Swedish (sv) | ISO-8859-1 | Simplified Chinese (zh_CN) | GBK |
| Belarusian (be) | CP1251 | Simplified Chinese, Singapore (zh_SG) | GBK |
| Bulgarian (bg) | CP1251 | Traditional Chinese, Hong Kong (zh_HK) | BIG5HKSCS |
| Czech (cs) | ISO-8859-2 | Traditional Chinese (zh_TW) | BIG5 |
| Greek (el) | ISO-8859-7 |
退出并清理软件包:
cd ..
rm -rf man-db-2.9.3
- 安装 tar:tar 文件的创建和管理功能
- 大致构建用时: 2.0 SBU
- 所需磁盘空间: 39 MB 解压并进入软件包:
tar -xf tar-1.32.tar.xz
cd tar-1.32
准备编译 Tar:
FORCE_UNSAFE_CONFIGURE=1 \
./configure --prefix=/usr \
--bindir=/bin
FORCE_UNSAFE_CONFIGURE=1 强制以 root 用户运行 mknod 的测试。通常认为以 root 用户运行该测试是危险的,但由于是在部分构建的系统上运行,这样并没有问题。 编译软件包:
make
用以下命令测试结果(大概 3 SBU):
make check
安装软件包:
make install
make -C doc install-html docdir=/usr/share/doc/tar-1.31
退出并清理软件包:
cd ..
rm -rf tar-1.32
19. 安装 texinfo:排版语法
- 大致构建用时: 0.8 SBU
- 所需磁盘空间: 104 MB 解压并进入软件包:
tar -xf texinfo-6.7.tar.xz
cd texinfo-6.7
准备编译 Texinfo:
./configure --prefix=/usr --disable-static
配置选项的含义: 顶级的配置脚本会告诉你 --disable-static 是一个未能识别的选项,但是 XSParagraph 的配置脚本能够识别它,并能用其来禁用安装静态 XSParagraph.a 至 /usr/lib/texinfo 的操作。 编译软件包:
make
用以下命令测试结果:
make check
安装软件包:
make install
可选地安装 TeX 中的组件:
make TEXMF=/usr/share/texmf install-tex
此处的 makefile 变量 TEXMF 记录了作为 TeX 树的根位置,以待日后安装 TeX 软件包时候使用。 该信息文档系统使用一个纯文本文件来存放菜单条目清单。文件保存在 /usr/share/info/dir。不幸的是,由于不同软件包 Makefile 的偶然问题,有时候会和系统中安装的信息页不同步。如果需要重建 /usr/share/info/dir 文件,下面的可选命令能完成任务:
pushd /usr/share/info
rm -v dir
for f in *
do install-info $f dir 2>/dev/null
done
popd
退出并清理软件包:
cd ..
rm -rf texinfo-6.7
20. 安装 Vim:文本编辑器
- 大致构建用时: 2.2 SBU
- 所需磁盘空间: 201 MB 如果你钟情于其他的编辑器,比如 Emacs、Joe,或 Nano。请参考 http://www.linuxfromscratch.org/blfs/view/8.4/postlfs/editors.html 中的安装指导。 解压并进入软件包:
tar -xf vim-8.2.1361.tar.gz
cd vim-8.2.1361
把配置文件 vimrc 从默认位置移动到 /etc:
echo '#define SYS_VIMRC_FILE "/etc/vimrc"' >> src/feature.h
让 Vim 做好编译准备:
./configure --prefix=/usr
编译安装包:
make
为测试做准备,确保 tester 用户拥有源码目录的写权限:
chown -Rv tester .
现在用用户 nobody 执行测试:
su nobody -s /bin/bash -c "LANG=en_US.UTF-8 make -j1 test" &> vim-test.log
这个测试套件会输出一堆二进制数据到屏幕上。这会导致当前设置下的终端出现问题。把输出重定向到一个日志文件就可以解决这个问题。测试成功的话就会输出【ALL DONE】]。 安装软件包:
make install
许多用户习惯于使用 vi 而不是 vim。为了当人们在习惯性的输入 vi 时能执行 vim,需要给二进制文件和 man 页建立符号连接:
ln -sv vim /usr/bin/vi
for L in /usr/share/man/{,*/}man1/vim.1; do
ln -sv vim.1 $(dirname $L)/vi.1
done
默认情况下,Vim 的说明文档被安装在/usr/share/vim 里。下面的这个符号链接使得可以通过/usr/share/doc/vim-8.1 访问该文档,让它的位置与其他软件包的文档位置保持一致:
ln -sv ../vim/vim81/doc /usr/share/doc/vim-8.2.1361
如果要把一个 X Window 系统安装在 LFS 系统上,可能得在安装完 X 系统后再重新编译 Vim。Vim 带有一个 GUI 版本,这个版本需要安装 X 和一些额外的库。想了解更多信息,请参考 Vim 文档和 BLFS http://www.linuxfromscratch.org/blfs/view/8.4/postlfs/vim.html 中 Vim 安装指导页。
21. 配置 Vim
默认情况下,vim 使用的是不兼容 vi 的模式。这对于使用其他编辑器的用户这个问题可能十分新颖。下面列出的设置中,【nocompatible】尤为突出排在第一位,由此提醒着那些想换成【compatible】模式的用户。这个设置尤为重要,因为它会影响其他的设置,而且必须在这个设置之后进行覆盖设置。 让我们先创建一个默认的 vim 配置文件:
cat > /etc/vimrc << "EOF"
" Begin /etc/vimrc
" Ensure defaults are set before customizing settings, not after
source $VIMRUNTIME/defaults.vim
let skip_defaults_vim=1
set nocompatible
set backspace=2
set mouse=
syntax on
if (&term == "xterm") || (&term == "putty")
set background=dark
endif
" End /etc/vimrc
EOF
设置 set nocompatible 让 vim 比 vi 兼容模式更有用。删掉「no」以保留旧的 vi 特性。设置 set backspace=2 让退格跨越换行、自动缩进和插入的开始。syntax on 参数使 vim 能高亮显示语法。设置 set mouse 让你能在 chroot 和远程连接的时候用鼠标粘帖文本。
最后,带有 set background=dark 的 if 语句矫正了 vim 对于某些终端模拟器的背景颜色的估算。这让某些写在黑色背景上的程序的高亮色能有更好的调色方案。
用下面的命令可以获得其他选项的文档:
vim -c ':options'
默认情况下,Vim 只安装了英文的拼写检查文件。要想安装你想要的语言的拼写检查文件,请从 ftp://ftp.vim.org/pub/vim/runtime/spell/ 下载你所用语言的 *.spl 文件,可下可不下的 *.sug 文件和文字编码。并把它们保存到 /usr/share/vim/vim81/spell/。
要使用这些文件,需要设置 /etc/vimrc 里的某些项,例如:
set spelllang=en,ru
set spell
想要了解更多信息,请阅读上方 URL 里对应 README 文件。 退出并清理软件包:
cd ..
rm -rf vim-8.2.1361
Systemd 分支部分
1 安装 systemd
- 大致构建用时: 2.0 SBU
- 所需磁盘空间: 262 MB 解压并进入软件包:
tar -xf systemd-246.tar.gz
cd systemd-246
创建符号链接以解决丢失 xsltproc 的问题:
ln -sf /tools/bin/true /usr/bin/xsltproc
由于我们还未安装 Util-Linux 的最终版本,所以在适当的位置创建指向库的链接:
for file in /tools/lib/lib{blkid,mount,uuid}.so*; do
ln -sf $file /usr/lib/
done
设置 man 手册:
tar -xf ../systemd-man-pages-240.tar.xz
移除不能在 chroot 下构建的测试:
sed '177,$ d' -i src/resolve/meson.build
从默认的 udev 规则中删除没有必要的组 render:
sed -i 's/GROUP="render", //' rules/50-udev-default.rules.in
systemd 编译前准备:
mkdir -p build
cd build
PKG_CONFIG_PATH="/usr/lib/pkgconfig:/tools/lib/pkgconfig" \
LANG=en_US.UTF-8 \
CFLAGS+="-Wno-format-overflow" \
meson --prefix=/usr \
--sysconfdir=/etc \
--localstatedir=/var \
-Dblkid=true \
-Dbuildtype=release \
-Ddefault-dnssec=no \
-Dfirstboot=false \
-Dinstall-tests=false \
-Dkmod-path=/bin/kmod \
-Dldconfig=false \
-Dmount-path=/bin/mount \
-Drootprefix= \
-Drootlibdir=/lib \
-Dsplit-usr=true \
-Dsulogin-path=/sbin/sulogin \
-Dsysusers=false \
-Dumount-path=/bin/umount \
-Db_lto=false \
-Drpmmacrosdir=no \
..
选项的含义:
| -D*-path=* | 此类参数提供了 systemd 在运行时所需的二进制的位置,这些二进制文件有些还未安装,有些的 pkgconfig 文件现在还在/tools/lib/pkgconfig 中 |
|---|---|
| -Ddefault-dnssec=no | 此参数关闭了实验性 DNSSEC 的支持 |
| -Dfirstboot=false | 此参数将防止负责首次设置系统的 systemd 服务被安装。这对 LFS 没什么用,因为 LFS 的一切都是手动完成的 |
| -Dinstall-tests=false | 此参数将防止编译测试的安装 |
| -Dldconfig=false | 此参数将防止启动时运行 ldconfig 的 systemd 单元被安装,对于 LFS 这样的 原生发行版而言毫无作用还会拖慢启动时间。如果你也是这么想的就删掉它 |
| -Droot* | 此类参数用于确保核心程序和共享库能够安装到根分区的子目录 |
| -Dsplit-usr=true | 此参数用于确保 systemd 可以在 /bin,/lib 和 /sbin 目录工作,而非符号链接指向的 /usr 副本 |
| -Dsysusers=false | 此参数用于防止负责于设置 /etc/group 和 /etc/passwd 文件的 systemd 服务被安装。这两个文件都早在前章就创建了 |
| -Drpmmacrosdir=no | 由于 LFS 不支持 RPM,所以禁用 RPM 宏的安装 |
编译软件包:
LANG=en_US.UTF-8 ninja
安装软件包:
LANG=en_US.UTF-8 ninja install
删除不必要的符号链接:
rm -f /usr/bin/xsltproc
创建/etc/machine-id 需要的 systemd-journal 文件:
systemd-machine-id-setup
启动 preset-all:
systemctl preset-all
为了保证由 systemd-networkd 提供网络配置的系统不会出现问题,禁用 systemd-time-wait-sync.service:
systemctl disable systemd-time-wait-sync.service
防止 systemd 重设最大 PID 值:
systemd-time-wait-sync.service
退出并清理软件包:
cd ../..
rm -rf systemd-246
安装 D-Bus
- 大致构建用时:0.2 SBU
- 所需磁盘空间:18 MB
解压并进入软件包:
tar -xf dbus-1.12.20.tar.gz
cd dbus-1.12.16
准备编译 D-Bus:
./configure --prefix=/usr \
--sysconfdir=/etc \
--localstatedir=/var \
--disable-static \
--disable-doxygen-docs \
--disable-xml-docs \
--docdir=/usr/share/doc/dbus-1.12.20 \
--with-console-auth-dir=/run/console
”--with-console-auth-dir=/run/console” 用于指定 ConsoleKit 验证目录的位置。 编译软件包:
make
该软件包没有测试套件,但要求 LFS 中没有的几个软件包。运行测试套件的命令可以在 BLFS 指南 http://www.linuxfromscratch.org/blfs/view/8.4/general/dbus.html 中找到。 安装软件包:
make install
需要移动共享库到 /lib,因此需要重建 /usr/lib 中的 .so 文件:
mv -v /usr/lib/libdbus-1.so.* /lib
ln -sfv ../../lib/$(readlink /usr/lib/libdbus-1.so) /usr/lib/libdbus-1.so
创建符号链接,使得 D-Bus 和 systemd 可以使用相同的 machine-id 文件:
ln -sfv /etc/machine-id /var/lib/dbus
退出并清理软件包:
cd ..
rm -rf dbus-1.12.20
3 安装 Procps-ng
- 大致构建用时:0.2 SBU
- 所需磁盘空间:17 MB 解压并进入软件包:
tar -xf procps-ng-3.3.16.tar.xz
cd procps-ng-3.3.16
准备编译 Procps-ng:
./configure --prefix=/usr \
--exec-prefix= \
--libdir=/usr/lib \
--docdir=/usr/share/doc/procps-ng-3.3.16 \
--disable-static \
--disable-kill \
--with-systemd
“--disable-kill” 选项告知软件包无需构建已由 Util-linux 软件包安装了的 kill 命令。 编译软件包:
make
用下面的命令运行测试套件:
make check
安装软件包:
make install
最后,如果 /usr 没有挂载的话,移动重要文件到一个可以找到的位置。
mv -v /usr/lib/libprocps.so.* /lib
ln -sfv ../../lib/$(readlink /usr/lib/libprocps.so) /usr/lib/libprocps.so
退出并清理软件包:
cd ..
rm -rf procps-ng-3.3.16
4 安装 Util-linux
- 大致构建用时: 1.5 SBU
- 所需磁盘空间: 214 MB 解压并进入软件包:
tar -xf util-linux-2.36.tar.xz
cd util-linux-2.36
FHS 推荐使用 /var/lib/hwclock 目录而不是通常的 /etc 目录作为 adjtime 文件的位置。首先新建目录用于存储 hwclock 程序:
mkdir -pv /var/lib/hwclock
删除早前创建的符号链接:
rm -vf /usr/include/{blkid,libmount,uuid}
准备编译 Util-linux:
./configure ADJTIME_PATH=/var/lib/hwclock/adjtime \
--docdir=/usr/share/doc/util-linux-2.36 \
--disable-chfn-chsh \
--disable-login \
--disable-nologin \
--disable-su \
--disable-setpriv \
--disable-runuser \
--disable-pylibmount \
--disable-static \
--without-python
--disable 和 --without 选项用于防止出现关于 LFS 中缺少构建组件需要的软件包或和其他软件包安装的程序不一致的警告。 编译软件包:
make
如果需要的话,以非 root 用户运行测试套件:
注意:以 root 用户运行测试套件会对系统有害。为了运行测试套件,必须保证当前运行的系统中用于内核的 CONFIG_SCSI_DEBUG 选项可用,且是以一个模块的方式编译。把它构建到内核中将阻止启动。为了全面覆盖,还必须安装其他的 BLFS 软件包。如果需要的话,可以在重启进入完整的 LFS 系统后用以下命令运行该测试:
bash tests/run.sh --srcdir=$PWD --builddir=$PWD
chown -Rv tester .
su tester -c "make -k check"
安装软件包:
make install
退出并清理软件包:
cd ..
rm -rf util-linux-2.36
5 安装 e2fsprogs
- 大致构建用时: 4.4 SBU
- 所需磁盘空间: 106 MB 解压并进入软件包:
tar -xf e2fsprogs-1.45.6.tar.gz
cd e2fsprogs-1.45.6
E2fsprogs 的文档建议在源码目录下新建目录进行编译:
mkdir -v build
cd build
准备编译 E2fsprogs:
../configure --prefix=/usr \
--bindir=/bin \
--with-root-prefix="" \
--enable-elf-shlibs \
--disable-libblkid \
--disable-libuuid \
--disable-uuidd \
--disable-fsck
环境变量和配置选项的含义:
| 环境变量和配置选项 | 含义 |
|---|---|
| --with-root-prefix="" --bindir=/bin | 有些程序(例如 e2fsck)属于重要程序。比如,当 /usr 没有挂载的时候,仍然要求这些程序可用。它们放在类似 /lib 和 /sbin 的目录中。如果没有传递这个参数到 E2fsprogs 的配置参数中,程序就会被安装在 /usr 目录 |
| --enable-elf-shlibs | 创建该软件包中一些程序会使用的共享库 |
| --disable-* | 这会阻止 E2fsprogs 编译和安装 libuuid 和 libblkid 库、uuidd 守护进程、以及 fsck 封装包。因为 Util-Linux 安装了更新的版本 |
编译软件包:
make
输入命令运行测试:
make check
E2fsprogs 的其中一个测试程序会试图分配 256M 的内存。如果你没有比这更多的 RAM,确保为测试启用了足够的交换空间。阅读 2.1.2.1 构建宿主系统查看创建和启用交换空间的详细信息。
安装二进制文件、文档以及共享库:
make install
安装静态库和头文件:
make install-libs
使安装的静态库可写,以便后面可以移除调试符号:
chmod -v u+w /usr/lib/{libcom_err,libe2p,libext2fs,libss}.a
该软件包安装了一个 gzip 压缩的 .info 文件但并没有更新系统级的 dir 文件。解压该文件并用下面的命令更新系统的 dir 文件:
gunzip -v /usr/share/info/libext2fs.info.gz
install-info --dir-file=/usr/share/info/dir /usr/share/info/libext2fs.info
如果需要的话,用下面的命令创建和安装一些额外的文档:
makeinfo -o doc/com_err.info ../lib/et/com_err.texinfo
install -v -m644 doc/com_err.info /usr/share/info
install-info --dir-file=/usr/share/info/dir /usr/share/info/com_err.info
退出并清理软件包:
cd ../..
rm -rf e2fsprogs-1.45.6
System V 分支部分
安装 Eudev
- 大致构建用时: 0.2 SBU
- 所需磁盘空间: 82 MB 解压并进入软件包:
tar -xf eudev-3.2.9.tar.gz
cd eudev-3.2.9
准备编译 Eudev:
./configure --prefix=/usr \
--bindir=/sbin \
--sbindir=/sbin \
--libdir=/usr/lib \
--sysconfdir=/etc \
--libexecdir=/lib \
--with-rootprefix= \
--with-rootlibdir=/lib \
--enable-manpages \
--disable-static
编译软件包:
make
现在创建一些目录,不仅仅是为了测试需求,一部分的安装操作也将用到:
mkdir -pv /lib/udev/rules.d
mkdir -pv /etc/udev/rules.d
测试编译结果,通过:
make check
安装软件包:
make install
安装一些对 LFS 的环境中有益的自定义规则和支持文件:
tar -xvf ../udev-lfs-20171102.tar.xz
make -f udev-lfs-20171102/Makefile.lfs install
配置 Eudev
有关硬件的设备存放在 /etc/udev/hwdb.d 和 /lib/udev/hwdb.d 目录中。Eudev 需要将这些信息编译到一个二进制数据库文件 /etc/udev/hwdb.bin 中去。创建初始数据库:
udevadm hwdb --update
该命令需在每次更新硬件信息时运行。 退出并清理软件包:
cd ..
rm -rf eudev-3.2.9
2 安装 Procps-ng
- 大致构建用时:0.2 SBU
- 所需磁盘空间:17 MB 解压并进入软件包:
tar -xf procps-ng-3.3.16.tar.xz
cd procps-ng-3.3.16
准备编译 Procps-ng:
./configure --prefix=/usr \
--exec-prefix= \
--libdir=/usr/lib \
--docdir=/usr/share/doc/procps-ng-3.3.16 \
--disable-static \
--disable-kill \
--with-systemd
“--disable-kill” 选项告知软件包无需构建已由 Util-linux 软件包安装了的 kill 命令。 编译软件包:
make
用下面的命令运行测试套件:
make check
安装软件包:
make install
最后,如果 /usr 没有挂载的话,移动重要文件到一个可以找到的位置。
mv -v /usr/lib/libprocps.so.* /lib
ln -sfv ../../lib/$(readlink /usr/lib/libprocps.so) /usr/lib/libprocps.so
退出并清理软件包:
cd ..
rm -rf procps-ng-3.3.16
3 安装 Util-linux
- 大致构建用时: 1.2 SBU
- 所需磁盘空间: 260 MB 解压并进入软件包:
tar -xf util-linux-2.36.tar.xz
cd util-linux-2.36
FHS 推荐使用 /var/lib/hwclock 目录而不是通常的 /etc 目录作为 adjtime 文件的位置。首先新建目录用于存储 hwclock 程序:
mkdir -pv /var/lib/hwclock
准备编译 Util-linux:
./configure ADJTIME_PATH=/var/lib/hwclock/adjtime \
--docdir=/usr/share/doc/util-linux-2.34 \
--disable-chfn-chsh \
--disable-login \
--disable-nologin \
--disable-su \
--disable-setpriv \
--disable-runuser \
--disable-pylibmount \
--disable-static \
--without-python \
--without-systemd \
--without-systemdsystemunitdir
--disable 和 --without 选项用于防止出现关于 LFS 中缺少构建组件需要的软件包或和其他软件包安装的程序不一致的警告。 编译软件包:
make
如果需要的话,以非 root 用户运行测试套件:
警告:以 root 用户运行测试套件会对系统有害。为了运行测试套件,必须保证当前运行的系统中用于内核的 CONFIG_SCSI_DEBUG 选项可用,且是以一个模块的方式编译。把它构建到内核中将阻止启动。为了全面覆盖,还必须安装其他的 BLFS 软件包。如果需要的话,可以在重启进入完整的 LFS 系统后用以下命令运行该测试:
bash tests/run.sh --srcdir=$PWD --builddir=$PWD
chown -Rv tester .
su tester -c "make -k check"
安装软件包:
make install
退出并清理软件包:
cd ..
rm -rf util-linux-2.36
4 安装 e2fsprogs
- 大致构建用时: 1.6 SBU
- 所需磁盘空间: 96 MB 解压并进入软件包:
tar -xf e2fsprogs-1.45.6.tar.gz
cd e2fsprogs-1.45.6
e2fsprogs 的文档建议在源码目录下新建目录进行编译:
mkdir -v build
cd build
准备编译 e2fsprogs:
../configure --prefix=/usr \
--bindir=/bin \
--with-root-prefix="" \
--enable-elf-shlibs \
--disable-libblkid \
--disable-libuuid \
--disable-uuidd \
--disable-fsck
环境变量和配置选项的含义:
| 环境变量和配置选项 | 含义 |
|---|---|
| --with-root-prefix="" --bindir=/bin | 有些程序(例如 e2fsck)属于重要程序。比如,当 /usr 没有挂载的时候,仍然要求这些程序可用。它们放在类似 /lib 和 /sbin 的目录中。如果没有传递这个参数到 E2fsprogs 的配置参数中,程序就会被安装在 /usr 目录 |
| --enable-elf-shlibs | 创建该软件包中一些程序会使用的共享库 |
| --disable-* | 这会阻止 E2fsprogs 编译和安装 libuuid 和 libblkid 库、uuidd 守护进程、以及 fsck 封装包。因为 Util-Linux 安装了更新的版本 |
编译软件包:
make
输入命令运行测试:
make check
E2fsprogs 的其中一个测试程序会试图分配 256M 的内存。如果你没有比这更多的 RAM,确保为测试启用了足够的交换空间。阅读 第 2.5 节「在分区上创建文件系统」以及 第 2.7 节「挂载新分区」查看创建和启用交换空间的详细信息。 安装二进制文件、文档以及共享库:
make install
安装静态库和头文件:
make install-libs
使安装的静态库可写,以便后面可以移除调试符号:
chmod -v u+w /usr/lib/{libcom_err,libe2p,libext2fs,libss}.a
该软件包安装了一个 gzip 压缩的 .info 文件但并没有更新系统级的 dir 文件。解压该文件并用下面的命令更新系统的 dir 文件:
gunzip -v /usr/share/info/libext2fs.info.gz
install-info --dir-file=/usr/share/info/dir /usr/share/info/libext2fs.info
如果需要的话,用下面的命令创建和安装一些额外的文档:
makeinfo -o doc/com_err.info ../lib/et/com_err.texinfo
install -v -m644 doc/com_err.info /usr/share/info
install-info --dir-file=/usr/share/info/dir /usr/share/info/com_err.info
退出并清理软件包:
cd ../..
rm -rf e2fsprogs-1.45.6
5 安装 Sysklogd
- 大致构建用时:少于 0.1 SBU
- 所需磁盘空间: 0.6 MB 解压并进入软件包:
tar -xf sysklogd-1.5.1.tar.gz
cd sysklogd-1.5.1
首先,修复 klogd 在某些条件下会导致「segmentation fault」的问题,并修复一个过时的程序结构:
sed -i '/Error loading kernel symbols/{n;n;d}' ksym_mod.c
sed -i 's/union wait/int/' syslogd.c
编译软件包:
make
该软件包没有测试套件。 安装软件包:
make BINDIR=/sbin install
退出并清理软件包:
cd ..
rm -rf sysklogd-1.5.1
创建/etc/syslog.conf 文件:
cat > /etc/syslog.conf << "EOF"
# Begin /etc/syslog.conf
auth,authpriv.* -/var/log/auth.log
*.*;auth,authpriv.none -/var/log/sys.log
daemon.* -/var/log/daemon.log
kern.* -/var/log/kern.log
mail.* -/var/log/mail.log
user.* -/var/log/user.log
*.emerg *
# End /etc/syslog.conf
EOF
6 安装 Sysvinit
- 大致构建用时:少于 0.1 SBU
- 所需磁盘空间: 1.4 MB 解压并进入软件包:
tar -xf sysvinit-2.97.tar.xz
cd sysvinit-2.97
打一个补丁移除一些其他软件包安装的程序,改正一个消息,并修复一个编译器的警告:
patch -Np1 -i ../sysvinit-2.97-consolidated-1.patch
编译软件包:
make
该软件包没有测试套件。 安装软件包:
make install
退出并清理软件包:
cd ..
rm -rf sysvinit-2.97
清理工作
构建任务已经完成,下面一起清理以下调试符号和无用内容。
4.4.1 移除调试符号
这个部分是可选的。如果预期的用户不是一个程序员或者不打算对系统软件进行任何调试,通过从二进制文件和库中删除调试符号能减少 90MB 的系统大小。除了不能完全调试软件,这不会导致任何不便。
默认情况下大多数程序和库的编译带有调试符号。(类似 gcc 的 -g 选项。)这意味着当你调试一个包含调试信息的已编译的程序或库时,调试程序不仅能提供内存地址,还能提供变量和实例的名字。
然而,包含这些调试符号明显的增大了程序或库。下面这个例子说明了这些符号有多么占地方:
- 有调试符号的二进制 bash:1200 KB
- 无调试符号的二进制 bash:480 KB
- 有调试符号的 glibc 和 gcc 文件(/lib 和 /usr/lib):87 MB
- 无调试符号的 glibc 和 gcc 文件:16 MB
大小可能会因为所使用的编译器和 C 语言库的不同而改变,但是当比较有无调试符号的程序时,大小可能相差 2-5 倍。
因为大多数用户从来不会在他们的系统软件上使用调试器,没了这些调试符号可以省下很多磁盘空间。
注意:运行下面的命令非常简单,很多人往往顺手就复制粘贴运行了。然而这步是非常容易发生问题的,严重的甚至会导致新系统无法使用,因此在运行 strip 命令之前,对当前状态的 LFS 系统进行备份是个好主意。当然如果空间足够甚至可以跳过清理的步骤。
首先将选定库的调试符号文件分开放置。如果要在后续的 BLFS 中用 valgrind 或 gdb 做回归测试,那么调试信息还有用武之地。
save_lib="ld-2.32.so libc-2.32.so libpthread-2.32.so libthread_db-1.0.so"
cd /lib
for LIB in $save_lib; do
objcopy --only-keep-debug $LIB $LIB.dbg
strip --strip-unneeded $LIB
objcopy --add-gnu-debuglink=$LIB.dbg $LIB
done
save_usrlib="libquadmath.so.0.0.0 libstdc++.so.6.0.28
libitm.so.1.0.0 libatomic.so.1.2.0"
cd /usr/lib
for LIB in $save_usrlib; do
objcopy --only-keep-debug $LIB $LIB.dbg
strip --strip-unneeded $LIB
objcopy --add-gnu-debuglink=$LIB.dbg $LIB
done
unset LIB save_lib save_usrlib
移除程序和库的调试符号:
find /usr/lib -type f -name \*.a \
-exec strip --strip-debug {} ';'
find /lib /usr/lib -type f -name \*.so* ! -name \*dbg \
-exec strip --strip-unneeded {} ';'
find /{bin,sbin} /usr/{bin,sbin,libexec} -type f \
-exec strip --strip-all {} ';'
运行上述命令会产生大量警告,因为许多文件的格式无法被识别。完全可以忽略这些警告,他们只是表示这些文件是脚本不是二进制文件而已。
4.4.2 清理系统
最后,清除运行测试留下来的多余文件:
rm -rf /tmp/*
现在,登出后用以下新的 chroot 命令重新进入 chroot 环境。在此以后当需要进入 chroot 环境时,都是用这个新的 chroot 命令:
logout
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
这样做的原因是不再需要 /tools 中的程序。因此你可以删除 /tools 目录。 移除 /tools 也会删除用于运行工具链测试的 Tcl、Expect 和 DejaGNU 的临时复制。如果你在后续还需要这些程序,需要重新编译并安装它们。BLFS 手册有关于这些的指令(请查看 http://www.linuxfromscratch.org/blfs/)。 如果通过手动或者重启卸载了虚拟内核文件系统,重新进入 chroot 的时候确保挂载了虚拟内核文件系统。在 3.4 中介绍了该过程。 还有一些此章之前为了一些软件包的回归测试而留下的静态库。这些库来自于 binutils、Bzip2、e2fsprogs、Flex、libtool 和 zlib。运行以下命令删除:
rm -f /usr/lib/lib{bfd,opcodes}.a
rm -f /usr/lib/libbz2.a
rm -f /usr/lib/lib{com_err,e2p,ext2fs,ss}.a
rm -f /usr/lib/libltdl.a
rm -f /usr/lib/libfl.a
rm -f /usr/lib/libz.a
还有几个安装在/usr/lib 和/usr/libexec 目录下的文件,文件的扩展名为.la。这些是 libtool 的归档文件,在 Linux 系统中通常不需要它们。这些都是没有必要的东西。运行以下命令删除:
find /usr/lib /usr/libexec -name \*.la -delete
本章小结
本章节我们为 LFS 系统构建了除了内核以外的所有软件包,用以达成 LBS 标准,使其具备成为一个操作系统的所有要件。下一章,我们将画龙点睛,为系统注入灵魂。
配置系统
第 4 章,我们已经安装了除了内核以外的所有软件包,系统的框架已经基本构建完毕。本章将承接上一章内容完成内核的配置和编译,以及引导、网络等重要的系统配置。
与第 4 章相同,System V 和 systemd 两个版本的配置会有所出入,笔者将针对两个版本说明,顺序一般是先说明 System V 再说明 systemd。因为 System V 的配置比较传统,烦琐但好理解,而 systemd 是相较 System V 比较新颖的管理方式,在 System V 的基础上会更好理解。开始前提醒一下使用虚拟机构建系统的读者,这里是一个比较好的保存快照的节点。
还需注意,如果从第 4 章过来,中间的构建过程不是连续的,比如是配置错误返回快照,或者执行完第 4 章后休息了一段时间再实施本章节的内容,则需要挂载完硬盘后运行一下命令 chroot 到 LFS 系统。同时最好确认一下自己的$LFS 变量是否正确。
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
内核
5.1.1 安装内核
- 大致构建用时: 5.0 - 125.0 SBU(通常约为 9 SBU)
- 所需磁盘空间: 1200- 6750 MB(通常约为 1500 MB) 解压并进入软件包:
tar -xf linux-5.8.3.tar.xz
cd linux-5.8.3
首先,为保证内核树的绝对干净,执行如下命令:
make mrproper
接着,根据当前系统的架构生成一份基础配置:
make defconfig
然后,展开可视化配置界面:
make menuconfig
可视化配置界面的上部介绍了操作方法:方向键控制上下左右,回车进入子菜单,Y 是选中,N 是取消选中。此外,还有“/”用于搜索。一个个翻找会耗费大量时间,使用搜索可以很快确认完必要的配置。

确认以下的选项的选中情况,请务必保持一致,即有要确保选中,没有的确保不要选中,不然可能会导致最终系统无法启动。 需要注意的选项 System V 版本和 systemd 版本有所不同,先来看 System V 版本。
Device Drivers --->
Generic Driver Options --->
[ ] Support for uevent helper [CONFIG_UEVENT_HELPER]
[*] Maintain a devtmpfs filesystem to mount at /dev [CONFIG_DEVTMPFS]
Kernel hacking --->
Choose kernel unwinder (Frame pointer unwinder) ---> [CONFIG_UNWINDER_FRAME_POINTER]
接着是 systemd 版本,如果构建的是 systemd 版本,那么以下的选项状态需要注意。
General setup -->
[ ] Enable deprecated sysfs features to support old userspace tools [CONFIG_SYSFS_DEPRECATED]
[ ] Enable deprecated sysfs features by default [CONFIG_SYSFS_DEPRECATED_V2]
[*] open by fhandle syscalls [CONFIG_FHANDLE]
[ ] Auditing support [CONFIG_AUDIT]
[*] Control Group support [CONFIG_CGROUPS]
Processor type and features --->
[*] Enable seccomp to safely compute untrusted bytecode [CONFIG_SECCOMP]
Networking support --->
Networking options --->
<*> The IPv6 protocol [CONFIG_IPV6]
Device Drivers --->
Generic Driver Options --->
[ ] Support for uevent helper [CONFIG_UEVENT_HELPER]
[*] Maintain a devtmpfs filesystem to mount at /dev [CONFIG_DEVTMPFS]
[ ] Fallback user-helper invocation for firmware loading [CONFIG_FW_LOADER_USER_HELPER]
Firmware Drivers --->
[*] Export DMI identification via sysfs to userspace [CONFIG_DMIID]
File systems --->
[*] Inotify support for userspace [CONFIG_INOTIFY_USER]
<*> Kernel automounter version 4 support (also supports v3) [CONFIG_AUTOFS4_FS]
Pseudo filesystems --->
[*] Tmpfs POSIX Access Control Lists [CONFIG_TMPFS_POSIX_ACL]
[*] Tmpfs extended attributes [CONFIG_TMPFS_XATTR]
Kernel hacking --->
Choose kernel unwinder (Frame pointer unwinder) ---> [CONFIG_UNWINDER_FRAME_POINTER]
如果是实机安装的需要注意是否使用 UEFI 进行引导,如果是的话,需要保证选中下列选项。
Processor type and features --->
[*] EFI stub support [CONFIG_EFI_STUB]
这里举个例子,比如需要检索了 EFI 相关的那个条目,按“/”然后输出 CONFIG_EFI_STUB,然后按回车检索会得到下面的结果。

很幸运,只检索出了一个条目,我们可以看到:
(1) -> EFI runtime service support (EFI [=y])
如果有其他条目,除了这个(1)以外,还会有(2)(3),按相应的数字就可以进入条目进行修改。 当然,如果只是想了解条目的选中情况,第一行已经告诉我们,该条目处于未选中状态。请使用这个方法,根据上述分版本列出的条目全部确认一下。如果有更改的话,记得保存变更。
Symbol: EFI_STUB [=n]
修改完配置后,我们开始编译内核:
make
并安装模块:
make modules_install
我们需要将一些文件复制到/boot 目录下。如果/boot 有独立分区,那么文件就应该复制到/boot 分区中去。简单的解决方法就是在执行前将/boot 分区绑定到宿主的/mnt/lfs/boot。以宿主系统中的 root 用户运行:
mount --bind /boot /mnt/lfs/boot
内核映像文件所在的实际目录根据主机系统架构会有所不同。文件名也可以根据个人喜好修改,不过为了兼容配置引导过程中的自动设定,最好以 vmlinuz 开头。下面的命令假设主机是 x86 架构:
cp -iv arch/x86/boot/bzImage /boot/vmlinuz-5.8.3-lfs-10.0
System.map 是内核的符号文件,映射了每一个内核 API 函数的入口,以及内核运行时的数据结构地址,是调查内核问题时的资源。运行下面的命令复制映射文件:
cp -iv System.map /boot/System.map-5.8.3
指令 make menuconfig 中生成的内核配置文件.config 包含了当前编译的内核的所有配置。最好能保存下来留作参考,这里我们将其复制一份以为备用:
cp -iv .config /boot/config-5.8.3
安装 Linux 内核文档:
install -d /usr/share/doc/linux-5.8.3
cp -r Documentation/* /usr/share/doc/linux-5.8.3
需要注意,内核源代码目录下文件的所有者并不是 root。在以 root 用户解压软件包的时候(我们在 chroot 环境里解压的),解压出来的文件会拥有解压该软件包的电脑中的用户和组。在安装其他包的时候这并不是问题,因为它们的源代码在安装完后就删除了。不过,Linux 内核的源代码经常会保留比较长时间。这样的话,就有可能会把软件包作者的用户 ID 对应到本机的某个用户上,从而导致某潜在用户拥有内核源代码写权限的可能。
不同于其他的软件包,由于在构建 BLFS 的时候需要更新内核,在构建结束后并不一定要删除内核源码的目录。
如果想要保留内核的源目录,请在 linux-5.8.3 目录下运行 chown -R 0:0 来确保所有文件的所有者都 root。
注意,一些内核文档里建议创建符号链接/usr/src/linux 指向内核源代码目录。这是 Linux 2.6 及以前版本内核的特定要求,而在 LFS 系统中一定不要创建这个链接。因为这样的话,反而会导致在往构建完成后的基础 LFS 系统中安装某些软件包时的不便。
注意,系统 include 目录(/usr/include)下的头文件应该总是和编译 Glibc 时用到的头文件保持一致。这里的头文件就是在 4.2.1 节整理过的头文件。
5.1.2 配置 Linux 模块加载顺序
虽然大多数情况下,Linux 模块会被自动加载,但是有时候需要特别指定加载顺序。modprobe 或 insmod 在加载模块时会读取/etc/modprobe.d/usb.conf。如果将 USB 设备(ehci_hcd、ohci_hcd 和 uhci_hcd)编译为模块,则需要此文件,这样它们就会以正确的顺序加载。ehci_hcd 需要在 ohci_hcd 和 uhci_hcd 之前加载,否则在系统启动过程中将会输出警告。 运行以下命令建立/etc/modprobe.d/usb.conf 文件:
install -v -m755 -d /etc/modprobe.d
cat > /etc/modprobe.d/usb.conf << "EOF"
# Begin /etc/modprobe.d/usb.conf
install ohci_hcd /sbin/modprobe ehci_hcd ; /sbin/modprobe -i ohci_hcd ; true
install uhci_hcd /sbin/modprobe ehci_hcd ; /sbin/modprobe -i uhci_hcd ; true
# End /etc/modprobe.d/usb.conf
EOF
配置引导
5.2.1 创建/etc/fstab 文件
/etc/fstab 文件的作用是让其他程序确定存储设备的默认挂载点、挂载参数和检查信息(例如完整性检测)。 以下是笔者的 fstab 文件为例讲解,请根据自己的分区情况进行更改。一个取巧的方法是参考宿主机中的 fstab 文件,进行更改。
cat > /etc/fstab << "EOF"
# Begin /etc/fstab
# file system mount-point type options dump fsck
# order
/dev/sda5 / ext4 defaults 1 1
/dev/sda1 /boot ext2 defaults 0 2
proc /proc proc nosuid,noexec,nodev 0 0
sysfs /sys sysfs nosuid,noexec,nodev 0 0
devpts /dev/pts devpts gid=5,mode=620 0 0
tmpfs /run tmpfs defaults 0 0
devtmpfs /dev devtmpfs mode=0755,nosuid 0 0
# End /etc/fstab
EOF
其中的 sda1、sda5 和 ext2、ext4 请使用适当的值替换。关于文件中 6 个字段的含义,请查看 man 5 fstab1 。
基于 MS-DOS 或者是来源于 Windows 的文件系统(例如:vfat,ntfs,smbfs,cifs,iso9660,udf)需要在挂载选项中添加 iocharset,才能让非 ASCII 字符的文件名正确解析。此选项的值应该与语言区域设置的值相同,以便让内核能正确处理。此选项在相关字符集定义已为内核内建或编译为模块时生效(在文件系统→本地语言支持中查看)。此外,vfat 和 smbfs 还需启用 codepage,同样也是为了解决文字的正常显示问题。例如,想要挂载 USB 闪存设备,zh-CN.GBK 用户需要在/etc/fstab 中添加以下挂载选项:
noauto,user,quiet,showexec,iocharset=gbk,codepage=936
对于 zh_CN.UTF-8 用户的对应选项是:
noauto,user,quiet,showexec,iocharset=utf8,codepage=936
需要注意的是,iocharset 默认值是 iso8859-1(其保证文件系统大小写敏感),而 utf8 这个参数告知内核使用 UTF-8 转换文件名,以便可以在 UTF-8 语言环境中解释它们。 此外,还有可能在内核的配置过程中就指定一些文件系统的默认 codepage 和 iocharset 值。相关参数有“默认 NLS 选项”(CONFIG_NLS_DEFAULT),“默认远程 NLS 选项”(CONFIG_SMB_NLS_DEFAULT),“FAT 默认代码页”(CONFIG_FAT_DEFAULT_CODEPAGE),和“FAT 默认 IO 字符集”(CONFIG_FAT_DEFAULT_IOCHARSET)。不过,无法在内核编译阶段指定 ntfs 文件系统的设置。 另外,一些硬盘类型在遇到电源故障时,假如在/etc/fstab 中使用 barrier=1 这个挂载选项,则会让 ext3 文件系统的数据更加安全。如需检查磁盘是否支持此选项,请运行 hdparm。例如:
hdparm -I /dev/sda | grep NCQ
如果有输出内容,则代表选项可用。
注意:基于逻辑卷管理(LVM)的分区不可使用 barrier 选项。
5.2.2 使用 GRUB 设置启动过程
这一步可以算是临门一脚,也是最容易功亏一篑的一步,因为这步配置问题会直接导致系统无法启动。这也是为什么笔者非常推荐使用虚拟机来完成构建的原因。如果是实机环境,就不得不面临使用 CD 或者 USB 来救场的情况。不过需要明确的是这一步出错绝没有严重到需要推倒重来的地步,出现无法引导的情况只需要通过 USB 等的方法挂载分区,重新配置 Grub 即可。
GRUB 对于硬盘和分区,自身有一套命名规则 (hdn,m),其中 n 是硬盘数,m 是分区号。硬盘数 N 从 0 开始计数,分区数需要区别对待——主分区从 1 开始计数而扩展分区从 5 开始计数。需要注意的是,和早期版本相比,计数方式有所变化。例如,分区 sda1 是 (hd0,1),sdb3 是 (hd1,3)。Linux 下,并不将 CD-ROM 设备假想为硬盘。例如,就算已有 CD 设备挂载为 hdb,第二块硬盘挂载为 hdc,Grub 依旧将第二块硬盘称为 (hd1)。
GRUB 会将一些数据写入硬盘的第一个物理扇区。这一部分不属于任何一个操作系统,在启动时,该部分数据激活,然后寻找 Grub 的模块,GRUB 模块的默认位置为/boot/grub/。
引导分区的位置会影响配置和使用。
一种建议是使用一个独立的小分区(建议大小 100MB)专用于存放引导信息,则每一个发行版,不论是 LFS 还是其他的商业发行版都能访问相同的引导文件,而且任何已经启动的系统都能访问该分区中的内容。如果你选择这么做,你需要挂载这个独立分区,移动所有的文件从当前的/boot 目录(比如说你上一节刚编译的 Linux 内核)到新的分区。你然后要卸载这个新分区,重新挂载它为/boot。如果你这么做,一定要更新/etc/fstab。本书采用的就是这种方式 。 另一种则是直接使用当前的 lfs 分区,但这种方式在配置多系统启动的时候比较麻烦。
以上两种不同的方式需要注意的分区也不同,如果你使用单独的分区,需要知道引导分区的磁盘位置;如果使用当前的 lfs 分区,也需要明确当前分区的磁盘位置,以下假定根分区(或者是磁盘分区)是 sda2。
将 GRUB 文件安装到/boot/grub 然后设置引导扇区:
grub-install /dev/sda
如果系统是通过 UEFI 引导的,grub-install 将会尝试安装 x86_64-efi 的文件,但是这些文件并未在第 4 章中安装。所以在在这种情况下,请在上述命令后面追加--target i386-pc。 创建 GRUB 配置文件:
cat > /boot/grub/grub.cfg << "EOF"
# Begin /boot/grub/grub.cfg
set default=0
set timeout=5
insmod ext2
set root=(hd0,1)
menuentry "GNU/Linux, Linux 5.2.8-lfs-9.0" {
linux /boot/vmlinuz-5.2.8-lfs-9.0 root=/dev/sda2 ro
}
EOF
请参照上述对于 Grub 的介绍,并根据自己的分区情况、是否使用 UEFI 等因素,修改自己的 grub.cfg 文件。
fsck 列的数值来决定需要检查的文件系统的检查顺序。允许的数字是 0, 1,和 2。根目录应当获得最高的优先权 1,其他所有需要被检查的设备设置为 2。0 表示设备不会被 fsck 所检查。
System V 启动脚本的运用与配置
本节将介绍 System V 的用法以及相关配置。System V 的特点就是配置文件多,所以本节会比 5.4 节(介绍 systemd)在篇幅上多很多,解释说明的地方也会多一点。
5.3.1 System V 的运行级概念
1983 年以来,System V 便是 UNIX 和类 UNIX(例如 Linux)系统中的经典启动过程。它包括小程序 init 用于启动诸如 login(由 getty 启动)这样的基础程序,并运行着名为 rc 的脚本。该脚本控制着一众附加脚本的执行,这些附加脚本是实施系统初始化所需要的任务的脚本。 程序 init 由文件/etc/inittab 控制着,并且组织成用户能够运行的运行级别形式:
- 0 —停止;
- 1 —单用户模式;
- 2 —多用户,无网络;
- 3 —完整的多用户模式;
- 4 —用户可定义;
- 5 —完整的多用户模式,附带显示管理;
- 6 —重启。 常用的默认运行级为 3 或 5。
5.3.2 安装 LFS-Bootscripts
- 大致构建用时:少于 0.1 SBU
- 所需磁盘空间: 244 KB 解压并进入软件包:
tar -xf lfs-bootscripts-20200818.tar.bz2
cd lfs-bootscripts-20200818
安装软件包:
make install
安装完成后,退出当前目录并清理软件包:
cd ..
rm -rf lfs-bootscripts-20200818
5.3.3 配置 Sysvinit
在内核初始化的时候,无论是命令行指定运行的第一个程序,还是默认的 init。该程序会读入初始化文件/etc/inittab。下面创建此文件:
cat > /etc/inittab << "EOF"
# Begin /etc/inittab
id:3:initdefault:
si::sysinit:/etc/rc.d/init.d/rc S
l0:0:wait:/etc/rc.d/init.d/rc 0
l1:S1:wait:/etc/rc.d/init.d/rc 1
l2:2:wait:/etc/rc.d/init.d/rc 2
l3:3:wait:/etc/rc.d/init.d/rc 3
l4:4:wait:/etc/rc.d/init.d/rc 4
l5:5:wait:/etc/rc.d/init.d/rc 5
l6:6:wait:/etc/rc.d/init.d/rc 6
ca:12345:ctrlaltdel:/sbin/shutdown -t1 -a -r now
su:S016:once:/sbin/sulogin
1:2345:respawn:/sbin/agetty --noclear tty1 9600
2:2345:respawn:/sbin/agetty tty2 9600
3:2345:respawn:/sbin/agetty tty3 9600
4:2345:respawn:/sbin/agetty tty4 9600
5:2345:respawn:/sbin/agetty tty5 9600
6:2345:respawn:/sbin/agetty tty6 9600
# End /etc/inittab
EOF
初始化文件的解释可以参考 inittab 的 man 手册页面。对于 System V 而言,启动的核心命令便是 rc。上面的初始化文件将指示 rc 运行/etc/rc.d/rcS.d 目录中所有以 S 开头的脚本,然后便是 /etc/rc.d/rc?.d 目录中所有以 S 开头的脚本。目录中的问号由指定的 initdefault 值来决定。
为了方便,rc 会从/lib/lsb/init-functions 中读取函数库。该库也会读取一个可选的配置文件/etc/sysconfig/rc.site。任何在后续章节中描述到的系统配置文件中的参数,都可以放在这个文件中(仅限 System V 版),所有的系统参数都可以合并到该文件中。
为了调试方便,函数脚本会将日志输出到/run/var/bootlog 文件中。由于/run 目录是个 tmpfs(临时文件系统),所以该文件在启动之后就不会持续存在了,但在启动过程的最后,这些内容会被添附到更为持久的/var/log/boot.log 文件中。
想要改变运行级,可以使用命令 init
/etc/rc.d 下有许多看起来像 rc?.d 的目录(其中的“? ”便是运行级)以及 rcsysinit.d,这些目录下包含了许多符号链接。它们有些以 K 开头,另一些的以 S 开头,首字母的后面都有两位数字。其中的 K 意味着停止(杀死)服务,S 意味着启动服务。而这些数字则决定了脚本的运行顺序,从 00~99,数字越小的将越先被执行。当 init 切换到另一个运行级时,相应的服务将会依据运行级启动或停止。
真正的脚本放在/etc/rc.d/init.d 目录中。那些链接全部指向它们,真正工作的便是它们。K 开头的链接和 S 开头的链接指向的其实是/etc/rc.d/init.d 目录中的同一个脚本。能这样的原因是脚本可以用不同的参数调用,例如 start、stop、restart、reload 和 status。当遇到的链接是 K 开头,便相应执行脚本的 stop 参数。当遇到的链接是 S 开头的话,便相应执行脚本的 start 参数。
还有一种情况刚才没有提及。那便是 rc0.d 和 rc6.d 目录中以 S 开头的链接是不会导致任何服务被启动的。它们只会调用 stop 参数去停止某些服务。这背后的隐含逻辑便是,当用户打算重起或是关闭系统时,无须去启动些什么。系统仅仅是需要被停止而已。
表 5-1 便是脚本参数的描述。
| 参数 | 作用 |
|---|---|
| stop | 服务将被停止 |
| restart | 服务将被停止,然后再一次启动 |
| reload | 服务的配置文件将被更新。该命令用于修改服务配置文件后,无需重启服务 |
| status | 告知你服务是否正处于运行中,以及相应的 PID |
不必拘谨,启动过程的工作你可以随意修改(这毕竟是你自己的 LFS 系统)。这里的给出的文件只不过是抛砖引玉,示范而已。
5.3.4 udev 启动脚本
启动脚本/etc/rc.d/init.d/udev 启动 udevd,触发已经被内核创建好的「冷插拔(coldplug)」设备,并等待规则完成。该脚本还会解除/sbin/hotplug 中默认的 uevent 处理。这是因为内核不再需要调用外部的二进制文件了。取而代之的是,udevd 会为内核引发的 uevent 去监听 netlink 的套接字。
初始化脚本/etc/rc.d/init.d/udev_retry 负责重新触发子系统的事件,这些子系统的规则,可能会依赖于直到 mountfs 脚本运行后才挂载的文件系统(特别是/usr 和/var)。该脚本会在 mountfs 脚本后运行,所以这些规则(若是被重新触发)应该会在第二次的时候成功。这些被配置在 /etc/sysconfig/udev_retry 文件中;这个文件中,除了注释以外的任何单词(word),都会被认为是在重试时触发的子系统名称。想要找到设备的子系统,使用udevadm info --attribute-walk <device>,其中的
5.3.5 配置系统时钟
脚本从硬件时钟,或称 BIOS 或 CMOS 时钟中读取时间。如果硬件时钟被设置为 UTC,该脚本会使用/etc/localtime 文件(它会告知程序 hwclock 用户的时区)将硬件时钟的时间转换为本地时间。因为无法检测硬件时钟是否为 UTC,所以需要手动去配置。
在内核的启动并检测硬件功能的时候,setclock 会通过 udev 运行。同样可以通过 stop 参数手动将系统时间存入 CMOS 时钟中。
如果你记不清是否将硬件时钟设置成 UTC,可以通过运行命令 hwclock --localtime --show 查看。这条命令会根据硬件时钟显示当前的时间。如果显示的时间和你的手表一致,那么硬件时间可能被设置成了本地时间。如果 hwclock 的输出不是本地时间,那就有可能是 UTC 时间。在 hwclock 的输出时间上加上或减去时区之间的时差。例如,我们常用的北京时间 UTC+8,在本地时间上减去 8 小时便是 UTC 时间了。 如果硬件时钟未设置成 UTC 时间,将 UTC 变量改成 0(零)。这里的 0 和设置中的 1 代表的不是时区,而是是否使用了 UTC 时间。 运行以下命令新建文件/etc/sysconfig/clock:
cat > /etc/sysconfig/clock << "EOF"
# Begin /etc/sysconfig/clock
UTC=1
# Set this to any options you might need to give to hwclock,
# such as machine hardware clock type for Alphas.
CLOCKPARAMS=
# End /etc/sysconfig/clock
EOF
更多系统时间设置相关的问题可以参考 http://www. linuxfromscratch.org/hints/downloads/files/time.txt,它非常好地解释了如何在 LFS 中应对时间的问题。其中说明了诸如时区,UTC 和 TZ 环境变量之类的问题。 有兴趣的读者,可以尝试着将 CLOCKPARAMS 和 UTC 参数设置在文件/etc/sysconfig/rc.site 中。
5.3.6 配置 Linux 控制台
本章简要介绍控制台的启动脚本,以及控制台键盘映射、字体和内核的日志级别的配置方法。由于国内键盘使用的大都是 US 布局,所以本身是无须设置键盘映射的,且无法通过配置启动文件使控制台显示中文,所以改配置文件主要面向欧美等语言,不感兴趣的读者可以跳过。如果没有配置文件(且 rc.site 也无相关配置),console 启动脚本就不会执行任何操作。
console 脚本读取/etc/sysconfig/console 文件以获取配置信息,以决定使用的键盘映射和屏幕字体。特定的语言各种各样,可以查看 HOWTO 获取帮助,详见 http://www.tldp.org/HOWTO/HOWTO-INDEX/other-lang.html。如果读者心存疑惑,不妨检索/usr/share/keymaps 和/usr/share/consolefonts 目录下的有效键盘映射和屏幕字体。查阅 loadkeys(1) 和 setfont(8) 手册,为这些程序选择正确的参数。
/etc/sysconfig/console 文件中每行的格式应该像 VARIABLE="value"这样。详细的配置举例便不再赘述,下列为 console 文件中几个常见变量。
- LOGLEVEL:日志级别从 1(没有消息)至 8。默认的级别是 7。
- KEYMAP:用于指定需要加载的键盘映射名称,例如 it。变体过多的情况需指定父目录(例如, qwerty/es),以保证加载的键盘映射是恰当的。
- KEYMAP_CORRECTIONS:用于解决现有的键盘映射不能完全满足的需求。例如,想要将欧元符号加入到原来不存在该符号的键盘映射中,将变量设置为 euro2 即可。
- FONT:通常包括字体名、-m 和需要加载的应用字符映射名。例如,为了让字体 lat1-16 和 8859-1 应用字符映射一起加载(美国常用设置),将变量设置为 lat1-16 -m 8859-1。
- UNICODE:如果需要使控制台进入 UTF-8 模式,则需将该变量设置为 1,yes 或 true。对于基于 UTF-8 语言环境的地区十分有用,不包含中文字符。
- LEGACY_CHARSET:很多的键盘布局,在 Kbd 的软件包现有的键盘映射中并未囊括。如果该变量设置的编码是键盘映射中非 UTF-8 的编码,那么 console 的启动脚本就不会将键盘映射转换为 UTF-8。
对于中文,日语、韩语以及一些其他的语言需要的字符,Linu 的控制台还无法通过配置使之正常显示。用户若要使用这些语言,需要安装 X Window 系统,用于扩充所需字符域的字体,以及合适的输入法(例如,支持语言十分广泛的 SCIM)。
需要注意,/etc/sysconfig/console 文件只负责 Linux 文本控制台的本地化。对于 X Window 系统,通过 ssh 的会话,以及串口的控制台的键盘布局和终端字体,以上的设定并不适用。所以这些情况下,不要应用列出的最后两项设置中提到限制。
5.3.7 文件 rc.site
可选的/etc/sysconfig/rc.site 文件中包含着那些为每个 System V 启动脚本自动设置好的设定。这些设定也可以在/etc/sysconfig/目录的 hostname、console 和 clock 文件中设置。如果这些设定值同时在以上的这些文件和 rc.site 中设定了,那么脚本中的设定值将会被优先采用。 rc.site 中还包含了另外一些可以自定义的启动过程的参数。设置 IPROMPT 变量会启用启动脚本的选择性运行。其他的选项,在文件的注释中有所描述。文件的默认版本如下所示:
# rc.site
# Optional parameters for boot scripts.
# Distro Information
# These values, if specified here, override the defaults
#DISTRO="Linux From Scratch" # The distro name
#DISTRO_CONTACT="lfs-dev@linuxfromscratch.org" # Bug report address
#DISTRO_MINI="LFS" # Short name used in filenames for distro config
# Define custom colors used in messages printed to the screen
# Please consult `man console_codes` for more information
# under the "ECMA-48 Set Graphics Rendition" section
#
# Warning: when switching from a 8bit to a 9bit font,
# the linux console will reinterpret the bold (1;) to
# the top 256 glyphs of the 9bit font. This does
# not affect framebuffer consoles
# These values, if specified here, override the defaults
#BRACKET="\\033[1;34m" # Blue
#FAILURE="\\033[1;31m" # Red
#INFO="\\033[1;36m" # Cyan
#NORMAL="\\033[0;39m" # Grey
#SUCCESS="\\033[1;32m" # Green
#WARNING="\\033[1;33m" # Yellow
# Use a colored prefix
# These values, if specified here, override the defaults
#BMPREFIX=" "
#SUCCESS_PREFIX="${SUCCESS} * ${NORMAL} "
#FAILURE_PREFIX="${FAILURE}*****${NORMAL} "
#WARNING_PREFIX="${WARNING} *** ${NORMAL} "
# Manually seet the right edge of message output (characters)
# Useful when resetting console font during boot to override
# automatic screen width detection
#COLUMNS=120
# Interactive startup
#IPROMPT="yes" # Whether to display the interactive boot prompt
#itime="3" # The amount of time (in seconds) to display the prompt
# The total length of the distro welcome string, without escape codes
#wlen=$(echo "Welcome to ${DISTRO}" | wc -c )
#welcome_message="Welcome to ${INFO}${DISTRO}${NORMAL}"
# The total length of the interactive string, without escape codes
#ilen=$(echo "Press 'I' to enter interactive startup" | wc -c )
#i_message="Press '${FAILURE}I${NORMAL}' to enter interactive startup"
# Set scripts to skip the file system check on reboot
#FASTBOOT=yes
# Skip reading from the console
#HEADLESS=yes
# Write out fsck progress if yes
#VERBOSE_FSCK=no
# Speed up boot without waiting for settle in udev
#OMIT_UDEV_SETTLE=y
# Speed up boot without waiting for settle in udev_retry
#OMIT_UDEV_RETRY_SETTLE=yes
# Skip cleaning /tmp if yes
#SKIPTMPCLEAN=no
# For setclock
#UTC=1
#CLOCKPARAMS=
# For consolelog (Note that the default, 7=debug, is noisy)
#LOGLEVEL=7
# For network
#HOSTNAME=mylfs
# Delay between TERM and KILL signals at shutdown
#KILLDELAY=3
# Optional sysklogd parameters
#SYSKLOGD_PARMS="-m 0"
# Console parameters
#UNICODE=1
#KEYMAP="de-latin1"
#KEYMAP_CORRECTIONS="euro2"
#FONT="lat0-16 -m 8859-15"
#LEGACY_CHARSET=
LFS 启动脚本会以相当效率的方式启动和关闭系统,不过你可以在 rc.site 文件中进行调整,来提升速度或是根据需求调整消息。若是有这样的需求,就去调整上面/etc/sysconfig/rc.site 文件的设置吧!
在启动脚本 udev 运行时,会调用一次 udev settle,完成检测需要很长时间。这段时间根据当前系统的设备,可花可不花。如果你需要的仅仅是简单的分区和单个网卡,在启动的过程中,就没有必要等待这个命令执行。通过设置变量 OMIT_UDEV_SETTLE=y,可以跳过此命令。
启动脚本 udev_retry 默认也执行 udev settle。该命令仅在/var 目录是分开挂载的情况下需要。因为这种情况下时钟需要文件/var/lib/hwclock/adjtime。其他的自定义设置可能也需要等待 udev 执行完成,但是在许多的安装中不需要自定义设置,可以设置变量 OMIT_UDEV_RETRY_SETTLE=y 跳过命令。
默认情况下,文件系统检测静默执行。看上去就像是启动过程中的一个延迟。想要打开 fsck 的输出,设置变量。
重起时,你可能想完全跳过文件系统检测 fsck。为此,可以创建/fastboot 文件或以/sbin/shutdown -f -r now 命令重启系统。另一方面,你也可以通过创建/forcefsck,或在运行 shutdon 命令时,用-F 参数代替-f,以此来强制检测整个文件系统。
设置变量 FASTBOOT=y 将会在启动过程中禁用 fsck。不推荐你长时间地使用该方式。
通常,/tmp 目录中的所有文件会在启动时删除。根据存在目录与文件的数量,该操作会导致启动过程中明显的延迟。如果要跳过移除文件的操作,则需要设置变量 SKIPTMPCLEAN=y。
在关机的过程中,init 程序会向每一个已经启动的程序(例如,agetty)发送一个 TERM 信号,并等一段时间(默认为 3 秒),然后给每个进程发送 KILL 信号。对没有被自身脚本关闭的进程,sendsignals 脚本会重复此过程。init 的延迟可以通过参数来设置。例如,想去掉 init 的延迟,可以通过在关机或重启时设置-t0 参数(如,设置/sbin/shutdown -t0 -r now)。sendsignals 脚本的延迟可以通过设置参数 KILLDELAY=0 跳过。
5.3.8 配置网络
1. 创建网络接口配置文件
网络脚本开启和关闭哪些端口,一般取决于/etc/sysconfig/目录下的文件。该目录中应包含要配置接口的描述文件,例如 ifconfig.eth0。eth0 为接口名,而文件名只能是 ifconfig。文件的内容是此接口的属性信息,例如 IP 地址、子网掩码等。
如果你不确定网卡接口的名称,则可以在系统启动之后执行 ip link 或者 ls /sys/class/net。

可以看到本机网卡的接口名为 enp0s3。相对的创建静态 IP 示例文件如下。
cd /etc/sysconfig/
cat > ifconfig.enp0s3 << "EOF"
ONBOOT=yes
IFACE=enp0s3
SERVICE=ipv4-static
IP=10.0.2.15
GATEWAY=10.0.2.2
PREFIX=24
BROADCAST=10.0.2.255
EOF
请根据实际的网络情况,更改上述文件中的 IP 地址、网关、掩码等。 如果变量 ONBOOT 设置为 yes,则 System V 网络脚本将在系统启动期间激活网卡,如果设置为其他非 yes 值则不会自动激活网卡。在这种情况下,你可以通过命令 ifup 和 ifdown 手动激活或者是禁用网卡。 变量 IFACE 用于定义接口的名称,例如 eth0。所有的网络设备配置文件都依赖于它。文件扩展名必须与该值匹配。 变量 SERVICE 定义了获取 IP 地址的方式。LFS-Bootscripts 包中有模块化的 IP 分配格式,且在文件夹/lib/services/中建立文件以允许其他的 IP 获取方式。在 BLFS 书中,通常它被设置为动态主机配置协议(DHCP)。 变量 GATEWAY 应该包括默认的网关 IP 地址。如果不需要,应该直接将这行注释掉。 变量 PREFIX 表示子网所用的位数。IP 地址中每一个字节是 8 位。如果子网掩码是 255.255.255.0,那么就会使用最前面的 3 个字节(24 位)表达网络号。如果子网掩码是 255.255.255.240,则使用最开始的 28 位。前缀如果大于 24 位,一般都用于 DSL 或者是有线 ISP。在此示例中(PREFIX=24),子网掩码是 255.255.255.0。根据你实际的指望调整此字段,如果省略此字段,则默认为 24。 如欲获得更多信息,请浏览 ifup 的 man 手册。
2. 创建/etc/resolv.conf 文件
如果系统连接到互联网,则需要通过 DNS 服务将域名和 IP 地址进行相互的转换。你可以从 ISP 或者网络管理员处获得可用的 DNS 服务器,并填入/etc/resolv.conf 文件。可以通过以下命令建立此文件:
cat > /etc/resolv.conf << "EOF"
# Begin /etc/resolv.conf
domain LFS-SYSV
nameserver 10.0.2.3
nameserver 8.8.8.8
# End /etc/resolv.conf
EOF
字段 domain 可以忽略或者使用 search 字段替代。你可以参考 man 手册的 resolv.conf 部分获得更多信息。
其中,nameserver 对应的 IP 地址也应该替换为合适的 DNS 服务器的 IP 地址。除了 DNS 服务器还可以是本地网络中的路由。通常来说,一般会有多个 DNS 地址可供填写(需要备选服务器具有相关兼容性)。如果你只想要填写一个 DNS 服务器,那么将文件中的第 2 行 nameserver 移除即可。
上述的 DNS 是直接抄宿主机的,如果宿主机可以上网,大可直接抄宿主机的 DNS 即可。
当然也可以使用 Google 这种公共 IPv4 DNS 的服务器,或者国内的 DNS 服务器。
- Google 公开的 DNS 解析服务器地址 IPv4 的为:8.8.8.8 和 8.8.4.4。IPv6 的为 2001:4860:4860::8888 和 2001:4860:4860::8844。
- 114 DNS:114.114.114.114 和 114.114.115.115。
- 阿里 DNS:223.5.5.5 和 223.6.6.6。
- 百度 DNS:180.76.76.76。
- OpenDNS:208.67.220.220。
3. 配置系统主机名称
在系统启动期间,文件/etc/hostname 用于建立系统的主机名称。 通过以下命令建立 /etc/hostname 文件且向其中写入主机名:
echo "<lfs>" > /etc/hostname
4. 创建 /etc/hosts
与/etc/resolv.conf 类似,也是处理域名和 IP 地址转换的。优先度高于/etc/resolv.conf,可以理解为一个本地的 DNS 服务。 通过以下命令建立/etc/hosts 文件:
cat > /etc/hosts << "EOF"
# Begin /etc/hosts
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
# End /etc/hosts
EOF
这部分没需要特别设置的,直接使用上述的命令,配置回环地址即可。
systemd 的运用与配置
5.4.1 网络配置
1. 网络接口配置文件
自版本 209 开始,systemd 提供了一个名为 systemd-networkd 的命令用于处理基本的网络配置。另外,自版本 213 开始,DNS 名称解析可用 systemd-resolved 代替静态的/etc/resolv.conf 文件来解决。默认情况,两种服务都将被启用。
systemd-networkd(和 systemd-resolved)的配置文件可能在/usr/lib/systemd/network 或 /etc/systemd/network 中。在/etc/systemd/network 中,文件比/usr/lib/systemd/network 中的有更高的优先级。配置文件类型有 3 种:.link、.netdev 和.network 文件。你可以通过查阅 man 手册的 systemd-link(5),systemd-netdev(5) 和 systemd-network(5) 获取更多关于这些配置文件的详细介绍。
udev 通常会根据系统物理特性分配接口名称。例如,enp2s1。如果你不确定接口名称,你可以在系统启动后运行 ip link 查看。
对于大多数系统,每种连接只会有一种网络接口。例如,传统有线连接的接口名 eth0。而无线连接的名称通常是 wifi0 或 wlan0。
如果你更青睐传统或是自定义的网络接口名称,有 3 种实现的方法:
- 为默认的策略屏蔽 udev 的.link 文件: ln -s /dev/null /etc/systemd/network/99-default.link
- 创建手动命名规则,例如将接口命名成诸如「internet0」、「dmz0」或「lan0」。为此,请在/etc/systemd/network/中创建.link 文件,为其中的一个、一些,或者全部的接口赋予明确的名字,或更妥善的命名规则。
示例:
systemd 独立小节,介绍 systemd 的用法以及相关配合配置。
/etc/systemd/system.conf 文件包含了大量的 systemd 控制命令。假如未作任何的更改,文件中的所有行应该都是注释掉的,这代表了 systemd 正使用默认的运行方式。这个文件中可以设置日志级别,可以修改日志的基本设置。所有设置项都可以在 man 手册的 systemd-system.conf(5) 中查看。
cat > /etc/systemd/network/10-ether0.link << "EOF"
[Match]
# Change the MAC address as appropriate for your network device
MACAddress=12:34:45:78:90:AB
[Link]
Name=ether0
EOF
你可以参考 man 手册 systemd.link(5) 获取更多信息。 在/boot/grub/grub.cfg 中,给内核命令行传递 net.ifnames=0 选项。
2. 静态 IP 配置
以下为设置静态 IP 而创建的基础配置文件,同时用到了 systemd-networkd 和 systemd-resolved:
cat > /etc/systemd/network/10-eth-static.network << "EOF"
[Match]
Name=enp0s3
[Network]
Address=10.0.2.15/24
Gateway=10.0.2.2
DNS=10.0.2.3
Domains=LFS-BOOK-systemd
EOF
如果你的 DNS 服务器超过了一个,可以为其添加多个 DNS 条目。如果你打算用静态的/etc/resolv.conf 文件,就不要添加 DNS 或域名条目。
3. DHCP 的配置
如果要使用动态 IP,运行以下命令,创建用于设置 IPv4 DHCP 的基础配置文件:
cat > /etc/systemd/network/10-eth-dhcp.network << "EOF"
[Match]
Name=enp0s3
[Network]
DHCP=ipv4
[DHCP]
UseDomains=true
EOF
4. 配置 systemd-resolved
如果你使用其他方式来配置你的网络接口(例如 ppp、network-manager 等),或者任何类型的本地解析器(例如 bind、dnsmasq 等),以及任何生成/etc/resolv.conf 文件的软件(例如 resolvconf),就别用 systemd-resolved 服务了。
用 systemd-resolved 配置 DNS 时会创建/run/systemd/resolve/resolv.conf 文件,并在/etc 中创建一个指向生成文件的符号链接:
ln -sfv /run/systemd/resolve/resolv.conf /etc/resolv.conf
如果需要静态/etc/resolv.conf 文件,可以参考 5.3.8 节。
5.4.2 配置系统时间
systemd 可以通过配置 systemd-timedated 配置时间和时区。 如果你不确定是否将硬件时钟设置为 UTC,可以通过 hwclock --localtime --show 来查看。 这将根据硬件时钟显示当前的时间。如果显示的和手表的时间相同,应该是设置为本地时间了;如果 hwclock 输出的时间不一致,应该是设置为 UTC 了。通过增减数小时,可以确定你所在时区。 systemd-timedated 读取/etc/adjtime,然后确定是本地时间还是 UTC 时间。 如果想要将硬件时钟设置为本地时间,使用以下命令建立/etc/adjtime:
cat > /etc/adjtime << "EOF"
0.0 0 0.0
0
LOCAL
EOF
如果第一次启动时/etc/adjtime 文件不存在, systemd-timedated 会认为硬件时钟设置成 UTC 并且以此调整该文件。 你也可以使用 timedatectl 程序来告诉 systemd-timedated 你的硬件时钟是 UTC 还是本地时间:
timedatectl set-local-rtc 1
timedatectl 也可以用来更改系统时间和时区。 要更改当前系统时间,使用下面的命令:
timedatectl set-time YYYY-MM-DD HH:MM:SS
硬件时钟也会相应更新。要更改当前时区,使用下面命令:
timedatectl set-timezone TIMEZONE
你可以通过运行下面命令查看可用时区列表:
timedatectl list-timezones
5.4.3 配置 Linux 控制台
如果键盘布局是标准 us 键盘的话,可以忽略此配置。
不过作为和 System V 的区别,systemd 可以通过配置 systemd-vconsole-setup 系统服务,配置控制台字体和控制台的键盘映射。
systemd-vconsole-setup 服务读取/etc/vconsole.conf 中的配置信息,确定使用的键盘类型和屏幕的字体。http://www.tldp.org/HOWTO/HOWTO-INDEX/other-lang.html 页面有很多其他语言的「HOWTO」内容可以给你很多帮助。 localectl list-keymaps 可以列举出所有可用的终端键盘布局。/usr/share/consolefonts 目录提供了所有可用的字体。
/etc/vconsole.conf 的每一行都应该形如:VARIABLE="value"。表列举了可用的 VARIABLE:
| KEYMAP | 此变量指定了键盘的按键映射表。如未设置,默认为 us |
|---|---|
| KEYMAP_TOGGLE | 此变量指定配置第二个切换键盘映射,默认不设置 |
| FONT | 此变量指定虚拟控制台的字体 |
| FONT_MAP | 此变量指定要使用的控制台映射 |
| FONT_UNIMAP | 此变量指定 Unicode 字体映射 |
由于中文没有必要设置,这里就不罗列配置的示例是德语的。
cat > /etc/vconsole.conf << "EOF"
KEYMAP=de-latin1
FONT=Lat2-Terminus16
EOF
同时,可以使用 localectl 实用程序更改 KEYMAP 值:
localectl set-keymap MAP
5.4.4 配置系统语言环境
本地语言的支持依赖于 /etc/locale.conf,它包含不少和此相关的环境变量。更改此文件后,可能会出现以下的变化:
- 程序的输出将以本地语言展示;
- 修正字符在字母、数字等类型的分类。对于非英语区域设置来说,只有这样,bash 才能正常显示非 ASCII 字符;
- 国家顺序可以按照字母顺序正常排序
- 默认纸张尺寸;
- 货币、时间和日期值的格式。
请将/etc/locale.conf 中
locale -a
字符映射表可能存在很多的别名,比如 ISO-8859-1 可以写作 iso8859-1 或 iso88591。 但是有一些程序不支持这些乱七八糟的写法(比如 UTF-8 只能写作 UTF-8,utf8 就不认识了)。 所以,为了安全起见,在设置的时候还是尽量的使用特定区域设置的规范名称。可以通过以下命令查询在特定区域下的字符映射表标准名称,
LC_ALL=<locale name> locale charmap
对于「zh_CN.utf8」以上命令将会如下输出:
UTF-8
根据以上输出,再次修改/etc/locale.conf,将字符映射表设置为标准形式(zh_CN.utf8 变为 zh_CN.UTF-8)。同理,也可一并查询以下设置的标准命令,然后将其添加到 bash 的启动文件中 。
LC_ALL=<locale name> locale language
LC_ALL=<locale name> locale charmap
LC_ALL=<locale name> locale int_curr_symbol
LC_ALL=<locale name> locale int_prefix
以上的命令将会输出当前区域设置的语言、字符编码、本地货币单位以及电话国际编码。如果出现类似下文的错误输出,可能是你没有严格按照第 4 章指导的方法操作或者是你当前所用的 Glibc 不支持该地域。
locale: Cannot set LC_* to default locale: No such file or directory
如果这种情况真的发生,你应该使用 localedef 命令安装对应的系统区域,或者考虑更改为其他的区域。假如没有出现错误提示,我们就可以继续进行下一步操作了!
有一些 LFS 之外的包可能会出现对你设置的区域支持很差的情况。比如 X 的库(X Windows System 的一部份), 就可能在内部文件中输出以下消息:
Warning: locale not supported by Xlib, locale set to C
在一些情况下,Xlib 希望以带规范破折号的大写形式列出字符映射表,比如 ISO-8859-1 而不应该写作 iso88591。不过,也可以通过去除区域规范中的字符映射部分找到合适的规范。这可以通过运行 locale charmap 命令来检查。例如,需要更改 de_DE.ISO-8859-15@euro 为 de_DE@euro 以便 Xlib 能识别区域。
即便如此,也可能遇到某些程序因为区域设置和它们预置的不同而导致功能异常(可能不会显示任何的错误消息)。 如果出现这样的情况,可以通过查看其他的发行版是如何进行设置区域的,从而得到启发。 确定了区域后,就可以创建 /etc/locale.conf 文件了:
cat > /etc/locale.conf << "EOF"
LANG=<ll>_<CC>.<charmap><@modifiers>
EOF
也可以通过 systemd 提供的实用程序 localectl 修改/etc/locale.conf:
localectl set-locale LANG="<ll>_<CC>.<charmap><@modifiers>"
也可以指定其他和语言相关的环境变量,例如 LANG、LC_CTYPE、LC_NUMERIC 等。以下示例中,将 LANG 设置为 zh_CN.UTF8,LC_CTYPE 设置为 zh_CN:
localectl set-locale LANG="zh_CN.UTF8" LC_CTYPE="zh_CN"
其他配置文件
本节 System V 和 systemd 版本配置方法一致。
5.5.1 创建/etc/inputrc 文件
inputrc 文件的作用是告知系统应该以怎样的键盘布局处理键盘。此文件对于 readline —— 输入相关库,或者是一些 shell(例如 bash 等)来说十分重要。
对于大部分用户来说,都不使用那些奇奇怪怪的键盘映射,所以,可以通过以下的命令建立一个全局的/etc/inputrc 以供所有用户使用。如果需要更改某一个用户的键盘映射,仅需要在那个用户的 HOME 目录下建立一个.inputrc 文件,然后修改对应的键盘映射就可以了。
如果需要了解更多有关如何编辑 inputrc 文件的信息,可以查看 info bash 中 Readline Init File 小节的内容。其实查看 info readline 也可以获取到不少有用的信息。
下面显示的就是通用的 inputrc 文件,其中包含有“#”的都是注释行。需要注意的是,此文件不支持在设置后跟随注释。使用以下命令创建此文件:
cat > /etc/inputrc << "EOF"
# Begin /etc/inputrc
# Modified by Chris Lynn <roryo@roryo.dynup.net>
# Allow the command prompt to wrap to the next line
set horizontal-scroll-mode Off
# Enable 8bit input
set meta-flag On
set input-meta On
# Turns off 8th bit stripping
set convert-meta Off
# Keep the 8th bit for display
set output-meta On
# none, visible or audible
set bell-style none
# All of the following map the escape sequence of the value
# contained in the 1st argument to the readline specific functions
"\eOd": backward-word
"\eOc": forward-word
# for linux console
"\e[1~": beginning-of-line
"\e[4~": end-of-line
"\e[5~": beginning-of-history
"\e[6~": end-of-history
"\e[3~": delete-char
"\e[2~": quoted-insert
# for xterm
"\eOH": beginning-of-line
"\eOF": end-of-line
# for Konsole
"\e[H": beginning-of-line
"\e[F": end-of-line
# End /etc/inputrc
EOF
5.5.2 创建/etc/shells 文件
shells 文件是当前系统所有可用 shell 的列表文件。应用程序通过读取它,可以知道需要使用的 shell 是否有效。每行指定一个 shell 的绝对路径,即从根目录(/)开始的路径。例如,当非特权用户想要使用 chsh 命令更改自己登录所用的 shell 时。如果命令没有在/etc/shell 中找到,那么将会拒绝更改。
这个文件对于某些程序来说是必需的,比如 GDM 在找不到/etc/shells 时就不会启用头像登录界面(Face Browse),还有 FTP 守护进程通常会禁止使用不在这个文件里列出终端的用户登录。
cat > /etc/shells << "EOF"
# Begin /etc/shells
/bin/sh
/bin/bash
# End /etc/shells
EOF
本章小节
本章节主要用于说明 Linux 中系统的相关配置,帮助读者完成 LFS 系统的配置任务。其中的内核、引导尤为重要,务必在系统重启前认真配置,而其他的配置其实可以在重启后再实施。systemd 作为后起之秀正在逐步取代 System V,但很多机器上运行的仍然是 System V。本章介绍了这两种管理方式的优劣,配置和使用方式。以及 LFS SystemV 和 systemd 两种版本不同的配置方式。
下一步
本书已步入尾声,经过前几章节的构建和配置,我们的自制 Linux 已然完成,让我们最后收个尾,并做一个总结。
记录系统信息
首先恭喜大家,实质性的构建和配置已经完成。不过为了记录系统信息,同时也是为了遵守 Linux Standards Base (LSB),还需要创建几个文件。
运行以下命令创建/etc/lsb-release 文件,以记录当前系统的完整信息。
cat > /etc/lsb-release << "EOF"
DISTRIB_ID="Linux From Scratch"
DISTRIB_RELEASE="10.0-systemd"
DISTRIB_CODENAME="<your name here>"
DISTRIB_DESCRIPTION="Linux From Scratch"
EOF
「DISTRIB_CODENAME」用于记录构建者。如果你按照本书的说明,构建 LFS 直至此处,那便可以在
「DISTRIB_RELEASE」用于记录 LFS 版本,指令中填写的是 systemd 的版本,System V 可以改成“10.0”或“10.0-SystemV”。
如果构建的 LFS 版本与本书不同,请根据自身情况进行调整。该文件经常在日常的使用和维护中使用,以明确系统的版本信息。为了方便日后使用和维护,请尽可能保证内容的准确。
如果构建的是 LFS 的 systemd 版本,还需要额外创建一个/etc/os-release 文件。许多软件在安装时会通过该文件查看系统信息。
cat > /etc/os-release << "EOF"
NAME="Linux From Scratch"
VERSION="10.0-systemd"
ID=lfs
PRETTY_NAME="Linux From Scratch 10.0-systemd"
VERSION_CODENAME="<your name here>"
EOF
运行以下命令以创建 LFS 的版本文件/etc/lfs-release:
echo 9.0-systemd > /etc/lfs-release
同样的这里是以 systemd 举例,System V 版本请务必把“9.0-systemd”改成“9.0”或“9.0-SystemV”等。
该文件是 LFS 独有的配置文件,其目的就是记录你当前 LFS 系统的版本。如果你在使用中产生问题,在提出的问题前附上版本是非常有必要的,这能帮助你及愿意帮助你的人快速定位并解决问题。 所以务必保证正确填写版本信息哦!
注册一个 LFS 用户
访问 http://www.linuxfromscratch.org/cgi-bin/lfscounter.php,注册 LFS 用户并记录你安装的版本。成为注册用户的其中之一,是 LFS 对你成果的表彰,也是你对 LFS 的支持。
重启
辛苦了那么久,是该初次启动我们崭新的 LFS 系统的时候了!首先,请退出 chroot 环境:
logout
然后卸载虚拟文件系统:
umount -v $LFS/dev/pts
umount -v $LFS/dev
umount -v $LFS/run
umount -v $LFS/proc
umount -v $LFS/sys
卸载 LFS 文件系统本身:
umount -v $LFS
如果还建立了其它的挂载点,需要在卸载 LFS 文件系统之前先卸载它们:
umount -v $LFS/usr
umount -v $LFS/home
umount -v $LFS
最后,运行指令重启系统:
shutdown -r now
运行指令后,等机器再次启动的时候,进入的便是你自制的 Linux 系统中了。
常见问题与处理方式
在构建过程中会出现各种各样的问题,有些问题经常发生,也经常被问及。在这里将问题的现象和大致的处置措施总结如下,希望大家在发生问题的时候也能保持冷静,根据现象去寻求正确的解决方法。
-
GRUB 配置问题
- 现象:重启后无法进入系统、会进入 grub rescue 界面、甚至找不到引导。
- 原因:每个人的系统的环境也不相同,分区、安装双系统、使用的是 UEFI 都会导致命令和书中的例子有出入。照搬书中的例子去配置,失败的可能性很高。
- 措施:使用安装宿主机时用到的 iso,挂载/boot 分区更改配置即可,如果没有独立的/boot 分区就挂载/(根分区)。
-
其他配置问题
- 现象:你的配置没有生效。类似于网络无法访问、系统时间错误、键盘设置错误。
- 原因:配置未正确配置
- 措施:比 GRUB 配置更小的错误,大多数情况下不会影响系统的启动,系统正常启动后在重新配置即可。如果严重影响到自己配置可以参考 GRUB 配置问题,挂载/(根分区)找到并更改配置文件,或者 chroot 使用命令更改配置皆可。
-
软件包安装错误
- 现象:构建过程出现失败提示。
- 原因:环境配置错误或依赖的软件包安装错误。
- 措施:根据错误提示定位到自己安装错误的软件包,找到出错的原因,然后重新构建错误环节,再来一遍的时候务必仔细。这里推荐重新构建错误环节,也就是临时系统有问题,删掉$LFS/tools 重新构建临时系统,正式构建有问题清空$LFS 中$LFS/tools 以外的目录后重新开始构建。
- 注意:出现失败或者错误信息,并一定是软件包构建错误的原因。有些失败是可以忽略,其中有些在页面中已经记载,有些可能没有记载。原则是不影响后续构建就不是问题。反之如果影响到后续构建了,推荐重新构建该环节,因为你无法判断,在出错之后运行指令哪些生效了,哪些没生效。
-
没有正确设置$LFS
- 现象:无明显现象,但是自构建临时系统起,如果你的$LFS 没有正确设置,会错的乱七八糟,不可能构建成功。
- 预防措施:这个问题是三令五申过的,是不应该发生的问题。多发生在中断构建并重新回来开始构建的时候。能避免这个问题的做好方法就是每次重新开始构建要 chroot 前检查一下$LFS。
-
构建软件包前没有解压并进入软件包
- 该问题多发生于第一次构建 LFS,且没有认真阅读手册中通用编译指南章节的读者。由于问题过于常见,为此本书区别于 LFS 手册,直接把解压并进入软件包和退出并清理软件包的命令也完整的写出来了。
LFS 并没有很难,至少按照手册的推荐运行指令,可以很容易的完成构架工作。但 LFS 同样也没有很简单,至少不能将构建工作等同于复制粘贴。初学者常常不看指令的描述,甚至连标注注意的段落都不看,单纯的复制粘贴指令,然后发生了种种低级错误,再回过头提问,使用搜索引擎等。这样非常消磨时间和构建者本身的意志力。笔者更推荐读者将构建系统这个看得见的目标放下,转而去学习,去体会 LFS 的构建逻辑,去理解 Linux 系统的组成,构建本身只是践行所学知识的一种实践方式而已。
希望通过阅读本书,读者可以学到 Linux 构成的相关知识,享受自制 Linux 的过程。
接下来做什么
十分感谢你们可以耐心的读完本书。
看到标题,你可能也在想,接下来做什么呢?
首当其冲,自然是 BLFS http://www.linuxfromscratch.org/blfs/。因为刚构建完的 LFS 所包含的功能,远远无法满足我们的日常使用。即使你只是想要构建一个最小的系统,去完成某些单一的任务。相信刚刚登录过系统的你,可以理解我想说的是一种什么感觉。但最好别急,是否需要构建 BLFS,该怎么构建 BLFS 其实是个很漫长的思考过程。笔者并不推荐将 LFS 作为日常使用的系统,所以自然也不推荐在 LFS 大动干戈构建更多的软件包。
当然尽管如此,BLFS 手册还是构建完 LFS 之后最佳的推荐读物,你可能需要 Lynx 帮你浏览网页,可能还需要 sudo 来管理账户,可能需要 wget 来下载软件,可能还需要桌面等,BLFS 能帮助你很多。不过需求可能很多,需要构建的软件包可能更多。他们可能包含在 BLFS 工程中,也可能需要你自行构建。花费的时间和精力远远超过构建一个 LFS 的 SBU,当然成就感也会随之提升。
接着,最好定期检查软件的 bug 和安全公告。因为在从源码构建出 LFS 之后,你便应该养成经常去查看这些报告的好习惯。有关查询的去处,网上倒是有一些不错的资源,这里列举几个:
CERT(计算机应急响应小组)
CERT 有一个邮件列表,专门公示各种操作系统和应用程序的安全警报。订阅信息请点击此链接查看:http://www.us-cert.gov/cas/signup.html。
Bugtraq
Bugtraq 是一个专门公示计算机安全的邮件列表。它公示新发现的安全问题,偶尔还会尽可能的提出修补方案。订阅信息请点击此链接查看:http://www.securityfocus.com/archive。
然后,如果力所能及的话,其实还有以下几种方式可以去帮助 LFS 以及 Linux 的发展。
LFS Hints
LFS Hints 是由 LFS 社区的志愿者提交的教育文集。有关信息访问以下网址取得: http://www.linuxfromscratch.org/hints/list.html。
邮件列表
linuxfromscratch.org 的服务器上部署了一些和 LFS 项目开发工作相关的邮件列表。其中包括主要开发列表和支持列表,以及一些其他相关话题的讨论列表。如果 FAQ 不能解决您的问题,那么您可以在此搜索邮件列表:http://www.linuxfromscratch.org/search.html。
The Linux Documentation Project(TLDP,Linux 文档项目)
Linux 文档项目(TLDP)的目标是通过协作来完善 Linux 文档中的所有不足。TLDP 已经完成了大量的 HOWTO、指南和 man 帮助页。它的网站是:http://www.tldp.org/.
最后,当然也要为我们自己打个广告啦!LCTT 非常欢迎大家加入,无论是致力于维护 LFS、还是翻译其他的文档资料、或是想要发表自己的原创内容,我们都非常欢迎。