OS X 如何在 32 位内核上运行时运行 64 位二进制文​​件?

OS X 如何在 32 位内核上运行时运行 64 位二进制文​​件?

我最近发现,即使加载了 x86 内核,Mac OS X 实际上也可以运行 64 位 (x64) 应用程序。这对我来说是第一次感到震惊。

但后来我意识到,如果系统在 x64 兼容 CPU 下运行,无论哪个内核管理进程,都无法运行 x64 应用程序,这真的很奇怪。真的那么难吗?只需将该应用程序加载到内存中并将 CPU 操作指针设置为第一个字节,非常简单!

我可以想象,实现这一点的唯一障碍是某种“可执行文件头”。不幸的是,我对 Windows 架构和二进制结构不太熟悉,因此我需要在这里进行更多解释。

事实上,类 UNIX 操作系统二进制头标准 ELF 有它的兄弟 ELF64,它(如文档这里描述)与 ELF32 没有太大区别,但即使 32 位内核也无法运行 x64 代码。是的,这个程序可能链接到 x64 库,让我们想象一下我们只是将它们复制并粘贴到/usr/lib64文件夹中。但我很确定这没有帮助,为什么?

最后,Mac OS X 内核有什么特别之处,以至于它不必担心所使用的程序指令集?Mac OS X 是否有一些通用且适用于两个内核的可执行文件头,因此它只需将应用程序加载到内存中并对 CPU 说“从这里开始执行,我不介意它代表什么”?

附言:我确实考虑过将这个问题放在哪里:放在 stackoverflow.com 还是 superuser.com,最后决定放在这里,因为这个主题很可能是更多特定于操作系统的东西。

答案1

真正的问题是为什么其他一些操作系统不能在 32 位内核上运行 64 位二进制文​​件。没有根本原因导致它不可能。底层处理器架构同时支持 64 位指令集(amd64 又名 x86-64)和 32 位指令集(i386),并且对两者一起使用没有任何限制(特别是,没有与“32 位模式”分开的“64 位模式”;只有一个长模式,它允许来自 i386 和“本机” amd64 集的指令)。

在 32 位内核上运行 64 位应用程序确实需要在内核内部做更多工作,因为它必须管理指向用户空间的 64 位指针以及指向内核空间的 32 位指针。内核中传递的大多数(如果不是全部)指针要么指向内核空间,要么指向用户空间,因此即使它们的大小不同也没有问题。主要的困难在于放弃拥有通用指针类型的可能性,该指针类型具有针对进程内存、内核内存和各种硬件(包括 RAM)使用的内存的单独值范围,但这在 PC 级硬件上的最新 32 位内核中无论如何都是不可能的(如果您有 4GB 或更多的 RAM,或者想要映射 2GB 的 RAM 加上 2GB 的进程空间加上内核内存等,无论如何您都需要能够映射超过 32 位的地址)。

根据维基百科文章正如您所引用的,OSX 在拥有 64 位内核之前就能够在 amd64 处理器上运行 amd64 进程。Solaris 还会在 amd64 处理器上随意混合 i386 和 amd64 可执行文件,无论内核是 32 位还是 64 位(两者都可用)。

其他操作系统可以在 (64 位) amd64 内核上运行 i386 进程,但不能在 32 位内核上运行 amd64 进程,例如 Linux、FreeBSD、NetBSD 和 Windows。然而,其他操作系统将 amd64 和 i386 视为完全不同的架构,例如 OpenBSD。

答案2

我对 x86_64 架构不够熟悉,无法给出详细信息,但本质上发生的事情是 CPU 在 64 位模式和兼容(32 位)模式之间切换,这是内核和用户空间程序之间的上下文切换的一部分。这与在 64 位内核下运行 32 位程序几乎相同,只是反向发生。

顺便说一句,OS X 不使用 ELF 二进制格式,它使用马赫-O二进制文件。Mach-O 格式允许多架构(“通用”)二进制文件,因此程序(以及内核)可以同时以 32 位和 64 位(以及 PPC 和 PPC64 等)提供,并且操作系统可以在加载时选择要加载哪个版本(以及因此以哪种模式运行它)。您可以file在二进制文件上使用命令来查看其格式。例如,这是随 OS X v10.5 一起提供的 Chess 应用程序:

