13.2. FreeBSD 的启动过程

开启计算机和启动操作系统带来了一个有趣的难题。根据定义,在操作系统启动之前,计算机不知道如何做任何事情,包括从磁盘上运行程序。如果计算机在没有操作系统的情况下不能从磁盘上运行程序,而操作系统的程序又在磁盘上,那么操作系统是如何启动的?

这个问题与《吹牛大王历险记》一书中的情节类似。一个人掉入了窨井里,通过提起鞋带就把自己拉了出来。在早期的计算机中,bootstrap 这个词被应用于用于加载操作系统的机制。此后,它被缩短为 booting

在 X86 硬件上,基本输入/输出系统(BIOS)负责加载操作系统。BIOS 在硬盘上寻找主引导记录(MBR),该记录必须位于磁盘上的一个特定位置。BIOS 有足够的能力来加载和运行 MBR,并假设 MBR 随后可以执行加载操作系统所涉及的其余任务,并有可能要在 BIOS 的帮助下进行。

注意:
FreeBSD 提供了从旧的 MBR 标准和新的 GUID 分区表(GPT)启动的功能。GPT 分区通常出现在使用统一可扩展固件接口(UEFI)的计算机上。然而,FreeBSD 可以使用 gptboot(8) 以从 只有传统 BIOS 的机器上的 GPT 分区启动。目前正在努力提供直接的 UEFI 启动。

MBR 中的代码通常被称为 boot manager ,尤其是当它与用户进行交互时。引导管理器通常在磁盘的第一个磁道或文件系统内有更多的代码。引导管理器的例子包括标准的 FreeBSD 引导管理器 boot0(也叫 Boot Easy) ,以及被许多 Linux® 发行版使用的 Grub

如果只安装了一个操作系统,MBR 会在磁盘上搜索第一个可启动的(活动的)分区,然后运行这个分区上的代码来加载操作系统的剩余部分。当有多个操作系统时,可以安装一个不同的引导管理器来显示操作系统的列表,这样用户就可以选择一个来启动。

FreeBSD bootstrap 系统的其余部分被分为三个阶段。第一阶段只知道让计算机进入一个特定的状态并运行第二阶段。第二阶段可以多做一些事,然后运行第三阶段。第三阶段完成加载操作系统的任务。这项工作被分成三个阶段是因为 MBR 对第一和第二阶段可以运行的程序的大小进行了限制。将这些任务串联起来,就可以使 FreeBSD 提供一个更灵活的加载器。

内核随后被启动,同时开始探测设备并初始化它们以便使用。内核启动过程结束以后,内核将控制权移交给用户进程init(8)。它将确保磁盘处于可用状态,启动用户级资源配置,挂载文件系统,设置网卡以便在网络上通信,并启动已经配置好的启动时运行的进程。

这一节更详细地描述了这些阶段,并演示了如何与 FreeBSD 启动程序进行交互。

13.2.1. 引导管理器

MBR 中的引导管理器代码有时被称为引导过程的第零阶段。默认情况下,FreeBSD 使用 boot0 引导管理器。

由 FreeBSD 安装程序安装的 MBR 是基于 /boot/boot0 的。由于分区表和 MBR 末尾的 0x55AA 标识符,boot0 的大小被限制在 446 字节。如果 boot0 和多个操作系统被安装,在启动时将会显示类似这个例子的信息。

示例 1. boot0 屏幕截图

  1. F1 Win
  2. F2 FreeBSD
  3. Default: F2

如果在 FreeBSD 之后安装其他操作系统,它们就会覆盖现有的 MBR。如果发生这种情况,或者想用 FreeBSD 的 MBR 替换现有的 MBR,请使用下面的命令:

  1. # fdisk -B -b /boot/boot0 device

其中 device 代表着启动盘,比如 ad0 代表第一个 IDE 磁盘,ad2 代表第二个 IDE 控制器上的第一个 IDE 磁盘,或者 da0 代表第一个 SCSI 磁盘。要创建 MBR 的自定义配置,请参考 boot0cfg(8)

13.2.2. 第一阶段和第二阶段

