Linux on Alpha AXP—Milo,迷你加载器

作者:David Rusling

1994 年末,我到美国马萨诸塞州哈德逊的总部小组访问,那里是 Alpha AXP 处理器的制造地。在一个空闲的上午,我跟进了一个几周前听到的传闻。传闻是 Jim Paradis 正在将 Linux 移植到 Alpha。那时,我对 Linux 一无所知,只知道它是芬兰一位学生开发的 Unix 免费软件版本。当我找到 Jim 时,我感到非常惊讶。实际上是两个惊喜:Linux——运行在 Alpha 笔记本电脑上。我们聊了一会儿,我很快就被 Jim 的热情感染了(如果这是正确的说法)。

我们讨论的一个主题是 Linux/Alpha 对小型加载器的需求。在 Intel PC 系统上,加电时初始化系统的固件称为 BIOS。有几家非常著名的 BIOS 代码提供商,PC 的制造符合非常严格的规则集,这意味着 PC 在硬件方面非常相似。在 Digital 的基于 Alpha 的系统(甚至在基于 VAX 的系统)中,等效的软件是 控制台,在 Digital 内部它被称为 SRM 控制台,因为它的接口在系统参考手册中描述。

那么,Linux 为什么需要 SRM 控制台呢?首先,Linux 需要从某些介质加载,而 SRM 包含执行此操作的设备驱动程序。其次,Linux 需要 Digital Unix PALcode。PALcode 可以被认为是一个微小的软件层,它使芯片适应特定的操作系统。它在特殊模式(PALmode)下运行,并具有某些限制,但它使用标准的 Alpha 指令集。通过这种方式,Alpha 芯片可以运行各种不同的操作系统,如 Windows NT、Open VMS、Digital Unix,当然还有 Linux。最后,Jim 在他的原型设备驱动程序中使用了 SRM 回调。但从 Linux 的角度来看,SRM 控制台做得太多了。它包含回调过程,允许正在运行的操作系统向控制台写入消息或写入环境变量等等。Linux 不使用这些功能;事实上,一旦加载 Linux,SRM 控制台唯一需要的部分就是 PALcode。

我自愿编写一个小型加载器,它只做 Linux 需要的事情。像所有好的项目一样,我这个单人项目有一些直接的项目目标。首先,该软件将根据自由许可证发布,构建并作为标准 Linux 发行版的一部分免费分发。其次,Linux 驱动程序应该能够在 Milo 中使用,而无需修改甚至重新编译。第三,它应该最大限度地增加 Linux 可用的内存量。我当时并没有意识到自己要做的是什么。

Milo

Milo 包含以下功能模块

  • PALcode

  • 设备驱动程序

  • Linux 内核和伪内核

  • Linux 内核接口代码

  • 用户界面代码

找到 Digital Unix PALcode 并不困难。早在 1992 年,当 Digital 宣布 Alpha 处理器时,它还宣布将进军商业芯片市场。我加入了一个位于英国的小组,为欧洲提供工程力量,以进一步实现这些目标。我们是总部位于马萨诸塞州哈德逊硅工厂的主要小组的一个小分支。我们为 Alpha 处理器和 PCI 外围芯片构建评估板。这些系统包括一个非常低级别的评估板调试监视器,它使用 Digital Unix PALcode。评估板调试监视器和 PALcode 的源代码都在自由许可证下。

虽然此 PALcode 完全符合 Alpha 架构手册中描述的接口,但它与 SRM 控制台的 PALcode 之间存在一些差异。基于 Alpha 的系统之间的差异之一是处理器外部中断的处理方式。进入 CPU 本身的中断信号数量有限,数量因 CPU 而异,但通常有三种:定时器、I/O 和不可屏蔽中断。

实际设备中断映射到 CPU 中断的方式是特定于系统的。当前大多数 Alpha 系统都包含 ISA 总线,其中断通过一对 8259 路由,方式与 x86 PC 相同。SRM 控制台 PALcode 处理这些差异并解释中断,将其作为“SCB 偏移”(Jim Paradis 在上个月的 Kernel Korner 中描述)传递给 OS 的中断处理代码。Milo 中使用的 PALcode 不进行这种解释,因此 OS 的中断处理代码必须自己进行解释。特别是对于 PCI 设备,PCI BIOS 代码和中断处理程序中必须有代码来理解中断在系统中的路由方式。这样做的一个副作用是,当 Linux 由 Milo 加载后,中断处理程序每次被调用时可以处理多个设备的中断。