$ file Applications/Chess.app/Contents/MacOS/Chess 
Applications/Chess.app/Contents/MacOS/Chess: Mach-O universal binary with 4 architectures
Applications/Chess.app/Contents/MacOS/Chess (for architecture ppc): Mach-O executable ppc
Applications/Chess.app/Contents/MacOS/Chess (for architecture ppc64):   Mach-O 64-bit executable ppc64
Applications/Chess.app/Contents/MacOS/Chess (for architecture i386):    Mach-O executable i386
Applications/Chess.app/Contents/MacOS/Chess (for architecture x86_64):  Mach-O 64-bit executable x86_64

对于那些怀疑这是否可能的人,请注意:OS X 从 v10.4 开始支持 64 位程序(API 支持有限),但直到 v10.6 才包含 64 位内核(即使如此,内核在大多数型号上默认以 32 位模式运行)。请参阅Apple 的 64 位过渡指南了解详情。我使用运行 10.6 版 32 位内核的 MacBook Pro 发布此帖(此特定型号不支持 64 位内核),但根据活动监视器仅有的未在 64 位模式下运行的进程是 kernel_task。

答案3

Mac 支持在 32 位内核上运行 64 位应用程序,因为有一个多阶段计划可以做到这一点:

  1. Mac 应用程序以“软件包”中的“胖二进制文件”形式发布,允许 64/32 位和 Intel/PPC 的所有四种组合成为单个安装的一部分,只需进行一次拖放即可完成。操作系统会运行相应的程序。
  2. 运行 32 位内核的 Mac 使用 PAE 访问超过 4GB 的 RAM。Windows 不允许在非服务器版本上使用 PAE,因为存在与驱动程序的兼容性问题,而 Windows 有很多驱动程序,包括第三方驱动程序。
  3. Tiger 添加了 64 位 ABI(应用程序二进制接口),以在 32 位内核上运行 64 位代码,并添加了用于“控制台”(非 GUI)应用程序的 64 位版本的低级 API(应用程序编程接口)。
  4. Leopard 为 GUI 应用程序添加了 64 位 Cocoa(但没有添加 64 位 Carbon)。
  5. Snow Leopard 增加了 64 位内核,这是仅某些高端机型的默认内核。
  6. Lion 需要 64 位 CPU,但仍包含 32 位内核。例如,如果旧 Mac 配有 64 位 CPU,但 GPU 仅配有 32 位驱动程序,则必须运行 32 位内核。

因此,OS X 尽快支持 64 位应用程序,并尽可能长时间地继续运行 32 位内核,以应对驱动程序的情况。(内核的位数只有在尝试管理大量 RAM 时才会成为一个因素——页表也占用内存——而切换到 64 位内核会带来一些性能优势。)但 Apple 绝对不会羞于放弃一些东西。

那么真正的问题是为什么 Windows 和 Linux 没有做同样的事情。对于 Windows,请考虑一下他们首次尝试 Win64 是在 Itanium 上,这完全不同。但最终答案可能归结为过去几十年来它通常所做的事情:与一堆第三方程序兼容,做事不太正确

OS X 的 64 位实现与 Windows 有很大不同,Windows 将其 32 位和 64 位版本视为存储在不同安装介质上的两个不同的操作系统。这样做主要是为了保持 Windows 与旧应用程序的兼容性——移动或重命名 System32 文件夹之类的东西会破坏期望它在那里的程序——因此,两者被分离到 32 位 Windows 和 64 位 Windows 之间甚至没有升级路径的地步。正因为如此,而且由于 Windows 应用程序和驱动程序通常具有不同的 32 位和 64 位版本,Windows 向 64 位的过渡略显坎坷,对用户来说也略显明显。

关于 64 位过渡的背景信息有很多,Mac 端Windows 端。(这些链接指向每一系列文章的最后一篇;请务必返回到每篇文章的开头。)

我不知道 Linux 的故事是怎样的,但可以想象 Linus 对它有强烈的意见。

答案4

我意识到,如果系统在 x64 兼容 CPU 下运行,无论哪个内核管理进程,都无法运行 x64 应用程序,这真的很奇怪。真的那么难吗?只需将该应用程序加载到内存中并将 CPU 操作指针设置为第一个字节,非常简单!

您遗漏了一个重要部分。应用程序会调用操作系统提供的功能和服务的 API。如果 64 位应用程序向 32 位操作系统发送 64 位指针,事情就会变得一团糟。

我怀疑,为了让事情以任何方式正常工作,操作系统必须重载该函数并提供每个函数的 64 位和 32 位版本。对于每个内核,“off”函数(32 位内核上的 64 位函数,64 位内核上的 32 位函数)将只是一个存根,它将调用转换为 32 位安全,然后重新调用本机函数。

相关内容