> For the complete documentation index, see [llms.txt](https://freebsd-journal-cn.bsdcn.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://freebsd-journal-cn.bsdcn.org/20150102-mips-yu-arm64/arm64.md).

# arm64

* 原文：[arm64](https://freebsdfoundation.org/our-work/journal/browser-based-edition/mips-and-arm64/)
* 作者：**Andrew Turner**

历史上，ARM 主要为大量嵌入式和移动设备中使用的芯片提供 CPU 核心设计。FreeBSD 用户很可能熟悉使用 ARM 芯片的开发板，例如树莓派和 PandaBoard。这些 CPU 此前都是 32 位的；但在过去几年里，ARM 发布了新的 64 位架构。新架构称为 AArch64，包含全新的指令集 A64，首批 AArch64 处理器遵循 ARMv8 规范设计。熟悉 32 位 ARM 和 Thumb 指令集的开发者应该能很快上手 A64。

## ARMv8 规范

开发者注意到的第一个变化是通用寄存器数量增多、位宽加大——共 31 个 64 位通用寄存器。此外，程序计数器和栈指针不再属于通用寄存器集合，只能通过少数指令访问。

另一个重大变化是取消了大多数指令的条件执行能力。现在只有分支指令可以条件执行。条件执行是早期芯片的一大特性，但每条指令要为此占用 4 个比特，只剩 28 个比特用于编码指令。

ARM 的命名体系可能令人困惑。此处的 ARMv8 描述的是可用指令集，以及其他重要的架构领域，比如缓存应如何工作，或如何编程 MMU。该架构会向后兼容前一版本，但可能添加新标志或不兼容选项。后者一个例子是，部分 ARMv7 设计在 MMU 中加入了对更大物理地址的支持，称为大物理地址扩展（Large Physical Address Extension，LPAE）。在这些设计中，既有页表格式仍可用，但操作系统可以使用新格式访问超过 4GB 的物理内存。

除了 ARMv 命名外，还有设计家族。在 ARMv7 中，ARM 推出了 Cortex-A 系列。这些设计都是 ARMv7 设计，但有一些不同特性——例如，流水线长度可能不同，或是否实现 LPAE 支持。此外，公司也可以选择设计自己的 ARM 核心，即“架构许可证”（Architecture License），由被许可方设计自己的兼容核心。Marvell 就走了这条路，实现了自研的 ARMv7 兼容核心。在 ARMv8 中，ARM 力求有更多独立设计。

最底层是核心的具体实现——例如 Cortex-A7。这是一款带 LPAE 的 ARMv7 设计，从软件角度看，与 Cortex-A15 在架构上完全相同。这一点很重要，因为它允许将成对的核心放在同一 SMP 系统中协同工作——例如，搭配四个高能效的 Cortex-A7 核心和两个高性能的 Cortex-A15 核心。软件可按需开启或关闭它们。这种配置称为 big.LITTLE，旨在提供更长的电池续航，并在需要时提供性能。

ARMv8 架构沿袭这一思路，推出了 ARM 的 Cortex-A53 和 Cortex-A57 核心，以及若干第三方设计。一个例子是 Nvidia 的 Project Denver，该设计接收 ARM 指令并将其转换为内部指令集，后续阶段可能进行优化。这延续了 Transmeta 的 x86 芯片思路，只是输入指令集换成了新的。

ARMv8 还改变了异常状态的运作方式。这些异常状态可以视为不同的特权级。在 32 位芯片中它们相当复杂，中断处理和系统调用处理是不同的状态，但两者具有相同特权。ARMv8 有四个异常级别；较低的三个可视为用户态、内核态和虚拟机监控器态。最高特权级通常由芯片厂商编程，用于抽象芯片的部分功能，例如提供电源管理接口。这些状态称为 EL0 到 EL3，数字越大特权越高。它们可以这样使用：EL0 用于用户态，EL1 用于内核，EL2 用于 bhyve 或 xen。

## arm64 项目

过去两年我一直在业余时间做这个项目，自 2014 年 11 月起作为 FreeBSD 基金会项目的一部分，将 FreeBSD 移植到 AArch64。最初的工作是编写简单代码引导硬件，并跳转到 C 代码。之后这些代码被引入 FreeBSD 代码树的项目分支。最后，作为 FreeBSD 基金会项目，代码迁移到 GitHub 以便多名开发者协作。这一新架构在 FreeBSD 中将称为 arm64。

项目有两个目标：第一个也是我投入时间的重点，是为 FreeBSD 在多种不同芯片上运行奠定基础，包括内核内部基础设施和用户态。第二个目标是让 FreeBSD 在 Cavium ThunderX 处理器上运行。这是一款服务器芯片，最多 48 个 ARMv8 核心，可双芯片配置实现 96 核心。

在硬件可用之前将操作系统移植到新平台相当困难。幸运的是，ARM 发布了其模拟器的一个版本——称为 Foundation Model——让开发者可以测试代码。我所有的工作都在此完成，包括推进到可运行用户态的阶段。

为新移植编写的大部分代码都在内核的机器相关部分，arm64 移植也不例外。要完成新移植需要若干步骤，第一步是工具链。项目启动时，可用的工具链只有一套——gcc 配合 binutils——这要求我移植两者以构建 FreeBSD 二进制文件。（主要是复制现有 Linux 版本并按需修改。）由于此前参与过 ARM EABI 工作，我意识到这里需要类似方法，因此对两个项目都足够熟悉，得以让它们工作起来。

但工具链只是第一步。还需要硬件或模拟器来测试硬件启动。Foundation Model 在此发挥了作用，让实验可以在硬件面世前数年就开始。ARM 还发布了一个简单的启动包装器，用于初始化模拟器供 Linux 使用。由于采用 BSD 许可证，将其修改为实验起点毫无问题。最初这些实验很简单，因为没有任何文档——只有指令集概览。但正如 FreeBSD 经常遇到的情况，需要通过查看 Linux 来推断硬件工作方式，并找出与 ARMv7 的相似之处。有些东西（例如 MMU 页表）非常相似，可以直接动手；另一些（如系统寄存器）已发生变更，可能相同也可能不同。即便没有文档，我也推进到了从纯汇编转向执行 C 代码的阶段。MMU 仍未开启，但已能测试其余代码。

2013 年 9 月，ARM 发布了 ARMv8 架构参考手册（ARM Architecture Reference Manual），称为 ARM ARM。这使开启 MMU 所需的最后部分得以编写，任何魔数也都得到正确记录。有了它，代码可以从 GitHub 迁移到 FreeBSD 项目分支。作为其中一环，可以使用基本系统中的 llvm 和 clang，它们足够新，能生成 AArch64 代码，稍加修改即可为 FreeBSD 生成代码。仍需一份 binutils 来提供基本系统中过旧而缺失的几个 FreeBSD 工具。

## 启动环境

预计 AArch64 服务器上的启动环境将基于 UEFI，为了简化启动，让 FreeBSD 加载器支持 UEFI 是流程中的重要一步。在移植过程中，加载器可视为一个简单的单线程内核。遗憾的是，UEFI 启动环境要求加载器是 EFI 二进制文件。AArch64 上的流程比其他 UEFI 平台略难，因为 AArch64 版 binutils 无法将 ELF 文件转换为所需的 PE+ 格式。为绕过这一限制，需要组合使用汇编、链接器脚本和 `objcopy` 来创建镜像。PE+ 头在汇编文件中创建，链接器脚本提供二进制大小等所需信息，`objcopy` 从 ELF 镜像复制到二进制镜像，将 PE+ 头留在二进制开头。

即便加载器格式正确，要让它运行并加载内核还需要更多工作。首先，加载器可能需要重定位，过程类似于加载动态库。还需要实现若干函数，包括在物理内存与未来的内核虚拟内存之间复制数据的函数。这些函数以及主函数大多可以从现有移植复制并按需调整。

## 内核

对 AArch64，我决定使用 semihosting 文件系统来加载加载器和内核。Semi-hosting 是一种让运行在 ARM 处理器上的软件访问主机环境的方法。在物理硬件上，它通过调试适配器工作；在模拟器中，则允许访问其运行目录中的任何文件。UEFI 环境将其导出为可供任何 EFI 应用程序使用的文件系统。为访问它，我编写了新的文件系统处理程序，其中包含对 UEFI 实现中若干 bug 的规避。有了它，无需创建磁盘镜像即可加载内核，从而加快开发。

加载器能运行并加载内核之后，下一步就是为它构建要加载和运行的内核。内核需要编写若干机器相关函数。一开始，这些可以是桩函数，每个被调用时调用 `panic` 并打印自身名称。内核能构建后，需要加载并运行初始代码。由于我早期实验时就计划将其引入 FreeBSD，这简化了流程，因为我已经有了启动内核执行的代码。

但即便内核中有了这些代码，仍只能进入内核的 C 代码。此后还需要继续早期引导，包括解析加载器传入的任何内核元数据、引导 pmap、初始化内核。内核元数据包含描述硬件的数据，可以是 Flattened Device Tree Blob，或 ACPI 数据。由加载器传入这些数据，我们得以保持内核通用。

FreeBSD 有一层名为 pmap 的代码，用于处理虚拟内存子系统的机器相关部分，例如更新 MMU 以调整虚拟地址指向的物理地址。每种架构各不相同，且如 32 位 ARM 和 PowerPC 那样，单一架构内可能需要不同实现。arm64 上决定采用单个大型的直接映射区。这在需要物理地址到虚拟地址的计算时简化了代码。引导 pmap 层可能有点棘手。需要考虑既有映射，并添加新映射。

操作页表常常会出现问题，因为配置表项时若未正确执行 TLB 失效，可能导致意外结果。为此，ARM 添加了一条有用的指令，用于查看硬件如何执行地址转换。这条 AT 指令执行地址转换，并将结果返回给开发者查看。配合基于软件的页表遍历，它在追踪问题方面很有用。地址转换指令能返回若干有用信息，包括转换是否成功，以及物理地址或描述失败原因的标志。它还能对用户态地址和内核地址尝试转换。当内核出现异常行为时，通常首先检查这条指令——例如最近 shell 在 fork 后执行命令时崩溃。借助此功能，上下文切换和 pmap 代码中的问题被发现并修复。

完成早期平台初始化后，内核进入机器无关代码。但即便在此阶段，它仍会回调到机器相关代码。内核进行设备枚举就是其中一处。在 FreeBSD 中，我们有一棵设备树，其根是 nexus 设备。nexus 的职责是处理架构可能需要的任何总线驱动，为此它需要实现若干资源处理函数。这些函数涉及分配和释放设备内存与中断，以及配置中断。此配置是必需的，因为可能有多种不同的中断控制器——32 位 ARM 上就是如此。根据配置，可能存在标准的 ARM 通用中断控制器（Generic Interrupt Controller）；但这在支持 SMP 的芯片上才常见。在较老的设计中，设计者使用自有的中断控制器。

nexus 还需要处理设备内存分配。为此，它在资源管理抽象中配置内存资源的基本情况。驱动需要处理任何要分配的物理地址。FreeBSD 随后会限制物理内存大小，以管理 Open Firmware 和 Flattened Device Tree 总线的使用。预计 ACPI 也会以类似方式工作；但这一工作尚未开始，因为目前为止 ARMv8 的重点是使用 Flattened Device Tree，即便在 Linux 上也是如此。

仅有 nexus 以及 Open Firmware 和 Flattened Device Tree 设备，还不足以引导系统。我们至少还需要中断控制器、定时器和某种形式的控制台。在 Foundation Model 上，它们分别是通用中断控制器版本 2、全局定时器（Global Timer）以及 ARM pl011 UART。这些 FreeBSD 都已支持；但要让它们在 ARMv8 上工作仍需努力。最简单的是 UART。由于 ARM 为 Foundation Model 指定的设备树方式，父总线的基地址需要硬编码，但后来修正为正确解析数据。

中断控制器和定时器驱动需要更多工作。中断控制器改为支持多个控制器。这基于 PowerPC 设计，但预计在此独立项目集成到 CURRENT 时，会被更新后的 ARM 设计取代。在定时器方面，32 位 ARM 上访问定时器值的方式是通过系统协处理器，使用在协处理器和 ARM 寄存器之间移动数据的指令。在 AArch64 上，这些已移至特殊寄存器。此外，这些寄存器分为两组，一组用于物理定时器，另一组用于虚拟定时器。虚拟定时器被设置为物理定时器加上一个随机增量。通常内核只能访问虚拟定时器；但在某些情况下（例如作为 Hypervisor）可能访问虚拟定时器。为处理这一点，驱动的内部 API 已更新，可根据情况访问任一模式。

仅有这三个设备，内核走不了多远。首要问题是它需要读写设备内存。这一抽象称为总线空间（bus space），让单一驱动可以与总线通信而无需了解总线细节。总线空间提供若干读写设备的函数，最终所有这些都需要编写。在早期工作中只需要少数几个，主要是单项读写（1、2、4 或 8 字节）。没有它，任何驱动都无法访问其控制的设备。

总线空间抽象还处理这些总线资源的映射和取消映射。由于 ARM 内部设备总线是内存映射的，这要求总线空间分配一块虚拟地址，然后映射到正确的物理地址。为此，可以基本沿用现有 32 位 ARM 代码，稍作修改。

早期引导还需要实现内核的其他几个部分。内核期望创建内核线程。为此，内核有几个函数用于配置，或需要复制新线程和进程，并在它们之间切换。其中一些需要复制既有进程的数据或从零创建，另一些则负责将当前进程状态写入内存并加载新进程状态，以及处理任何缓存要求。把这些弄对可能很棘手，一种好的测试方法是让用户态工作起来，因为如果一个进程访问了错误的内存并修改了另一个进程的栈，会很快暴露出来。

到此阶段还不触发硬件异常几乎不可能。主要原因是某种内存错误——例如内核访问无效虚拟地址。为处理这一点，我写了一个简单的异常处理程序。它需要保存寄存器状态，调用适当的处理函数，然后恢复寄存器并返回到原执行处。早期它可以很简单，因为只需处理内核映射，其中地址的最高位为 1。如果发现无效地址，内核可以转储保存的寄存器以帮助追踪问题。

至此，中断配置、设备内存的分配和访问、进程创建和切换，以及简单的异常处理都已就绪。这让内核达到了第一个重要里程碑：到达 mountroot 提示符。此时系统已有足够部分运行，内核尝试挂载根文件系统并失败。调度器靠定时器中断运行，系统 UART 工作，任何触发的异常至少能被处理，哪怕是 panic。

项目推进到此，运行的还只是一个内核。用户态仍未工作，它需要某种文件系统。FreeBSD 能够将小型文件系统镜像嵌入内核，但要构建它，需要移植用户态的部分，并定义系统调用接口。

AArch64 将系统调用作为硬件异常在内核中处理，由内核决定如何解码用户态调用的系统调用。为此，我决定参考 Linux 的做法，将异常值存入寄存器。libc 和内核都需要更新以分别编码或处理这些。除此之外，libc 中 `setjmp` 和 `longjmp` 等函数也需要编写。最初只需要静态库，这能简化早期工作。随着更多用户态被移植，所需的 libc 函数可以逐步添加。在 arm64 上，目前只实现了非常少的函数集，但更多函数正在按需添加。

要开始执行应用程序，需要一小段名为 csu 的机器相关用户态代码。它需要从内存加载 argc 和 argv，以及环境和内核设置的若干其他指针。可能还有需要在 `main` 之前运行的代码，例如创建静态 C++ 对象。这些都由 csu 处理。

有了这些，构建简单文件系统所需的大多数用户态库就可以构建了。可用文件系统的最低要求是 `init` 和 `sh`。构建好两者后，我使用 `makefs` 命令。它接受一个目录结构并生成 UFS 文件系统镜像。如果镜像足够小，可以嵌入内核；4MB 的镜像足以容纳静态、剥离过的 `init`、`sh` 和 `ls`。

实现了所有必需的内核桩函数后，运气好的话，`init` 应该开始执行。最初，只会存在间接证据，例如通过观察异常处理程序中的状态。随着更多 bug 被修复，`init` 会走得更远，直到——如果 `init` 被设置为引导到单用户模式——它会打印一条消息让你选择 shell。此时可能还需要进一步修复，但内核应基本为用户态做好准备，`init` 会尝试运行 shell，shell 可用于运行文件系统中的任何其他应用程序。

截至本文撰写时，arm64 移植已进展到此阶段。FreeBSD 可以在 Foundation Model 上以内核文件系统启动并运行静态二进制文件。已开始加载动态可执行文件；但这仍只能从单用户模式运行。后续工作正朝着稳定移植推进。短期内，正在让 FreeBSD 在 Cavium ThunderX 硬件上运行，并支持动态二进制和多用户模式。移植仍只在单核上运行，而由于 ThunderX 核心众多，让 SMP 工作是必需的。

展望更远的未来，还需要稳定性工作，确保代码在生产环境中能正常工作。一种方式是在硬件上尝试构建 Ports。这也会让我们看到 Ports 树在这一新平台上的状态。对 DTrace 和 hwpmc 等其他项目也有兴趣。此外，总会需要将 FreeBSD 移植到任何新出现的硬件，因为遗憾的是，大量设备是各硬件厂商专有的。

## 致谢

我要感谢 FreeBSD 基金会，以及 ARM Ltd 和 Cavium 赞助本项目。由于这只是一个开始，后续仍需努力。芯片厂商需要发布文档，以便 FreeBSD 移植到他们的芯片。进一步的赞助也将有助于让 FreeBSD 在 ARMv8 上达到稳定且可用于生产的状态。

***

**Andrew Turner** 最早接触 FreeBSD on ARM，将其移植到了 OpenMoko 手机中的 Samsung CPU，并负责将 ARM EABI 支持引入 FreeBSD。他作为嵌入式软件工程师，参与过从只有几 KB RAM 的深度嵌入式 ARM 设备，到拥有数 GB 内存的多核 ARM 板的项目。他也以承包商身份将 FreeBSD 移植到 ARMv8 芯片。

## 延伸阅读

* FreeBSD wiki – <https://wiki.freebsd.org/arm64>
* Technology Preview: The ARMv8 Architecture – <http://www.arm.com/files/downloads/ARMv8_white_paper_v5.pdf>
* ARM Architecture Reference Manual – <http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0406c/index.html>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://freebsd-journal-cn.bsdcn.org/20150102-mips-yu-arm64/arm64.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
