# 早期的 FreeBSD 移植

* 原文：[Early FreeBSD Ports](https://freebsdfoundation.org/wp-content/uploads/2023/06/rabson_early_ports.pdf)
* 作者：DOUG RABSON
* 译者：basebyte

自诞生之初，FreeBSD 便专注于为 i386 架构提供稳固的支持。这种 PC 平台普遍易得且相对廉价，将现有资源集中于此有助于使 FreeBSD 在 i386 上保持稳定和高性能。但是，几年后，我们决定扩大支持范围；第一个目标是 DEC 的 Alpha 平台，因为它是 64 位架构的平台，是一个不错的选择。几年之后，我们又增加了对 IA-64 的支持，当时 Intel 将其定位为 i386 的继任者。

## 移植 FreeBSD 到 Alpha 平台

在 1997 年 5 月初，Jordan Hubbard 征求志愿者来将 FreeBSD 移植到 DEC Alpha 平台。Alpha 是一种 64 位的加载/存储体系结构，与 i386 截然不同，它拥有更多的寄存器和 RISC 指令集。当时的硬件使用了我们熟悉的 PCI 和 ISA 总线接口。

我自愿参加了这个项目。DEC 的 Clem Cole 借给了我们一些硬件，这些硬件于 1997 年 7 月运抵（除了 Peter Wemm 的机器，我记得他的机器好像在海关丢失了）。从那时起，我开始逐渐意识到这个项目需要多少工作量，并且需要在许多不同的领域进行改进。

### 设备驱动程序

在 FreeBSD 中，特别是针对 ISA 的设备驱动程序，需要进行修改以支持新的 Alpha 架构，因为硬件访问方式有很大的不同。为了使这项工作顺利进行，并在 i386 和 Alpha 平台之间尽可能共享代码，我们需要一个抽象层。

我对此有一些想法，这些想法最终演变成了 FreeBSD 的 newbus 框架。我的计划是从顶层系统设备总线开始自动发现设备，对于 i386 和 Alpha 来说，顶层系统设备总线通常是指 PCI 和 ISA。内核将动态构建设备树，然后将这些设备与可用的驱动程序进行匹配。我还希望能够将这个功能与早先开发的内核链接器（KLD）结合起来，以允许系统在初始引导后添加驱动程序。我在 1997 年末为此开发了一个原型，但并没有进展到支持任何"真实"的硬件驱动程序的程度。

### 文件格式

在 1997 年，FreeBSD 仍在使用 a.out 文件格式，这是一种非常简单的 32 位格式。而当时大多数其他类 Unix 系统已经在使用 ELF 格式，它提供了更多的灵活性，并且已经支持包括 Alpha 在内的 64 位平台。

改用新格式需要修改编译系统，以支持新格式，并支持动态链接，动态链接是通过运行时链接器（RTLD）在用户区实现的。在 1998 年初的几个月里，包括 John Polstra、Jordan Hubbard、Peter Wemm 和我在内的许多人都在为此而努力。

改用新的文件格式涉及对构建系统的修改，以支持新格式和动态链接。动态链接在用户空间中通过运行时链接器（RTLD）来实现。在 1998 年初的几个月里，包括 John Polstra、Jordan Hubbard、Peter Wemm 和我在内的许多人都在为此而努力。

### 启动

最初这是该项目的一个不确定领域。Alpha 的"预引导"环境非常分散，Digital Unix (DUX) 和 VMS 使用 SRM 控制台，NT 使用 AlphaBIOS，Linux 则同时使用这两者以及它们自己的 MILO。

在 Alpha 上，任何操作系统都需要一种称为 PALcode 的东西来处理虚拟页转换、缓存、中断以及用户模式和内核模式之间的转换。DUX、NT 和 VMS 都有各自的 PALcode 变体。尽管 DUX 的 PALcode 可能是我们最好的选择，但我们并不清楚是否可以在没有昂贵的许可证的情况下使用它。后来，我们支持的所有 Alpha 硬件都附带有支持 DUX PALcode 的 SRM 控制台，因此这个问题不再是问题。

i386 引导启动代码的大小被限制在 7.5 KB。这个限制来自于 UFS 文件系统格式，它为引导程序保留了 8KB 的区域，而我们需要其中的 512 字节的扇区来让 PC BIOS 进行引导。这只够将 a.out 格式的内核文件从根文件系统读入内存并启动它。

Alpha 也有类似的限制；SRM 控制台使用磁盘的第一个扇区来标识要加载的一系列连续扇区，这样与 i386 一样，UFS 引导区只剩下 7.5 KB 的空间。在 Alpha 上，由于 RISC 架构的代码密度较低以及 ELF 格式所需的额外复杂性，受限的 7.5KB 空间可能不能够装下完整的引导程序。最终我们重新编写了引导程序，使得这 7.5KB 的引导区能够加载一个更大的引导程序（在现代的 FreeBSD 系统中称为/boot/loader）。这给了我们足够的灵活性来完全支持在 i386 和 Alpha 上启动 ELF 格式的内核，以及其他新特性，如预加载内核模块、网络启动等等。Mike Smith 负责多阶段引导的工作，Peter Wemm 负责内核模块预加载。不知从什么时候开始，Forth 解释器被添加进来了---我想这是 Jordan Hubbard 的功劳。

### 用户空间

在我们开始这个项目时，FreeBSD 的源代码树并没有为简便的交叉编译而设置。这使得构建用户空间的实用程序具有一些挑战性。John Birrell 致力于在 NetBSD 主机上构建大部分 FreeBSD 源代码树，并成功运行了一个具有相当完整的 FreeBSD 用户空间的 NetBSD 内核系统。

NetBSD 的系统调用接口与 FreeBSD 略有不同，因此 John 早期的用户空间程序开发工作使用了 NetBSD 的 ABI 接口。原生的 FreeBSD 内核需要使用 FreeBSD ABI 的实用程序，但这是一个重要的进展。待我们有了一个可用的内核，从混合的 NetBSD/FreeBSD 系统迁移到一个完整的原生 FreeBSD 系统就相对比较简单了。

### 内核

这可能是整个项目中最大的一部分，它涉及填充内核中的所有与机器相关的部分，为虚拟内存、中断处理、进程上下文切换等提供底层支持。

我通过构建一个 Alpha 交叉编译器，尝试构建内核，看哪些部分无法编译通过，然后填补这些空白，要么使用空的存根，要么从 NetBSD 中导入代码，因为那些代码与 FreeBSD 的代码非常相似。这是一个相当繁琐的过程，花了好几天时间，但最终却得到了一个无法运行的二进制内核文件。

接下来，花费更长时间的移植工作是尝试运行这个内核，看它运行到哪个阶段出现问题，然后解决问题后再进行下一次尝试。为了运行每个测试，我使用了一个名为 SimOS 的工具。它模拟了一台基于 Alpha 的计算机，并配有磁盘和串口等模拟硬件。SimOS 支持使用 gdb 对模拟内核进行调试；这非常有帮助，因为我可以逐步执行非常早期的内核初始化过程，比如设置内核虚拟内存等，然后再进入与机器无关的初始化序列。

为了缩短移植过程，我在适当的地方使用了 NetBSD/alpha 的代码。不幸的是，在一些地方我忽略了 NetBSD 的版权信息。我不得不在提交代码后，在公开场合对这些问题进行更正，这让我感到相当尴尬。这也是 FreeBSD 提交历史被修改的极少数时刻之一---我们删除了版权信息不正确的修订。

在虚拟内存支持方面，使用 NetBSD 的代码是行不通的，因为 FreeBSD 在这方面有很大的不同。Alpha 的页表与 i386 类似，都是采用基于树的三层结构（而 i386 当时使用的是两层结构）。我复制了 i386 的代码并进行了修改，以增加了额外的层级。

Alpha 的初步支持于 1998 年 7 月提交，随后几个月里又推出了对 SimOS 仿真器和真正硬件的支持。FreeBSD 3.0 的发布说明中提到了这一点：“对 DEC Alpha 架构的移植已进入“ALPHA”（haha）状态。”

## 移植 FreeBSD 到 IA-64 平台

这个项目始于 2000 年，当时 Paul Saab 在 Usenix ATC 年度技术会议上带来了一套 IA-64 的文档，并询问我是否有兴趣将 FreeBSD 移植到这个新平台。当时，雅虎是 i386 上 FreeBSD 的大规模用户，他们的一些工作负载已经达到了 32 位平台的限制。

IA-64 架构在多个方面都非常有趣。其指令编码采用 128 位指令束，每个指令束最多包含三条可同时执行的 41 位指令。这使得它具有大型寄存器集，包括 128 个通用寄存器和 128 个浮点寄存器。

通用寄存器分为两组---32 个“静态”寄存器和 96 个“堆叠”寄存器。处理器会从一个大型寄存器池中分配这些寄存器，并在函数调用和返回时自动保存和恢复。每个寄存器是 64 位，外加一个用于推测执行的 NaT（"Not a Thing"）位。

条件执行通过 64 个谓词寄存器实现，每个寄存器都是一个比特位，保存比较指令的结果。每条指令都可以根据谓词寄存器的值进行条件执行。

使用 8 个分支寄存器支持间接跳转（例如函数指针），这有助于进行分支预测。

虚拟内存管理由虚拟散列页表（VHPT）控制，该表包含可能的虚拟地址到物理地址映射的子集。软件 TLB 缺失处理程序用于查找 VHPT 中不存在的映射。VHPT 支持两种格式，一种是 "短 "格式，可用于模拟传统的树形页表；另一种是 "长 "格式，即包含冲突链的简单哈希表。

### 启动

IA-64 硬件使用了 EFI 预启动环境。我在多级引导加载器中添加了对 EFI 的基本支持。在 IA-64 的 EFI 环境中，程序是可重定位的；这需要在 EFI 程序本身中进行处理，而这在调试时可能会很困难。

### 用户空间

移植 FreeBSD 用户空间工具和实用程序的工作相当简单，因为在这个时候，FreeBSD 构建系统已经支持交叉编译，只需要添加 IA-64 版本的底层库代码，比如字符串比较、内存复制和系统调用等。

### 内核

我们很幸运能够使用 HP IA-64 仿真器（SKI），Marcel Moolenaar 利用它制作了 FreeBSD 移植程序。它包括一个指令级调试器，对于调试内核早期初始化和陷阱处理非常有帮助。

与 Alpha 相比，内核的移植稍微困难一些。这次，没有其他可供参考的 BSD 移植版本，所以所有的底层支持都是新代码。由于额外的寄存器状态和推测执行以及堆叠寄存器的复杂性，IA-64 架构要求有两个堆栈，一个用于寄存器，另一个用于常规数据，这使得陷阱处理比大多数其他架构要困难得多。

长格式的 VHPT 最终成为 FreeBSD 的虚拟内存系统的一个合理选择。机器无关的虚拟内存系统通过向平台的 pmap 系统发出请求来建立虚拟地址到物理地址的映射。这些映射就是直接添加到 VHPT 中的。

### 32 位兼容性

在移植过程中，我们使用 Perforce 作为源代码控制工具，当时只有 i386 平台的二进制版本可用。

我希望能在移植过程中在目标平台上使用这个二进制文件，因此我最终实现了 i386 的兼容性，利用了 IA-64 处理器中内置的 i386 支持。这建立在早期 Linux 和 SVR4 仿真工作的基础上，这些工作明确地将系统调用的 ABI 和实现分开。

### 传统

针对 Alpha 的移植工作，促使了大量必要的支持性开发，这些开发帮助形成了现代的 FreeBSD 内核。从 a.out 到 ELF 格式的过渡是 Alpha 移植的必要步骤，但由于 ELF 迅速成为事实上的标准，使得所有平台上都不再使用 a.out，这避免了我们花费大量精力来支持和扩展过时的格式。事实证明，多阶段引导加载器是一个灵活的平台，它使新的架构移植变得更容易，并支持从现代文件系统（如 OpenZFS）引导。newbus 设备框架促进了驱动程序在不同架构之间的兼容性，并支持动态设备发现，这在设备可随时添加或移除的现代系统中是必需的。

增加对 Alpha 的支持迫使我们解决了内核和用户空间的 64 位兼容性问题。加载/存储架构还暴露了一些其他问题，例如假设对内存进行读 - 改 - 写操作以设置标志或递增计数器的操作不会受到硬件中断的影响。为了解决这个问题，我们在内核中添加了一组“原子”操作。John Baldwin 对原子框架进行了扩展，以支持 IA-64 的获取/释放语义，并广泛用于支持多 CPU 平台。

IA-64 移植的灵感来自于摆脱 32 位 i386 平台限制同时保留与传统软件的兼容性的需要。虽然这些目标已经实现，但该平台本身并没有达到更简单的 i386 架构的性价比。IA-64 最终在大规模超级计算中找到了自己的定位，但它并不适合大多数 FreeBSD 工作负载，并被 AMD 对 i386 架构的 x86-64 扩展所取代，后者在现代计算环境中无处不在。

此后，FreeBSD 移除了对这两个平台的支持。Alpha 架构是 Digital 与 Compaq 合并后的牺牲品，尽管它一直作为产品存在，直到 2007 年才停止生产。FreeBSD 于 2006 年移除了对 Alpha 平台的支持。对 IA-64 的支持持续时间稍长一些；Marcel Moolenaar 多年来对支持这个平台的多处理器和 NUMA 变体进行了许多改进。2014 年，FreeBSD 移除了对 IA-64 平台的支持，而该平台也于 2021 年停产。

***

**DOUG RABSON** 是一名软件工程师，拥有超过三十年的经验，历经上世纪 80 年代的 8 位文本冒险游戏到本世纪 20 年代的每秒 TB 级分布式日志聚合系统。自 1994 年以来，他一直是 FreeBSD 项目的成员和贡献者，目前正致力于改进 FreeBSD 对现代容器编排系统的支持，如 podman 和 kubernetes。