从概念上讲,第一阶段和第二阶段是同一程序的两个部分,在磁盘的同一区域。由于空间的限制,它们被分成了两个,但总是被安装在一起。它们是由 FreeBSD 安装程序或 bsdlabel 从合并的 /boot/boot 中复制出来的。

这两个阶段位于文件系统之外,在 boot slice 的第一个磁道上,从第一个扇区开始。这是 boot0 或任何其他引导管理器期望找到一个可以继续引导过程的程序的地方。

第一阶段,boot1,非常简单,因为它只能有 512 字节大小。它对FreeBSD bsdlabel 的了解只够找到并执行 boot2bsdlabel 存储了关于分片的信息。

第二阶段,boot2,稍微复杂一些,它对 FreeBSD 文件系统的理解足以找到文件。它可以提供一个简单的界面来选择要运行的内核或加载器。它运行更复杂的 loader,并提供一个引导配置文件。如果启动过程在第二阶段被打断,将显示以下交互式屏幕:

示例 2. boot2 屏幕截图

  1. >> FreeBSD/i386 BOOT
  2. Default: 0:ad(0,a)/boot/loader
  3. boot:

使用 bsdlabel 可以替换已安装的 boot1boot2。其中 diskslice 是要启动的磁盘和分区,例如 ad0s1 表示第一个IDE磁盘上的第一个分区。

  1. # bsdlabel -B diskslice

警告:
如果只使用磁盘名称,如ad0bsdlabel将在“危险的专用模式”下创建磁盘(没有分区)。这可能不是我们想要的结果,所以在按下回车键之前,请仔细检查磁盘分区。

13.2.3. 第三阶段

loader 是三阶段引导过程的最后阶段。它位于文件系统中,通常为 /boot/loader

loader 的目的是作为一种交互式的配置方法,使用内置的命令集,由一个更强大的解释器来支持,该解释器有一个更复杂的命令集。

在初始化过程中,loader 将探测终端和磁盘,并检测出它是从哪个磁盘启动的。它将相应地设置变量,并启动一个解释器,用户命令可以用脚本或交互式的方式传递。

然后,加载器将读取 /boot/loader.rc。默认情况下,它会读取 /boot/defaults/loader.conf,并为变量设置合理的默认值。然后读取 /boot/loader.conf 对这些变量进行本地修改。loader.rc 会根据这些变量进行处理,加载所选择的模块和内核。

最后,默认情况下,loader 会用10秒的时间等待按键。如果没有被打断,就启动内核。如果被打断,用户会看到一个可理解的命令集的提示,在这里用户可以调整变量,卸载所有模块,加载模块,最后启动或重启。loader 内置命令列出了最常用的loader命令。对于所有可用命令的完整讨论,请参考 loader(8)

表 1. Loader 的内置命令

变量 说明
autoboot seconds 如果在给定的时间跨度内没有被打断,就继续启动内核,单位是秒。它显示一个倒计时,默认的时间跨度是10秒。
boot [-options] [kernelname] 立即启动内核,并使用任何指定的选项或内核名称。在命令行上提供内核名称只适用于已经声明过 unload 的情况。否则,将使用先前加载的内核。如果 kernelname 没有限定,它将在 /boot/kernel/boot/modules 下搜索。
boot-conf 根据指定的变量(最常见的是内核),经历同样的自动配置模块的过程。这只有在先使用 unload,然后再改变一些变量时才有意义。
help [topic] 显示从 /boot/loader.help 读取的帮助信息。如果给出的 topicindex,则显示可用的 topic 列表。
include filename … 读取指定的文件并逐行进行解释。如果出现错误,立即停止 include
load [-t type] filename 加载内核、内核模块或所给类型的文件,并指定文件名。在文件名后面的任何参数都被传递给文件。如果文件名没有限定,它将在 /boot/kernel/boot/modules 下搜索。
ls [-l] [path] 显示给定路径中的文件清单,如果没有指定路径,则显示根目录。如果指定了 -l,文件的大小也将被显示。
lsdev [-v] 列出所有可能加载模块的设备。如果指定了-v,就会打印更多的细节。
lsmod [-v] 显示已加载的模块。如果指定了 -v,会显示更多的细节。
more filename 显示指定的文件,每显示一行就 pause 一次。
reboot 立即重启系统。
set variable, set variable=value 设置指定的环境变量。
unload 移除所有已加载的模块。