我从我们的 Alpha 评估板采用的示例 PALcode 的一个有趣且有用的功能是,它允许评估板调试监视器在 1 对 1 物理寻址模式下运行。虚拟页表基址寄存器的位 0 可以打开和关闭此功能。当发生转换缓冲区未命中时,PALcode 构建一个新的页表条目并将其插入到缓存中。Milo 加载后做的第一件事几乎就是切换到物理地址模式下的 PALcode。迷你加载器做的最后一件事是再次切换到此 PALcode,这次将最终控制权传递给 Linux 内核。

当 Milo 将控制权传递给内核时,它必须打开虚拟内存映射,因为 Linux 期望名为硬件重启参数块 (HWRPB) 的控制结构位于正确的虚拟地址。除此之外,它还描述了系统类型以及有多少内存可用,以及内存的位置。由于 Linux 最初是通过 SRM 控制台加载的,因此它自然地使用了 SRM 提供的接口,即 Alpha 架构手册中描述的 HWRPB。我看不到任何理由更改此接口:世界上已经有足够多的接口了,为什么要再发明一个呢?

为了使 Milo 正确设置内存映射,它本身必须清楚地了解有多少内存可用以及内存的用途。它找到可用内存量是因为在 PAL 复位代码执行后,内存大小被放入不纯区域,这是一个 PALcode 与控制台或评估板调试监视器之间共享的数据结构。Milo 保留一个内存映射,描述系统中每个页面的用途。当设备驱动程序运行时,它们会分配临时内存并使用它。就在控制权传递给 Linux 之前,Milo 必须在 HWRPB 中构建正确的内存簇描述,并且内存映射用于执行此操作。页面在内存映射中标记为“空闲”、“已分配”或“临时分配”。当 Milo 在 HWRPB 中构建内存簇描述时,它将所有临时分配的内存都视为可用,因为一旦 Linux 开始运行,它们将变为可用。通过这种方式,唯一标记为已分配的内存是包含 PALcode 的内存(8 页)、HWRPB 的内存(1 页)以及第 2 级和第 3 级页表的内存(2 页):总共 11 页。我认为我成功地实现了最大限度地增加 Linux 可用内存量的目标。

设备驱动程序

Linux 真正优秀之处之一是为其开发的设备驱动程序的数量。似乎任何市售的卡或芯片组都已经为其编写了驱动程序。因此,Milo 能够利用这些驱动程序似乎至关重要。这使得构建基于 Alpha 的系统的公司能够通过拥有大量可能的设备选择来区分其产品。

为了在不修改的情况下运行设备驱动程序,我不得不复制 Linux 内核的一些服务。最初,我计划不使用真正的中断,而是轮询驱动程序。这是 Milo 在 Linux 1.1.68 中工作的方式。但是,一旦我开始尝试让 NCR 53C810 SCSI 驱动程序在 Milo 中工作,我最终需要适当的中断处理,并且最好直接从 Linux 中获取中断处理,我就是这样做的。

我已经尽力将我不得不在 Milo 中复制的 Linux 服务数量保持在最低限度。毕竟,随着 Linux 的发展,这些例程往往需要重写。一个很好的例子是 1.1.68 和 1.2.8 之间的变化;软盘驱动程序改变了其在内核初始化期间确定其正在运行的方式。当我弄清楚这一点时,这让我头疼。

也许随着时间的推移,我会将更多真正的 Linux 内核合并到 Milo 中,但它应该是迷你加载器,所以我不希望将整个内核都添加到其中。目前,Milo 直接从内核中包含了 PCI BIOS 代码、块设备代码、中断处理和 DMA 代码。调度服务是我自己的,除非我添加多线程支持,否则我看不到它们会发生变化。

使用 Milo

Milo 的最后一个功能模块是大多数用户看到的部分,即用户界面。Milo 可以通过串行端口操作,但人们主要通过系统控制台使用它。因此,它必须具有一些键盘和 VGA 初始化代码。

键盘代码非常非常简单,并且只做了足够正确接收命令的工作。Linux 本身假设一些 BIOS 代码已经初始化了 VGA 设备,其控制台设备驱动程序只是使用它;这意味着 Milo 必须初始化 VGA 设备。有两种方法可以做到这一点。第一种方法是使用非常简单的 ISA VGA 初始化代码,这也是 Milo 最初的运行方式。第二种方法是包含 BIOS 仿真代码,它可以从不同的显卡运行板载初始化(它是 Intel x86 目标代码)。David Mosberger-Tang 将 Milo 的这一部分组合在一起,结果是它可以成功初始化许多常见的 ISA 和 PCI 显卡。

