# 15.2.FreeBSD 的引导过程

打开计算机并启动操作系统这个过程本身就提出了一个有趣的难题。根据定义，在操作系统启动之前，计算机本身什么也不会做，包括无法从磁盘运行程序。如果计算机在没有操作系统的情况下无法从磁盘运行程序，而操作系统的程序又正好存放在磁盘上，那操作系统究竟是如何被启动的呢？

这个问题类似于《吹牛大王历险记》中的一个情节：主人公掉进了一个下水道井盖的一半深度，却靠拉起自己靴子上的提带将自己救了出来。在计算机发展的早期，“bootstrap”（拔鞋带）这个词就是用来形容启动操作系统的机制的。这个词后来被缩写为“booting”（启动）。

在 x86 硬件上，基本输入输出系统（BIOS）负责加载操作系统。BIOS 会在硬盘上查找主引导记录（MBR），该记录必须位于磁盘上的特定位置。BIOS 有能力加载并运行 MBR，并假设 MBR 能够继续执行加载操作系统的其余任务，可能还会借助 BIOS 的帮助。

> **注意**
>
> FreeBSD 支持从旧的 MBR 标准和较新的 GUID 分区表（GPT）启动。GPT 分区方式通常用于带有统一可扩展固件接口（UEFI）的计算机。不过，即使是在仅有传统 BIOS 的机器上，FreeBSD 也可以使用 [gptboot(8)](https://man.freebsd.org/cgi/man.cgi?query=gptboot\&sektion=8\&format=html) 从 GPT 分区启动。目前正在开发直接从 UEFI 启动的支持。

MBR 中的代码通常被称为 *引导管理器*（boot manager），尤其是当它可以与用户交互时。引导管理器通常包含磁盘第一磁道或文件系统中的更多代码。引导管理器的例子包括 FreeBSD 的标准 boot manager —— boot0（也叫 Boot Easy），以及许多 Linux® 发行版使用的 GNU GRUB。

> **注意**
>
> 使用 GRUB 的用户请参考 [GNU 提供的文档](https://www.gnu.org/software/grub/grub-documentation.html)。

如果只安装了一个操作系统，MBR 会寻找磁盘上第一个可引导（active）分区，并运行该分区上的代码来加载操作系统的其余部分。当存在多个操作系统时，可以安装一个不同的引导管理器，用来显示操作系统列表，让用户选择要引导的系统。

FreeBSD 的其余引导系统分为三个阶段。第一阶段仅知道如何将计算机引导到特定状态并运行第二阶段。第二阶段可以执行更多的操作，然后运行第三阶段。第三阶段完成加载操作系统的任务。将工作分成三个阶段是因为 MBR 对第一阶段和第二阶段所能运行的程序大小有限制。通过链式加载各阶段任务，FreeBSD 能够提供更灵活的加载器。

之后内核被启动，并开始探测设备并初始化它们以供使用。待内核启动过程完成，内核将控制权交给用户进程 [init(8)](https://man.freebsd.org/cgi/man.cgi?query=init\&sektion=8\&format=html)，该进程负责确保磁盘处于可用状态，启动用户级资源配置，挂载文件系统，设置网络接口与网络通信，并启动在系统启动时配置为运行的进程。

本节将更详细地解释这些阶段，并展示如何与 FreeBSD 启动过程进行交互。

## 15.2.1. 引导管理器（The Boot Manager）

MBR 中的引导管理器代码有时被称为启动过程的 *第零阶段*（stage zero）。在默认情况下，FreeBSD 使用 boot0 引导管理器。

FreeBSD 安装器安装的 MBR 基于 **/boot/boot0**。由于分区表和位于 MBR 末尾的 `0x55AA` 标识符的存在，boot0 的大小和功能被限制在 446 字节以内。如果安装了 boot0 且存在多个操作系统，启动时将显示类似以下的消息：

**示例 1. boot0 截图**

```sh
F1 Win
F2 FreeBSD

Default: F2
```

如果在安装 FreeBSD 后安装其他操作系统，它们通常会覆盖现有的 MBR。如果发生这种情况，或需要用 FreeBSD MBR 替换当前 MBR，可使用以下命令：

```sh
# fdisk -B -b /boot/boot0 device
```

其中 *device* 是引导磁盘，例如第一块 IDE 磁盘为 **ad0**，第二个 IDE 控制器上的第一块 IDE 磁盘为 **ad2**，第一块 SCSI 磁盘为 **da0**。要创建 MBR 的自定义配置，请参考 [boot0cfg(8)](https://man.freebsd.org/cgi/man.cgi?query=boot0cfg\&sektion=8\&format=html)。

## 15.2.2. 第一阶段与第二阶段（Stage One and Stage Two）

从概念上讲，第一阶段和第二阶段属于位于磁盘同一区域的同一个程序。由于空间限制，它们被拆分成两个部分，但总是一起安装。它们是从合并后的 **/boot/boot** 复制而来的，由 FreeBSD 安装器或 `bsdlabel` 完成。

这两个阶段位于文件系统之外，在引导分区（boot slice）的第一磁道，从第一个扇区开始。boot0 或其他任何引导管理器都会在这里寻找一个可以运行、用以继续引导过程的程序。

第一阶段的 **boot1** 非常简单，因为它的大小只能是 512 字节。它只知道如何解析 FreeBSD 的 *bsdlabel*（用于存储分区信息），以便找到并执行 **boot2**。

第二阶段 **boot2** 略为复杂一些，它了解 FreeBSD 的文件系统结构，足以找到文件。它可以提供一个简单的界面，用于选择要运行的内核或加载器（loader）。它将运行加载器（loader），后者功能更强大，并支持使用启动配置文件。如果启动过程在第二阶段被中断，将显示如下交互式界面：

**示例 2. boot2 截图**

```sh
>> FreeBSD/i386 BOOT
Default: 0:ad(0,a)/boot/loader
boot:
```

要替换当前安装的 **boot1** 和 **boot2**，可以使用 `bsdlabel` 命令，其中 *diskslice* 是要从中引导的磁盘和分区，例如第一块 IDE 磁盘的第一个分区是 **ad0s1**：

```sh
# bsdlabel -B diskslice
```

> **警告**
>
> 如果仅仅指定磁盘名，例如 **ad0**，`bsdlabel` 会将磁盘设置为“危险专用模式”（dangerously dedicated mode），即不使用分区。这通常不是用户期望的操作，因此在按下 `回车` 前务必再次确认 *磁盘切片* 的名称。

## 15.2.3. 第三阶段（Stage Three）

加载器（loader）是三阶段引导过程的最后一个阶段。它位于文件系统中，通常是 **/boot/loader**。

加载器被设计为一个交互式配置工具，具备内建命令集，并配有一个更强大的解释器，支持更复杂的命令集。

初始化过程中，loader 会探测控制台和磁盘，并确定自己是从哪个磁盘启动的。它会据此设置变量，并启动一个解释器，用户可以通过脚本或交互方式传入命令。

随后，loader 会读取 **/boot/loader.rc**，该文件默认会读取 **/boot/defaults/loader.conf** 来设置一组合理的变量默认值，并读取 **/boot/loader.conf** 来加载本地对这些变量的修改。**loader.rc** 会根据这些变量加载所需的模块和内核。

最后，loader 默认会等待 10 秒钟以检测是否有按键操作，若无中断，将启动内核；如果被中断，将进入提示符，用户可以在此使用命令集来调整变量、卸载模块、加载模块，然后执行启动或重启。常用的加载器命令请参见 [Loader Built-In Commands](https://docs.freebsd.org/en/books/handbook/boot/#boot-loader-commands)，完整命令集详见 [loader(8)](https://man.freebsd.org/cgi/man.cgi?query=loader\&sektion=8\&format=html)。

**表 1. 加载器内置命令**

| 变量                                      | 描述                                                                                                                          |
| --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `autoboot *seconds*`                    | 如果在指定秒数内未被中断，则继续启动内核。期间会显示倒计时，默认时间为 10 秒。                                                                                   |
| `boot [-options] [kernelname]`          | 立即启动内核，可指定选项或内核名称。仅当执行过 `unload` 后，才可在命令行中指定内核名，否则将使用之前加载的内核。若未限定 *kernelname* 的路径，将在 */boot/kernel* 和 */boot/modules* 下查找。 |
| `boot-conf`                             | 基于所设定变量（最常见的是 `kernel`）自动配置模块。通常应先执行 `unload`，然后更改变量后再运行此命令。                                                                |
| `help [<topic>]`                        | 显示帮助信息，来源为 **/boot/loader.help**。若指定主题为 `index`，将显示所有可用主题列表。                                                                |
| `include <filename> …`                  | 读取指定文件并逐行解释执行。待遇到错误将立即停止 `include`。                                                                                         |
| `load [-t <type>] <filename>`           | 加载内核、内核模块或指定类型的文件，文件名可以携带参数。若 *filename* 未给出完整路径，则在 */boot/kernel* 与 */boot/modules* 中查找。                                   |
| `ls [-l] [path]`                        | 显示指定路径或根目录（若未指定路径）的文件列表。加上 `-l` 会显示文件大小。                                                                                    |
| `lsdev [-v]`                            | 列出所有可能用于加载模块的设备。加 `-v` 显示更多信息。                                                                                              |
| `lsmod [-v]`                            | 显示当前已加载的模块。加 `-v` 显示详细信息。                                                                                                   |
| `more <filename>`                       | 分页显示指定文件内容，每显示 `LINES` 行暂停一次。                                                                                               |
| `reboot`                                | 立即重启系统。                                                                                                                     |
| `set <variable>`，`set <variable=value>` | 设置指定的环境变量。                                                                                                                  |
| `unload`                                | 卸载所有已加载的模块。                                                                                                                 |

以下是一些加载器实际用法的示例。

以单用户模式启动默认内核：

```sh
boot -s
```

卸载默认内核和模块后加载之前的或另一个指定内核：

```sh
unload
load /path/to/kernelfile
```

使用完整路径 **/boot/GENERIC/kernel** 指向安装时默认的内核，使用 **/boot/kernel.old/kernel** 指向系统升级前或自定义内核前的内核。

使用另一个内核加载常规模块（此处不需完整路径）：

```sh
unload
set kernel="mykernel"
boot-conf
```

加载自动化内核配置脚本：

```sh
load -t userconfig_script /boot/kernel.conf
```

## 15.2.4. 最后阶段（Last Stage）

待内核被 loader 或跳过 loader 的 boot2 加载，它会检查所有启动标志（boot flags），并据此调整其行为。[启动时内核交互](https://docs.freebsd.org/en/books/handbook/boot/#boot-kernel) 中列出了常用的启动标志。更多启动标志的详细信息请参见 [boot(8)](https://man.freebsd.org/cgi/man.cgi?query=boot\&sektion=8\&format=html)。

**表 2. 启动时内核交互**

| 选项   | 说明                          |
| ---- | --------------------------- |
| `-a` | 内核初始化期间，询问要挂载为根文件系统的设备。     |
| `-C` | 从 CDROM 启动根文件系统。            |
| `-s` | 启动为单用户模式（single-user mode）。 |
| `-v` | 内核启动时显示更详细的信息。              |

内核完成启动后，将控制权交给用户进程 [init(8)](https://man.freebsd.org/cgi/man.cgi?query=init\&sektion=8\&format=html)，该进程位于 **/sbin/init**，或由 `loader` 中 `init_path` 变量指定的程序路径。这是整个启动过程的最后阶段。

启动序列会检查系统上可用的文件系统是否一致。如果某个 UFS 文件系统不一致，并且 `fsck` 无法修复这些不一致性，`init` 会将系统切换到单用户模式，以便系统管理员直接解决问题。否则，系统将进入多用户模式（multi-user mode）。

### 15.2.4.1. 单用户模式（Single-User Mode）

用户可以通过添加 `-s` 启动参数，或在 loader 中设置 `boot_single` 变量来进入此模式，也可以在多用户模式下运行 `shutdown now` 进入。单用户模式的开始会显示如下提示：

```sh
Enter full pathname of shell or RETURN for /bin/sh:
```

若用户按 Enter 键，系统将进入默认的 Bourne shell。若想指定不同的 shell，需要输入该 shell 的完整路径。

单用户模式通常用于修复由于文件系统不一致或引导配置文件出错而无法启动的系统，也可以用于在未知 `root` 密码的情况下重设 `root` 密码。因为单用户模式提供了对系统和配置文件的完全本地访问。在此模式下不启用网络功能。

尽管单用户模式非常适合修复系统，但若系统物理环境不安全，则它也带来一定的安全隐患。默认情况下，任何能物理接触系统的用户都可以通过引导进入单用户模式而完全控制该系统。

如果在 **/etc/ttys** 中将系统的 `console` 设置为 `insecure`，系统在进入单用户模式前会先要求输入 `root` 密码。这种方式在增强安全性的同时，也失去了在忘记 `root` 密码时重置的能力。

**示例 3. 在 `/etc/ttys` 中配置不安全控制台（Insecure Console）**

```sh
# 名称  getty 程序                      类型     状态           注释
#
# 如果控制台被标记为 "insecure"，那么在进入单用户模式时，init 会要求输入 root 密码。
console none                            unknown off insecure
```

将控制台设置为 `insecure` 意味着物理访问该控制台被视为不安全，因此只有知道 `root` 密码的用户才能使用单用户模式。

### 15.2.4.2. 多用户模式（Multi-User Mode）

如果 `init` 判断文件系统正常，或者用户在单用户模式下完成操作并输入 `exit` 以退出单用户模式，系统将进入多用户模式，此时开始进行系统资源配置。

资源配置系统会先从 **/etc/defaults/rc.conf** 读取默认配置，然后从 **/etc/rc.conf** 读取系统特有的详细信息。接着，它会挂载 **/etc/fstab** 中列出的文件系统，启动网络服务、各种系统守护进程，以及本地安装的软件包的启动脚本。

要了解有关资源配置系统的更多信息，请参阅 [rc(8)](https://man.freebsd.org/cgi/man.cgi?query=rc\&sektion=8\&format=html)，并查看 **/etc/rc.d** 目录下的脚本。
