# 采访：Warner Losh，第 2 部分

* 原文链接：[Interview: Warner **LOSH**, Part 2](https://freebsdfoundation.org/wp-content/uploads/2020/11/Warner-**LOSH**.pdf)
* 作者：BSD Now 第 362 集，复原 2.11 BSD。录制于 2020 年 8 月 5 日。

Benedict Reuschling 和 Alan JUDE 与 Warner LOSH 在讨论 Unix 历史及其有趣的细节。他们探讨了他的 2.11 BSD 复原项目，该项目也是 Unix Heritage Society（Unix 遗产协会）的一部分。此外，他所编写的名为 devmatch 的东西又是什么呢？希望你会喜欢这一切！第 1 部分已刊登在 2020 年 78 月刊。

REUSCHLING：让我们回到你在博客和演讲中提到的 Unix Heritage Society——它究竟是什么？人们如何参与其中？

WARNER **LOSH**：Unix Heritage Society 有时被称为 TUHS，因为全称太拗口了。它致力于保存多年来在贝尔实验室以及 Unix 进入世界后所存在的各种 Unix 文物。TUHS 建立了可通过其网站自由访问的档案库，他们不仅努力保存 Unix 历史的代码和二进制文件，还寻找关于 Unix 的旧论文。例如，当某人退休或去世后，家人捐赠其论文，他们会将其扫描并公开。所有感兴趣的人都可以发现当年的历史发展是如何发生的，并在更广阔的背景下阅读相关内容。

一个例子是贝尔实验室关于网络的一组论文，但它们并不是关于 TCP/IP，而是关于一种叫做 Data Kit 的东西，这反映了贝尔实验室的思维模式。作为一家电话公司，他们拥有电路交换的思维方式，因为电话呼叫正是这样建立的——你从起点到终点创建一条电路，并在整个通话过程中保持硬连接。因此，他们的数据网络技术也体现了这一点——你会创建一条电路，然后通过这条电路发送数据。这与我们今天所使用的分组交换范式截然不同，而后者最终成为了主流。在 Unix Heritage Society 的收藏中，你可以阅读到这些以及数十个其他历史事件、思维方式或文化现象的相关资料。

如果你掌握了某段遗失的历史，或者拥有任何与 Unix 相关的旧资料，那它们可能会引起 TUHS 的兴趣。TUHS 设有公共和私人档案。William Toomey 是 TUHS 的创始人之一，他维护着一个私人档案，存放着尚未过版权保护期的早期商业版本，但他仍然进行者存档，以备日后可以公开。

因此，即便你手中的资料无法广泛传播，你仍可以将其存入档案库，以免其彻底消失于历史长河。参与 TUHS 的方式有很多，但通常来说，订阅邮件列表、参与讨论、关注各种话题，并在每隔一两年新文物浮现时参与相关项目，是大多数人加入的主要途径。

**JUDE**：这真的很有趣。你会不禁好奇，还有多少东西仍然遗失或尚未被发现，以及我们如何确保我们创造的更多内容不会最终消失。

**LOSH**：没错。除此之外，还有其他一些努力。Bitsavers 会扫描文档，他们关注来自 DEC、Sun 等公司的旧计算机手册，并对其进行扫描——尤其是那些有助于人们编写模拟器的文档。他们还保存了各种杂志文章，最近有人捐赠了所有早期的 DECUS 通讯。DECUS 是一个 DEC 用户组织，过去曾举办专题研讨会，并出版这些会议的论文集，同时也定期向会员发送通讯，介绍各种新发展。

这是一件好事，因为尽管我们继承了过去的智慧，但我们并不总是知道这些智慧是如何积累的，哪些知识被保留了，哪些被遗忘了。探索这一点真的很令人兴奋——至少对我而言是如此，而且鉴于我写过的一些主题受到了广泛关注，或许也有其他人对此感兴趣。

**JUDE**：我认为我们需要更多地考虑如何保存历史，不仅仅是关于我们正在做的工作，还包括围绕这些工作的思考过程。如今，人们不像以前那样频繁地撰写论文和出版会议论文集了。来自 BSDCan 或其他会议的演讲录音固然很好，并且我们也开始保存这些内容，但它们并不总是能解释清楚某些决策的缘由，而这些可能会成为未来值得探讨的问题。

**LOSH**：完全正确。你在写论文和做演讲时，面对的是完全不同的受众。演讲受时间限制，而且听众的能力和兴趣各异，因此你需要尽量让内容在这个背景下既有趣又相关。而在论文中，你会更倾向于详细地记录“我做了这个、这个和这个”，即使可能会让读者觉得有些枯燥，因为某个未来的读者可能会想知道：“原来这就是为什么现在会出现这种奇怪的现象？”我非常支持保存所有不同形式的媒体记录。

让我担忧的一点是，我们有邮件列表的存档，也勉强算有论坛的存档。但论坛往往是短暂的，时而兴起，时而消失。因此，许多思考过程会在这个过程中丢失。而这是一种缓慢的侵蚀。如果你在讨论如何设置 iCOBUS 网卡的跳线，可能没有人会特别去保存这些信息。但如果你试图重建一个 FreeBSD 4 系统，并意识到：“哦，我需要匹配当年的硬件，或者我需要一块网卡，而这就是我能找到的唯一一块”，那么这些信息突然就变得重要了。或许 FreeBSD 1 系统会需要这样的配置，而到了 FreeBSD 4 时代，一切都已经变成了即插即用。但当你丢弃这些信息时，你可能会觉得它们无关紧要、不值得保留——但对其他人来说，也许并非如此。

**JUDE**：这很有趣。当我在研究加密引导加载程序时，我花了大量时间阅读关于 FreeBSD 启动过程的论文。而你最近写了一篇文章，探讨了如何正确地重启和关闭计算机。

REUSCHLING：正确地。

**LOSH**：安全地。

**JUDE**：那么，正确重启 Unix 机器的方式是什么？

**LOSH**：在现代系统上，通常使用命令 `shutdown` 。`shutdown` 会确保所有进程都被正确终止，并尽最大努力确保所有脏缓冲区（dirty buffers）都被写入磁盘。但它也会设定一个时间限制，以防止系统无限等待数据写入完成，从而确保系统最终能够重启。这有点让人不太满意——你不能直接拔掉电源。在某些系统上，你可以输入 `sync`，然后立即关闭电源，因为 `sync` 是同步。在 Linux 上，`sync` 是同步操作，当 `sync` 返回时，所有数据都已写入磁盘。如果系统处于空闲状态，你此时就可以安全地关闭它。

BSD 只是调度写入操作。此外，UFS 软更新（soft updates）带来了一些额外的复杂性。当一个缓冲区被写入时，它可能会重新标记为“脏”（dirty），以便在其他数据写入时再次写入。这种机制可以确保即使系统在数据写入过程中崩溃，磁盘上的数据仍然保持一致。这也是为什么当你观察系统关闭时，有时会看到这样的提示：\
`syncing, dirty buffers: 1, 1, 1, 37, 28, 15, 2` 你可能会想——这是什么鬼？所有进程都已经终止了，为什么突然出现了 38 个脏缓冲区？这其实是软更新机制在起作用。由于存在一些隐藏的依赖关系，当某个数据块被写入时，所有依赖的数据块都会变为可写状态，结果系统就需要再一次刷新这些脏缓冲区到磁盘。

在早期 Unix 系统上，并没有 `reboot` 命令。除了直接拉下电源开关之外，压根没有停止内核的办法。如果你想确保所有数据都写入磁盘，你需要输入命令 `sync` ——而且要输入三次！第一次 `sync` 会真正执行同步操作并调度所有 I/O，接下来的两次 `sync` 其实是程序员人为设计的延迟循环（analog delay loop），用来给磁盘写入操作留足时间，以确保数据完全刷入磁盘后才关闭机器。

这种思维方式被深深植入了人们的观念之中。后来，实现了 `reboot` 命令，它在某种程度上可以算是“安全的”。我能找到的最早版本会先刷新文件系统，等待五秒钟，然后就宣告完成。但这并不是真正的安全——通常没问题，但有时候会出问题，尤其是在需要刷新的数据量很大的情况下。

因此，了解这个演变过程是很有用的——为什么有人会说要输入三次 `sync` ？也许你会想：“我可以在 `sync` 后面加上分号，这样只需要按一次回车就好了——这样更聪明。”但实际上，这样做并不会带来任何帮助，因为单独执行每个命令才是关键所在。围绕这个做法，还衍生出了各种传说。有个常见的说法是：“第一次 `sync` 只是调度写入操作，而第二次 `sync` 会阻塞，直到第一次 `sync` 完成。”这在我的博客反馈中出现过很多次。但也许某些系统确实有这样的行为？我没有研究过所有系统，但我查阅了所有能找到的 BSD 内核，没有一个是这样做的。我还检查了 Linux 内核，也没有这样的机制。我翻阅了所有能找到的 System V 内核，也没有发现这种行为的存在。

所以，这个说法完全就是个自行衍生的都市传说。实际上，没有哪个系统会等到所有当前的脏缓冲区写入完成后才去调度其他写入操作。`sync` 只会锁定所有缓冲区，并调度它们执行 I/O 操作。而在 Linux 中，`sync` 会额外加入一个屏障（barrier），确保所有 I/O 操作完成后再返回——这个决定很早就定下来了。而大多数其他系统并不会这样做。当然，可能确实有某些我不了解的系统存在这样的机制。

这个领域有着极大的多样性，因为人们一直在尝试让这个过程变得更可靠。许多人对它进行了各种修改，许多解决方案在孤立的环境中诞生又被遗忘，因为那时这些系统都是商业化的、专有的，不同厂商之间并不了解彼此的做法。

直到今天，部分源代码才泄露到互联网上，你可以找到这些源码，真正去研究不同系统的实现。但一个现实的难题是：某些公司对源代码的保密程度比其他公司更严格。例如，在 HP 系统进入 System V 之前，你根本找不到它的源代码。因此，想要从原始源码研究这些系统可能非常困难——唯一的办法就是反编译，但这对我而言已经超出了我的兴趣范围。

REUSCHLING: 总有一刻，你会接受现状，然后继续前进。

**LOSH**: 没错。我手头有 `man` 手册，而 `man` 手册上的内容就是我能知道的一切。

REUSCHLING: 换个话题，你能介绍一下 Devmatch 吗？它是什么？你还计划在这个方向上继续做更多的工作吗？

**LOSH**: 当然。Devmatch 是我很早就有的一个想法，但直到最近才有机会去实现。这个想法最初来源于一个观察：我们的系统里有大量驱动程序，每个驱动程序都有自己的匹配表，而 `probe` 例程会逐个遍历这些表项，检查插入的设备信息是否匹配——匹配第一个？不行。匹配第二个？还是不行……一直这样查找。然后我就想，如果驱动程序能自己编码一个指向匹配表的指针，并提供一些关于该表格式的元数据，将这些信息存储到可加载驱动中会怎样？这样，我们就可以编写一个程序，或者直接利用已有的程序……

如果我对这个程序进行扩展，让它读取这些元数据，并用这些数据遍历已有的匹配表，从而创建一个主匹配表，那么当有事件发生时，我们就能直接查询这个表，快速找到哪个设备事件对应哪个驱动，或者哪个模块声明它有合适的驱动来匹配该设备。

多年来，Hans Peter 曾经开发过一个专门用于 USB 的版本。他会解析 USB 设备的匹配表，并将其存入特定的 ELF 头部，然后解析该头部信息，生成 devd 规则，以便动态加载所有 USB 设备驱动。从 FreeBSD 11 开始，这种方式成为了默认行为。事实上，从 FreeBSD 10 到 FreeBSD 11 期间，是 FreeBSD 内核唯一一次变小的时期。而导致内核缩小的唯一原因，就是 Hans 把所有 USB 驱动移出了内核，并让它们通过 devd 规则按需加载。

所以，我也做了类似的事情。USB 设备可以正常工作，PC 卡 设备也能正常工作，PCI 设备基本上也可以工作了，可能还有一两个缺失的部分。我最近没有做过完整的审计。

我所有的工作重点都集中在在引导后立即加载必要的驱动。系统需要在引导时加载一切必要组件（例如用于挂载文件系统、路由和启动关键服务的驱动），而其余的部分则可以作为引导过程的一部分动态加载。

Manu 在嵌入式领域更进一步，他编写了一小段代码，与 FDT 配合使用，同时也可以很容易地泛化到 PCI 等其他场景。最难的部分是构建代码来遍历 `loader.hints` 文件中的二进制数据结构，该文件是 KLDX ref 生成的。因此，我们也可以对 PCI 设备做类似的事情，但目前还没有人这么做。这是我希望找到人来研究的方向。

我在做这个项目的同时，也做了很多与 boot loader 相关的工作，以便把 Lua 集成进去。当 Lua 相关的工作完成时，我对 boot loader 也暂时告一段落了。所以，我没有继续推动把这个方案集成到 boot loader 中。但如果我们这么做了，整个系统将会更加强大，因为我们几乎可以通过 boot loader 加载所有设备驱动。事实上，这已经是可行的——你可以查询 PCI 设备信息，判断是否需要加载特定驱动。因此，我们完全可以仅在 boot loader 中加载少量驱动，只要足够支撑挂载根文件系统，然后再动态加载其他驱动。这种逻辑可能稍微复杂一点，但确实是可以实现的，毕竟相关的信息是存在的。

此外，遍历不同的 UEFI 二进制数据结构会有些繁琐和耗时。我没有进一步研究这个方向的另一个原因是：你可以在设备启动时找到它，并找到它的路径，进而找到路径上的所有设备，最终确定需要加载的驱动。

不过，要让这一切顺利运作，需要比我目前能投入的更多精力。当我在做 FreeBSD 相关的工作时，我希望尽可能专注。所以，如果有人正在寻找一个有趣的项目，比如谷歌编程之夏规模的项目，可以联系我或者 Manu，我们可以提供你继续推进这个项目所需的一切信息。这就是目前的进展，以及我的计划。

FreeBSD 12 和 FreeBSD 13 在这个方面的支持水平基本相同，除非有人迅速介入，否则不会有太大变化。但谁知道呢？也许有人会把这当作他们的“疫情项目”来做，又或者他们会让它成为自己的“疫情项目”。

**JUDE**: 对于很多设备，比如网卡，使用 devmatch 似乎是一个不错的选择，你可以决定稍后加载它们，而不是将它们编译到内核中。

**LOSH**: 是的，尤其是在有很多选择的情况下。对于存储驱动来说，现在的选择并不多。如果你拥有我们曾经有过的所有遗留支持，那就会有更多选择。按需加载这些驱动也有助于减少内核大小。我想通过这个来实现一个更简化的内核，从而提高启动时间，也许能使得在稍小的设备上加载成为可能，尽管在过去几年里，这并不是我关注的重点。你知道，能够做到这一点，具备这种灵活性，是一个好的、有用的工具，我们应该完成这个工具的构建，因为如果人们拥有它，他们一定会使用这个工具。

**JUDE**: 是的，从安全的角度来看，最好是不把你未使用的代码加载到系统中。

**LOSH**: 嗯，最安全的代码是根本不存在于系统中的代码。

**JUDE**: 好的，非常感谢你抽时间与我们交谈。在结束之前，你还有什么想聊的吗？

**LOSH**: 我想我已经把我想说的都说完了。我想最后提一下，如果你对 2.11-BSD 项目感兴趣，欢迎联系我。有一些事情可以做，人们能参与其中，帮助这个项目更好地重现。
