将 Linux 移植到 DEC Alpha:基础设施
移植操作系统并非易事。操作系统是大型、复杂、异步的软件系统,其行为并非总是确定性的。此外,还有许多开发工具,例如编译器、调试器和库,程序员通常认为这些工具是理所当然的,但在移植项目开始时并不存在。移植团队必须在移植工作本身开始之前实现这些工具和其他基础设施。
本文是三篇描述 Digital Equipment Corporation 一小组程序员进行的一项移植工作的第一篇。我们的目标是将 Linux 操作系统移植到 Digital Alpha 微处理器系列。这些文章侧重于我们所做的最初的概念验证移植。虽然我们早期的许多工作已被 Linus Torvalds 为 1.2 版本所做的可移植性工作所取代,但我们的故事生动地说明了操作系统移植中所涉及的任务类型和规模。
Jon Hall 在第 29 页的文章中描述了我们参与 Linux 移植工作的许多商业案例理由。我将描述导致我开始 Linux 移植工作的实际事件。
首先,一些背景信息:我为 Alpha 迁移工具组工作,该小组是 Digital Semiconductor 内的一个工程开发小组。我们在 Alpha 项目初期成立,旨在开发自动方法,用于将 Digital 客户的遗留应用程序迁移到基于 Alpha 的系统。我们的第一个产品是 VEST,它将 VAX/VMS 二进制可执行文件转换为可以在 OpenVMS Alpha 上执行的二进制文件。紧随其后的是 MX,它将 MIPS Ultrix 可执行文件转换为在 Digital Unix 下的 Alpha 系统上运行的可执行文件。从那时起,我们的章程已扩展到其他“使能技术”(使用户能够迁移到 Alpha 的技术)领域。除了生产转换器和模拟器外,我们还向第三方供应商提供技术,并参与了 Alpha 编译器和汇编器的开发。
我们参与 Linux 始于 1993 年底,当时我们意识到基于 Alpha 的系统没有入门级操作系统。虽然 OpenVMS、Digital Unix 和 Windows NT 都是强大而可靠的操作系统,但它们资源消耗过多,无法在基本系统配置上运行。在许多情况下,特定系统的最小可用配置的成本至少比最小可能配置高出数千美元。我们决定,为了在低端与 PC 克隆系统竞争,我们需要使最低价格的系统配置可用。在调查了各种替代方案后,我们认为 Linux 在价格(免费)、性能(出色)和支持(全球成千上万渴望且有能力的黑客,以及开始出现的第三方商业支持)方面具有最佳组合。
在制定移植提案时,我为 Linux/Alpha 项目设定了以下目标
价格:Linux/Alpha 将继续是免费软件。Digital 为 Linux/Alpha 开发的所有代码都将根据 GNU 通用公共许可证免费分发。此外,用于构建 Linux/Alpha 的所有工具也将是免费的。
资源吝啬:Linux/Alpha 将能够在 PC 级 Alpha 系统的基本配置上运行。我的目标是在 8MB 内存中以文本模式运行,在 16MB 内存中以 X-Windows 运行。此外,一个功能齐全的 Linux/Alpha 系统应该能够安装在 340MB 硬盘上,并留有空间。
性能:Linux/Alpha 的性能应与 Digital Unix 相当。
兼容性:Linux/Alpha 应与现有的 Linux 应用程序源代码兼容。
进度:我们希望能够尽快展示一个可工作的移植版本。
以上标准推动了我们关于 Linux/Alpha 的几个设计决策。为了满足进度标准,我们决定将我们的初始代码库“冻结”在 Linux 1.0 级别,并从那里开始工作,除非我们需要错误修复,否则不合并后来的更改。这将最大限度地减少代码流的扰动(当您深入并几乎改变整个宇宙时,这是必要的),并将消除不断赶上最新版本的进度消耗。我们推断,一旦我们获得一个可工作的内核,我们就可以利用我们所学到的知识来赶上最新版本。
进度标准也推动了我们决定使我们的初始移植成为 32 位(而不是 64 位)实现。两者之间的主要区别在于所使用的 C 编程模型。Intel Linux 使用“32 位”模型,其中 int、long 和指针都是 32 位。Digital Unix 使用“64 位”模型,其中 int 仍然是 32 位,而 long 和指针是 64 位。在 Digital,我们遇到了很多将 int、long 和指针互换使用的 C 代码。这样的代码可能在 32 位编程模型中侥幸工作,但在 64 位模型中可能会产生不正确的结果。我们决定进行 32 位初始移植,以最大限度地减少此类问题的数量。我们认为将 long 和指针限制为 32 位不会过度阻碍任何现有代码,并且在新应用程序出现需要更大数据类型时,64 位 Linux 实现将可用。
为了权宜之计,我们还决定使用现有的 Digital Unix PALcode 支持,而不是编写我们自己的。Digital Unix PALcode 非常适合其他 Unix 实现,它很容易获得,并且已经过非常充分的测试。反过来,使用 Digital Unix PALcode 需要我们使用“SRM”控制台固件。SRM 固件包含设备驱动程序,Linux 可以通过回调函数使用这些驱动程序。虽然这些控制台回调驱动程序非常慢,并且必须在关闭所有中断的情况下运行,但它们确实使我们能够专注于 Linux 移植的其他领域,并推迟设备驱动程序的工作。
一些设计决策是由 Intel 和 Alpha 之间执行环境的差异驱动的。在 Intel 上,内核虚拟内存空间与系统物理内存空间一对一映射。由于可能与用户虚拟内存发生冲突,Intel Linux 使用段寄存器来保持地址空间分离。在内核模式下,CS、DS 和 SS 段指向内核虚拟内存空间,而 FS 段指向用户虚拟内存空间。这就是为什么内核中存在诸如 put_fs_byte()、put_fs_word()、put_fs_long() 等例程的原因;这就是在 Intel Linux 实现上在内核空间和用户空间之间传输数据的方式。
由于 Alpha 没有分段,我们需要使用一些其他机制来确保用户和内核地址空间不会冲突。一种方法是每次只映射一个地址空间。这需要一个转换缓冲区(有时称为转换后备缓冲区或 TLB),这是 CPU 上的一个特殊缓存,用于大大加快虚拟内存地址查找速度。但这使得用户空间和内核空间之间的数据传输变得繁琐。它也可能导致性能损失;在不实现地址空间标识符的系统上,对内核空间和用户空间使用相同的虚拟地址范围需要为用户空间和内核空间之间的每次转换使该范围的整个转换缓冲区无效。这可能会在每次系统调用、定时器滴答或设备中断中导致多次转换缓冲区未命中。
避免用户和内核之间地址空间冲突的另一种方法是分区地址空间,将指定的地址范围分配给指定的用途。这是 32 位 Linux/Alpha 移植所采用的方法。它很简单,不需要为每次进入内核模式而进行大规模的转换缓冲区无效化,并且它使用户和内核之间的数据传输成为一个完全微不足道的复制。
设计地址空间布局需要注意某些其他约束。首先,没有地址可以大于 0x7fffffff,因为 Alpha 在 64 位寄存器中处理 32 位量的方式。当发出 LDL(加载长字)指令时,加载的 32 位量将被符号扩展到 64 位寄存器中。因此,将地址 0x81234560 加载到 R0 将导致 R0 包含 0xffffffff81234560。尝试取消引用此指针将导致内存错误。有一些技术可以双重映射此类有问题的地址,但我们认为对于概念验证移植,我们不需要额外的复杂性。因此,我们只是将虚拟地址限制为 31 位。
另一个考虑因素是我们需要一个与系统物理内存一对一映射的区域。我们不想简单地使用低 256MB(例如),因为我们希望能够将用户程序放在低地址中,因此我们为此目的选择了一个高内存区域,并使物理地址等于虚拟地址减去一个常数。这在下面被称为“mini-KSEG”。
一旦考虑了所有约束,我们最终得到了如下所示的系统虚拟内存布局
0x00000000--0x3fffffff User 0x40000000--0x5fffffff Unused 0x60000000--0x6fffffff Kernel VM 0x70000000--0x7bffffff mini-KSEG (1:1 with physical memory) 0x7c000000--0x7fffffff Kernel code, data, stack
最后,我不得不决定在多大程度上修改代码库以完成移植。我觉得我没有像 Linus 为 1.1.x 到 1.2.x 的过渡所做的那样,对代码进行大规模更改和重新安排的自由。这样做会导致我的代码越来越偏离主流代码库,这将对 Linux 社区的接受度产生不利影响。
我决定保持原始 Intel 代码 100% 完整,因此人们可以想象仍然可以从我的代码库构建 Intel 内核。Alpha 代码将是对 Intel 代码库的添加或替换。需要更改的区域将通过条件编译来分隔。有时这需要我放下自尊,并设计一个不太干净的 Alpha 特定版本的算法,以对应于一个不太干净的 Intel 特定版本,而我实际上更愿意实现一个可以同时容纳两者的干净、通用的算法。幸运的是,当 Linus 为 Linux 1.1.x 和 Linux 1.2.x 进行可移植性工作时,他为我们所有人实现了干净、通用的算法。
我在 Alpha 系统上编译一些 Linux 代码的最初实验使用了 Digital Unix 编译器和工具。虽然这很成功,并且使我能够进行一些早期的原型设计工作,但免费软件标准要求我们使用免费软件编译器和工具集来构建内核。GNU C 编译器是显而易见的选择;Intel Linux 使用它,并且没有其他免费软件编译器能与其功能和复杂性相提并论。
尽管 gcc 可以配置为生成 Alpha 代码,但 FSF 提供的版本只理解 64 位 Digital Unix 编程模型。虽然我对编译器了解一些,但我不是专家。我试图修改 gcc Alpha 的机器描述文件,以生成 32 位指针和长字,但结果是灾难性的。事实证明,对机器描述的微小更改可能会产生深远的影响,我既没有时间也没有意愿盯着机器描述直到我获得启发。
幸运的是,我不必这样做。Digital 的另一个项目曾付费请 Cygnus Support 制作一个可以生成 32 位 Alpha 代码的 gcc 版本,我能够将其用于我的 Linux 工作。我拥有的第一个版本仅实现了 32 位编程模型;64 位量不可用于计算。这推动了某些早期的设计决策。后来,添加了 64 位“long long”和“double”数据类型,这使我能够重新审视并简化许多我需要 64 位量来进行机器和 PALcode 接口的领域。
我在 Digital Unix 和 Intel Linux 上都将编译器和工具套件构建为交叉编译器,并进行了广泛的测试。我在办公室的各种 Digital Unix 系统上和家里的个人 486-based Linux 系统上都做了相当多的开发工作。
当我开始 Linux/Alpha 项目时,我决定不在 Alpha 硬件上进行初始调试,而是在 Alpha 指令模拟器上进行。名为“ISP”的模拟器提供了比我在硬件中获得的对指令执行的更大控制。它还提供了一些支持功能,否则我必须将其添加到内核中(例如,在 ISP 中,我可以设置和捕获断点,而无需内核中的断点处理程序)。此外,由于我拥有 ISP 的源代码,我可以插入自定义代码来捕获所需的奇怪条件。
我使用的 ISP 版本包括它自己的 SRM 控制台和 Digital Unix PALcode 版本,因此我能够合理地调试我与控制台和 PALcode 的接口,并确信相同的代码应该可以在真实硬件上未经修改地工作。
虽然与真正的 Alpha 硬件相比,ISP 非常慢,但其性能对于初始调试来说是可以接受的。事实上,在 486DX2/66 Linux 系统上,ISP 能够在不到三分钟的时间内启动到 shell 提示符。
一旦交叉开发和执行环境到位,我就开始开发引导加载程序。引导加载程序的设计部分取决于通过 SRM 控制台从磁盘引导的机制。当用户向 SRM 控制台发出 boot 命令时,控制台首先读取指定引导设备的初始扇区。此扇区中的两个字段指定初始引导程序的块偏移量和块计数。然后,控制台将此引导程序读取到从虚拟地址 0x20000000 开始的内存中,并跳转到该地址。
虽然理论上可以简单地以这种方式读取整个内核,但这在实践中是不可行的,原因有二。首先,0x20000000 位于用户内存空间的中间。内核不能保留在那里;它必须被重新定位到一个更方便的地址。其次,内核很大;由于 SRM 加载的引导程序必须是连续的,因此以这种方式引导将倾向于排除从文件系统加载内核之类的操作。
由于这些原因,通过 SRM 控制台加载操作系统的首选方法是让控制台加载一个小的“引导加载程序”程序;反过来,这可以使用控制台回调函数从磁盘加载操作系统本身。从概念上讲,引导加载程序相当简单;它设置内核虚拟内存空间,读取内核,然后跳转到它。
引导加载程序是分阶段开发的。第一个版本只是假设内核映像被连接到引导加载程序映像的末尾。引导加载程序将检查引导扇区以确定内核的起始位置,并读取内核的 COFF 标头以确定其大小。
引导加载程序的第二个主要更新添加了从 ext2 文件系统读取文件的能力。这样,引导加载程序和 Linux 内核本身都是常规文件。引导加载程序必须由一个特殊程序 (e2writeboot) 安装,该程序在 ext2 文件系统上创建一个连续文件,并将引导加载程序文件的范围写入引导块。尽管如此,这种方法增加了更大的灵活性,因为它使更新引导加载程序和 Linux 内核变得更加容易。
引导加载程序的最终主要更新由 David Mosberger-Tang 提供;它是解压缩压缩 Linux 内核映像的能力。这不仅节省了磁盘空间,而且还使加载速度更快。
下个月,我们将介绍内核移植。
Jim Paradis 是 Digital Equipment Corporation 的首席软件工程师,也是 Alpha 迁移工具组的成员。自从一位大型机系统管理员在大学里对他大喊大叫以来,他就一直希望在自己的桌面上拥有一个多用户、多任务操作系统。为此,他尝试了几乎所有为 PC 生产的 Unix 变体,包括 PCNX、System V、Minix、BSD 和 Linux。不用说,他最喜欢 Linux。Jim 目前与他的妻子、十一只猫和一栋永远在装修的房子住在马萨诸塞州伍斯特市。