下面是一些使用加载器的实际例子。

在单用户模式下启动通常使用的内核:

  1. boot -s

卸载通常使用的内核和模块,然后加载以前使用的的或另一个指定的内核:

  1. unload
  2. load /path/to/kernelfile

使用限定的 /boot/GENERIC/kernel 来指代安装时附带的默认内核,或者使用 /boot/kernel.old/kernel 来指代系统升级前或配置自定义内核前安装的内核。

使用下面的方法来加载其他内核的常用模块。注意,在这种情况下,不需要限定名称:

  1. unload
  2. set kernel="mykernel"
  3. boot-conf

加载一个自动化的内核配置脚本:

  1. load -t userconfig_script /boot/kernel.conf

13.2.4. 最后阶段

一旦内核被 loader 或绕过 loaderboot2 加载,它就会检查所有引导标志,并根据需要调整其行为。Kernel Interaction During Boot 列出了常用的引导标志。更多关于其他引导标志的信息请参考 boot(8)

表 2. 内核在启动过程中的相互作用

选项 说明
-a 在内核初始化过程中,要求将该设备作为根文件系统挂载。
-C 从CDROM引导根文件系统。
-s 引导到单用户模式。
-v 在内核启动过程中更加详细地说明。

内核完成了启动以后,它就把控制权交给用户进程 init(8),它位于 /sbin/init,或者在 loaderinit_path 变量中指定的程序路径。这是启动过程的最后阶段。

启动顺序确保系统中可用的文件系统是一致的。如果UFS文件系统不一致,并且 fsck 不能修复不一致的地方,init 将系统降到单用户模式,这样系统管理员可以直接解决这个问题。否则,系统会启动到多用户模式。

13.2.4.1. 单用户模式

用户可以通过使用 -s 启动或在加载器中设置 boot_single 变量来指定这种模式。它也可以通过在多用户模式下运行 shutdown now 来达到。单用户模式以这个信息开始:

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

如果用户按回车键,系统将进入默认的 Bourne shell。要指定一个不同的 shell,请输入 shell 的完整路径。

单用户模式通常用于修复由于文件系统不一致或启动配置文件错误而无法启动的系统。当根密码未知时,它也可以用来重置根密码。这些操作是可能的,因为单用户模式的提示给了系统和它的配置文件完全的、本地的访问权。在这种模式下没有网络。

虽然单用户模式对修复系统很有用,但它会带来安全风险,除非系统处于一个物理上安全的位置。默认情况下,任何能够获得系统物理访问权的用户,在启动到单用户模式后,将完全控制该系统。

如果在 /etc/ttys 中把系统控制台改为不安全的,那么在启动单用户模式之前,系统会首先提示根密码。这增加了一种安全措施,同时取消了在根密码未知时重置根密码的能力。

示例 3. 在 /etc/ttys 中配置一个不安全的控制台

  1. # name getty type status comments
  2. #
  3. # If console is marked "insecure", then init will ask for the root password
  4. # when going to single-user mode.
  5. console none unknown off insecure

不安全的控制台意味着控制台的物理安全性被认为是不安全的,所以只有知道根密码的人可以使用单用户模式。

13.2.4.2. 多用户模式

如果 init 发现文件系统没有问题,或者假设用户在单用户模式下完成了他们的命令并键入 exit 离开单用户模式,系统就会进入多用户模式,在这个模式下,它开始对系统进行资源配置。

资源配置系统从 /etc/defaults/rc.conf 中读取配置默认值,从 /etc/rc.conf 中读取系统的具体细节。然后,它开始装载 /etc/fstab 中列出的系统文件系统。它启动网络服务、各种系统守护程序,然后启动本地安装的软件包的启动脚本。

要了解更多关于资源配置系统的信息,请参考 rc(8) 并检查位于 /etc/rc.d 中的脚本。