Milo 接口旨在非常简单,并且只需做足够的工作来加载正确的内核并将正确的启动参数传递给它。输入任何非法命令以外的内容都会显示所有可用命令。目前,Milo 假设它可以看到的所有设备都可以从中启动,并将尝试对它们使用 EXT2 文件系统。

加载加载器

Milo 是在 Alpha 评估板(EB66,它是一个基于 21066 的系统,类似于 AxpPCI33)上开发的。这意味着加载和测试 Milo 很简单,因为评估板调试监视器正在运行。但是,对于像 AxpPCI33 (Noname) 这样的真实系统,Milo 需要以其他方式加载。

基于 Alpha 的系统的启动分为几个步骤。第一步,在加电后立即,是将 SROM 代码直接时钟输入到 I-Cache 流中,然后开始执行它。此代码执行非常基本的系统设置,例如确定占用了多少 DRAM 插槽以及内存大小。下一步因系统而异,但本质上是 SROM 代码将固件代码(无论是哪种)加载到内存中,并在 PALmode 下将控制权传递给它。这是映像的 PALcode 复位入口点。某些固件,特别是 Milo,在 PALcode 中默认设置了用户模式固件的入口点地址,当 PALcode 复位代码完成系统初始化后,控制权将传递到该地址。其他系统将此信息存储在 NVRAM 中,或从跳线设置中推断出来。

由于各种原因,Milo 可以从故障保护启动块软盘、闪存和通过 Windows NT ARC 固件加载。系统与系统之间差异最大的是 SROM 代码能够从中加载固件的位置。在 AxpPCI33 上,SROM 代码能够从闪存、串行线或故障保护启动块软盘加载。在 AlphaPC64 上,不支持故障保护启动块软盘。所有这些都由跳线和/或保存在 NVRAM 中的启动选项控制(在 AlphaPC64 的情况下,在 TOY 时钟中)。有些系统不支持闪存,而是使用 ROM。用户在没有 ROM 烧录器的情况下不容易更改这些 ROM。因此,必须找到另一种加载 Milo 的方法。矛盾的是,您可以通过 SRM 控制台加载 Milo,但更有效的方法是通过 Windows NT ARC 固件加载它,因为那是这些板卡随附的固件。

有多种方法可以将映像放入闪存中,因此 Milo 支持运行任何映像,只要它链接到 Linux 内核通常所在的位置即可。通过这种方式,我可以构建在加载时更新闪存的映像,而不会让 Milo 负担了解它运行的每个不同系统的闪存要求。

通过 Windows NT ARC 控制台加载很有趣。在 Alpha 上,Windows NT 在“超级分页”模式下运行,该模式不支持 KSEG 寻址——不幸的是,这正是 Linux 完全 64 位操作所需要的。但是,所有 PALcode 实现都必须支持“Swap Pal”调用,这允许您从一种模式切换到另一种模式。Windows NT ARC 控制台在其内部具有运行映像并向其提供服务的概念,只要它们被构建为以适当的寻址模式运行并在内存中的安全位置运行即可。

因此,Windows NT OS 加载器实际上是一个可执行映像,加载该映像是为了使用适当的回调加载 Windows NT。我编写了一个非常简单的 OS 加载器,其唯一功能是加载 Milo,而 Milo 又加载 Linux。正是这个简单的加载器发出了“Swap Pal”调用,从而将控制权传递给 Milo 并打开 KSEG 寻址。从那时起,Milo 的操作与以前完全相同,只是它可以执行通过 [cw]OSLOADOPTIONS[ecw] 环境变量传递的命令以进行此启动选项,从而直接启动而无需在 Milo 提示符处暂停。

我曾尝试消除对在 Windows NT 固件开发树下构建映像的需求;不幸的是,这是不可能的,所以我已将这部分的功能保持在尽可能小的范围内。

当然,通过 Windows NT ARC 控制台加载 Milo 是一种让 Milo 运行的方式,以便 Milo 可以运行闪存更新实用程序以将自身放入闪存中。或者,它可以是一种在不互相干扰的情况下运行任一操作系统的方式。

未来

Milo 仍处于初期阶段,我希望看到 Linux 社区向其中添加他们需要的内容。毕竟,这就是 Linux 本身吸引力的一部分——一个由有能力、热情的程序员组成的社区为之努力。

David Rusling (david.rusling@reo.mts.dec.com) 与他的妻子、两个孩子、3 只猫和他的 1977 年 MGB GT 住在英国沃金厄姆。他在 Digital Equipment Corporation 的半导体部门工作,他认为 Alpha 上的 Linux 是他在 Digital 工作 10 年来参与过的最好的事情。

加载 Disqus 评论