下载本文的 PDF 版本 PDF

通往 64 位的漫长道路

双倍,双倍,辛劳和麻烦——莎士比亚,《麦克白》

约翰·R·马希,技术顾问

莎士比亚的话语(《麦克白》,第四幕,第一场)常常涵盖超出他最狂野梦想的情境。即使人们提前计划,重大的计算过渡也伴随着辛劳和麻烦。为了校准“今天的明日遗产”,我们应该研究“昨天的明日遗产”。明日的大部分软件仍将受到数十年前决策的驱动。过去的决策会产生意想不到的副作用,这些副作用会持续数十年,并且难以消除。

例如,考虑一下 32 位微处理器系统演变为 64/32 位系统的过程,这个过程过于漫长、常常很尴尬,有时还充满争议,而 64/32 位系统是寻址更大存储空间和运行 32 位和 64 位用户程序混合体所必需的。现在,大多数主要的通用 CPU 都有这样的版本,因此位数“翻了一番”,但“辛劳和麻烦”尚未结束,尤其是在软件方面。

这个例子说明了硬件、语言(尤其是 C 语言)、操作系统、应用程序、标准、既有基础惯性和行业政治之间的相互作用。我们可以从中吸取从高层战略到编程细节的教训。

基本问题(1980 年代后期)

地址空间不足是计算领域由来已久的传统,而且通常是可以预测的。摩尔定律使 DRAM 的大小大约每三到四年增加四倍,到 1990 年代中期,人们能够负担得起中档微处理器系统的 2 到 4 GB 内存,此时简单的 32 位寻址(4 GB)会变得很麻烦。理想情况下,64/32 位 CPU 本应尽早(1992 年)开始出货,以便在实际需要之前,它们已占相关既有基础的大多数。然后,人们可以切换到 64/32 位操作系统,并停止升级仅 32 位的系统,从而实现平稳过渡。供应商的上市时间自然各不相同,但出货时间从“刚好及时”到“相当晚”不等。考虑到地址位不足的悠久且众所周知的历史,以及摩尔定律的明显可预测性,这有点奇怪。客户常常无法使用他们可以轻松负担的内存。

有些设计决策很容易更改,但另一些设计决策则会产生长期遗产。此处说明的包括以下几点:

约束。硬件人员需要在正确的时间构建 64/32 位 CPU——既不能太早(额外成本,没有市场),也不能太晚(竞争,愤怒的客户)。现有的 32 位二进制文件需要在向上兼容的 64/32 位系统上运行,并且可以预期它们将永远共存,因为许多二进制文件永远不需要成为 64 位的。因此,32 位不能成为临时的兼容性功能,以便在以后的芯片中快速丢弃。

软件设计人员需要就整套标准达成一致;构建双模操作系统、编译器和库;并修改应用程序源代码以在 32 位和 64 位环境中工作。必须正确处理许多细节,以避免冗余的硬件工作并保持软件的健全性。

解决方案。虽然并非没有细微的问题,但硬件通常很简单且成本不高——第一个商用 64 位微处理器的 64 位数据路径最多增加了芯片面积的 5%,并且这个比例在后来的芯片中迅速下降。大多数芯片都采用了相同的通用方法,即将 32 位寄存器扩展到 64 位。软件解决方案要复杂得多,涉及到关于 64/32 位 C 语言、现有软件的性质、供应商之间的竞争/合作、官方标准以及有影响力但完全非官方的临时小组的争论。

遗产。IBM S/360 已经有 40 年的历史了,仍然支持 24 位遗留寻址模式。64/32 解决方案最多有 15 年的历史,但实际上将永远与我们同在。5000 年后,是否会有一些软件维护人员仍然喃喃自语,“他们当时为什么这么蠢?”1

我们设法度过了 Y2K 问题——付出了很多努力。我们仍在努力解决 64/32 位问题。我们还有其他类似的问题吗?64 位 CPU 是否足以帮助解决“Unix 2038”问题,或者我们是否需要在这方面更加努力?我们会用完 64 位系统吗,到那时我们将怎么办?IPv6 会尽快得到足够广泛的实施吗?

所有这些都是长期存在的问题的例子,适度的远见卓识可以避免以后的辛劳和麻烦。但是软件就像政治:有时我们会等到问题真的让人痛苦时才去解决它。

问题:CPU 必须寻址可用内存

任何 CPU 都可以有效地寻址一定数量的虚拟内存,最方便的方法是通过平面寻址来完成,在这种寻址方式中,整数寄存器中的全部或大部分位形成一个虚拟内存地址,该地址可能大于或小于实际物理内存。每当可负担的物理内存超过容易寻址的范围时,就很难再通过增加内存来解决性能问题,并且编程复杂性会迅速上升。有时,分段内存方案已被使用,其成功程度和编程痛苦程度各不相同。历史上充满了笨拙的扩展,这些扩展增加了一些位以延长产品寿命几年,但通常以操作系统人员的辛勤工作为代价。

几十年来,摩尔定律一直在增加可负担的内存。磁盘的增长速度甚至更快,尤其是自 1990 年以来。较大的磁盘指针比小的磁盘指针更方便,尽管不如内存指针那么关键。当使用映射文件时,这些会相互作用,迅速消耗虚拟地址空间。

在 1980 年代中期,一些人开始考虑 64 位微处理器——例如,DEC(数字设备公司)构建的实验系统。MIPS 计算机系统在 1988 年底决定,其下一个设计必须是真正的 64 位 CPU,并在 1991 年发布了 R4000。许多人认为 MIPS 疯了,或者至少是为时过早。我认为该系统来得恰好及时,可以开发软件来匹配不断增长的 DRAM,并且我写了一篇文章来解释原因。2 从那时起,这些问题并没有发生太大的变化。

N 位 CPU。按照长期以来的习惯,N 位 CPU 实现一个 ISA(指令集架构),该 ISA 具有 N 位整数寄存器和 N(或接近 N)个地址位,忽略总线或浮点寄存器的大小。许多“32 位” ISA 具有 64 位或 80 位浮点寄存器,以及具有 8 位、16 位、32 位、64 位或 128 位总线的实现。有时,营销人员会对此感到困惑。我在这里使用术语 64/32 位来区分较新的微处理器和较旧的面向 64 位字的超级计算机,因为软件问题有些不同。从相同的意义上讲,英特尔 80386 可能被称为 32/16 位 CPU,因为它保留了对早期 16 位模型的完全支持。

为什么是 2N 位? 人们有时需要更宽字长的计算机来提高并行位运算或数据移动的性能。如果您需要 2N 位运算(加法、乘法等),则可以在 2N 位 CPU 上用一条指令完成,但在 N 位 CPU 上则需要更长的序列。这些是直接的低级性能问题。然而,更宽字长的典型令人信服的原因一直是需要增加地址位,因为对于具有足够地址位的代码来说,简单而高效,可能需要进行全局重组才能在更少的位中生存。

通用系统中的寻址——虚拟地址和实际地址。用户虚拟地址映射到实际内存地址,可能带有中间页错误,操作系统由此将所需的代码或数据从磁盘映射到内存中。用户程序最多可以访问 VL(虚拟限制)字节,其中 VL 从某个硬件限制开始,然后有时会因操作系统而损失更多空间。例如,32 位系统很容易具有 4、3.75、3.5 或 2 GB 的 VL。给定的程序执行最多使用 PM(程序内存)字节的虚拟内存。对于许多程序,PM 可能因输入而异,但当然 PM ≤ VL。

RL(实际限制)对操作系统可见,并且通常受物理地址总线宽度的限制。有时,映射硬件用于将 RL 扩展到太小的“自然”限制之外(如 PDP-11 中发生的那样,稍后会介绍)。安装的 AM(实际内存)对用户程序不太可见,并且在机器之间有所不同,而无需用户程序的不同版本。

最常见的情况是 VL ≥ RL ≥ AM。一些程序为了方便而消耗虚拟地址空间,并且当 PM >> AM 时实际上表现良好:我见过 4:1 仍然有效的情况,这是良好局部性的结果。文件映射可以进一步增加该比率并且仍然有效。另一方面,一些程序在 PM > AM 时运行不佳,这证实了古老的谚语,“虚拟内存是出售实际内存的一种方式。”

有时,计算机系列以 VL ≥ RL ≥ AM 开始,然后 AM 增长,并且 RL 可能以仅对操作系统可见的方式增加,此时 VL << AM。单个程序根本无法使用容易购买的内存,迫使工作被拆分,使其更加困难。例如,在 Fortran 中,声明 REAL X(M, M, M) 是一个三维数组。如果 M=100,则 X 需要 4 MB,但人们希望相同的代码能够为 M=1,000 (4 GB) 或 6,300 (1,000 GB) 运行。少数这样的系统确实存在,尽管它们并不便宜。我曾经有一个客户抱怨缺乏对 1,000 GB 实际内存的当前支持,尽管后来客户能够购买这样的系统并在一个程序中使用该内存。之后,客户抱怨缺乏 10,000 GB 的支持……。

当然,在多任务系统中增加 AM 仍然有助于提高内存驻留任务的数量或减少分页开销,即使每个任务仍然受到 VL 的限制。如果操作系统代码可以直接且简单地寻址所有已安装的内存,而无需管理额外的内存映射、库寄存器等,则操作系统代码始终会得到简化。

地址位不足的问题由来已久。

大型机、小型计算机、微处理器

经常被引用的乔治·桑塔亚纳在这里很合适:“那些不能记住过去的人注定要重蹈覆辙。”

大型机。IBM S/360 大型机(约 1964 年;参见随附的年表侧边栏)具有 32 位寄存器,其中只有 24 位用于寻址,内存限制为 16 MB。这在当时被认为是巨大的。“大型”大型机最多提供 1 MB 的内存,尽管少数“巨型”大型机可以提供 6 MB。大多数 S/360 不支持虚拟内存,因此用户程序直接生成物理地址,并且已安装的 AM 在操作系统和用户程序之间进行分区。16 MB 的限制是不幸的,但忽略(而不是捕获)高 8 位更糟糕。汇编语言程序员“巧妙地”将 8 位标志与 24 位地址打包到 32 位字中。

随着虚拟寻址 S/370(1970 年)使程序能够大于物理内存允许的大小,以及随着磁芯让位于 DRAM(1971 年),16 MB 的限制变得不足。IBM 370-XA CPU(1983 年)添加了 31 位寻址模式,但保留了(必要的)24 位模式以实现向上兼容性。我曾经是那些“聪明”的程序员之一,并且有点惊讶地发现,最初于 1970 年编写的一个大型程序(S/360 ASSIST 汇编器)在 2006 年仍在使用——在 24 位兼容模式下,因为它无法以任何其他方式运行。曾经“聪明”的编译器代码早已停止这样做,但是汇编代码更难。(“人生前行,恶名常存,善行随逝世而湮没。”——莎士比亚,再次,《裘力斯·凯撒》)

然后,即使是 31 位寻址对于某些应用程序(尤其是数据库)也变得不足。ESA/370 (1988) 提供了用户级分段来访问内存的多个 2 GB 区域。

64 位 IBM zSeries (2001) 在 40 多年后仍然支持 24 位模式。为什么会发生 24 位?我被告知,这完全是为了一个低成本的早期型号 360/30,其中 32 位会因为具有 8 位数据路径而运行得更慢。这些型号最后一次出货是在 30 多年前。它们值得付出数十年的额外麻烦吗?

小型计算机。在 16 位 DEC PDP-11 小型计算机系列(1970 年)中,单个任务仅寻址 64 KB,或者在后来的型号(1973 年)中,寻址 64 KB 的指令加上 64 KB 的数据。“计算机设计中可能犯的最大和最常见的错误是没有为内存寻址和管理提供足够的地址位,”C. Gordon Bell 和 J. Craig Mudge 在 1978 年写道。“PDP-11 遵循了节省地址位的悠久传统,但由于一个好的设计可以通过至少一次重大更改来演变,因此得以幸免于难。对于 PDP-11,有限的地址空间在短期内得到了解决,但其精妙程度不足以支持大型小型计算机系列。这确实是一个代价高昂的疏忽。”3

公平地说,要以 32 位硬件实现 PDP-11 的成本目标是困难的,但我认为 DEC 低估了 DRAM 内存价格下降的速度。无论如何,这种情况持续了很长时间——PDP-11 最终于 1997 年停产!

PDP-11/70 (1976) 提高了可支持的并发任务数量,但任何单个程序仍然只能使用最大 4 MB 中的 64 KI(指令)+ 64 KD(数据),因此单个大型程序需要不自然的行为才能将代码和数据拆分为 64 KB 的片段。有些人认为这鼓励了模块化并抑制了“功能蔓延”,因此在哲学上是好的。

尽管 32 位 VAX-11/780 (1977) 仅比 PDP-11/70 稍快,但增加的地址空间是一项重大改进,结束了高端 PDP-11 的发展。VAX 架构师 William Strecker 这样解释道:“然而,有些应用程序在 65 KB 的地址空间中进行编程是不切实际的,也许更重要的是,其他一些应用程序通过拥有更大的地址空间而大大简化了编程。”4

微处理器。英特尔 8086 的 16 位 ISA 似乎很可能落入 PDP-11 的问题(1978 年)。但是,它确实为显式段操作提供了用户模式机制。这允许单个程序访问超过 64 KB 的数据。PC 程序员熟悉曾经需要的多种内存模型、库、编译器标志、扩展器和其他工件。80386 提供了 32 位平面地址(1986 年),但当然保留了早期的机制,并且 16 位 PC 软件“永远”存在。中间的 80286 (1982) 说明了修补架构以获得更多地址位的难度。

32 位摩托罗拉 MC68000 (1979) 从平面寻址编程模型开始。通过忽略 32 位寄存器的高 8 位,它完全重复了 S/360 的错误。再一次,“聪明”的程序员找到了这些位的用途,当 MC68020 (1984) 解释所有 32 位时,一些程序崩溃了——例如,当从最初的 Apple Macintosh 迁移到 Mac II (1987) 时。

幸运的是,64 位 CPU 设法避免了重复 S/360 和 MC68000 的问题。尽管早期版本通常实现 40 到 44 位虚拟地址,但它们捕获了对尚未实现的更高 v 位的使用,而不是忽略它们。人们最终会学到东西。

教训

1980 年代后期的 32 位到 64 位问题

到 80 年代后期,摩尔定律似乎已铸入硅片,并且很明显,到 1993-94 年,中档微处理器服务器可以经济高效地提供 2-4 GB 或更多的物理内存。我们已经看到实际程序有效地使用了比已安装物理内存多 4:1 的虚拟内存,这意味着 1993-94 年的压力,以及 1995 年的真正麻烦。正如我在 1991 年 9 月的 BYTE 中写道:5

“虚拟寻址方案通常可以超过可能的物理地址的限制。64 位地址可以处理字面意义上的内存山:假设 1 兆字节的 RAM 需要 1 立方英寸的空间(使用 4 兆位 DRAM 芯片),2**64 字节将需要一平方英里的 DRAM,堆积高度超过 300 英尺!目前,没有人期望寻址这么多的 DRAM,即使是下一代 16 MB DRAM 芯片,但将物理内存稍微增加到 32 位以上绝对是一个目标。使用 16 MB DRAM 芯片,2**32 字节仅占用 1 立方英尺多一点的空间(不包括冷却)——对于桌面系统来说是可行的……。”

“数据库系统通常将单个文件分散在多个磁盘上。当前的 SCSI 磁盘最多可容纳 2 千兆字节(即,它们使用 31 位地址)。将文件位置计算为虚拟内存地址需要整数运算。操作系统习惯于解决此类问题,但进行变通变得令人不快;程序员不仅仅是为了让事情顺利运行,而是在努力使某些东西能够运行……。”

因此,人们开始对这个问题采取行动。

SGI(硅谷图形公司)。从 1992 年初开始,所有新的 SGI 产品都仅使用 64/32 位芯片,但最初它们仍然运行 32 位操作系统。在 1994 年末,为大型服务器引入了 64/32 位操作系统和编译器,能够同时支持 32 位和 64 位用户程序。该软件逐渐应用于整个产品线。一些客户很快购买了超过 4 GB 的内存,并在一天之内重新编译了程序以使用它,在某些情况下,仅仅通过更改一个 Fortran 参数即可。然而,低端 SGI 工作站多年来继续出货仅限 32 位的操作系统,当然,现有的 32 位硬件也必须得到支持——多年。由于历史原因,SGI 具有比真正需要的更多的 32 位和 64 位指令集变体,因此实际上比只有两种更糟糕。

这就是糟糕的“长尾”——人们关注“首次发货日期”,但通常“最后发货日期”更重要,正如“我们将发布可以在该系统上运行的操作系统或应用程序的新版本的最后日期”一样重要。在 80386 发布 20 年后,Windows 16 位应用程序仍然在常规 Windows XP 上运行。这种支持最终已在 Windows XP x64 中取消。

DEC。DEC 在 1992 年底出货了 64 位 Alpha 系统,配备了 64 位操作系统,并在 1994 年底开始出货内存大到需要超过 32 位寻址的服务器。DEC 可能已经要求(简单的)32 位端口,但从长远考虑,它直接转向了 64 位,避免了重复。让第三方软件实现 64 位兼容在时间和金钱上都是昂贵的,但这对行业很有价值,因为它加速了 64 位兼容的清理工作。DEC 这样做可能是正确的,因为它没有 32 位 Alpha 程序的既有基础,并且可以避免不得不支持两种模式。对于 VMS,早期版本是 32 位的,后来的版本是 64/32 位的。

其他供应商。在接下来的几年中,许多供应商出货了 64 位 CPU,通常运行 32 位软件,后来又出货了 64/32 位 CPU:Sun UltraSPARC (1995)、HAL SPARC64 (1995)、PA-RISC (1996)、HP/UX 11.0 (1997)、IBM RS64 和 AIX 4.3 (1997)、Sun Solaris 7 (1998)、IBM zSeries (2001)、英特尔 Itanium (2001)、AMD AMD64 (2003)、英特尔 EMT64a (2004)、微软 Windows XP x64 (2005)。Linux 64 位版本在不同的时间出现。

大多数 64 位 CPU 被设计为现有 32 位架构的扩展,可以很好地运行现有的 32 位二进制文件,通常通过在 64 位模式下将 32 位寄存器扩展到 64 位,但在 32 位模式下忽略额外的位。这些版本发布的时间跨度很长,这是由于优先级的自然差异造成的。SGI 对高性能技术计算特别感兴趣,其用户习惯于 64 位超级计算机,并且通常可以通过简单地增加 Fortran 程序中的一个数组维度并重新编译来使用 64 位。SGI 和其他大型服务器供应商也关心大型数据库应用程序的内存。对于 X86 CPU 供应商来说,这当然不太重要,因为它们的销量主要来自 PC。在英特尔的案例中,也许对 Itanium 的重视延迟了 64 位 X86 的发展。

在 2006 年,4 GB 的 DRAM(由 1 GB DRAM 组成)通常使用四个 DIMM,成本可能低于 400 美元。至少一些大型笔记本电脑支持 4 GB,并且 300 GB 的磁盘广泛可用,每 GB 不到 1 美元,因此人们会期望现在对 64 位有成熟而广泛的支持。然而,这一切花费的时间可能比应有的时间更长,并且在过去的许多年中,人们可以购买内存,但无法方便地寻址它,或者无法购买某些第三方应用程序,因为此类应用程序自然会落后于 64 位 CPU。即使即将到来的问题肯定是众所周知的,也值得理解为什么以及如何发生这种情况。

教训

软件更难:操作系统、编译器、应用程序、用户、标准

构建 64 位 CPU 是不够的。嵌入式系统市场比通用市场更容易移动,例如,Cisco 路由器和 Nintendo N64 使用了 64 位 MIPS 芯片就是这种情况。然而,大多数 32 位系统的供应商必须通过以下所有步骤才能生产出有用的向上兼容的 64 位系统:

  1. 出货配备 64/32 位 CPU 的系统,可能以 32 位模式运行。继续支持仅限 32 位的 CPU,只要它们仍在出货,并在之后多年(通常为五年或更长时间)。大多数供应商都这样做,仅仅是因为软件需要时间。
  2. 为 C、C++ 和其他语言选择 64 位编程模型。这涉及到与标准机构的讨论以及与竞争对手的协商。如果您选择与大多数竞争对手不同的模型,可能会产生严重的后果。Unix 供应商和微软出于合理的原因选择了不同的模型。认真考虑跨语言问题——Fortran 期望 INTEGER 和 REAL 的大小相同,这使得 64 位默认整数变得笨拙。
  3. 仔细清理头文件。
  4. 构建编译器以生成 64 位代码。编译器本身几乎肯定以 32 位模式运行并交叉编译为 64 位,尽管偶尔会出现巨大的机器生成的程序,需要以 64 位模式运行的编译器。请注意,程序员的理智通常需要在此处执行引导步骤,其中首先修改 32 位编译器以接受 64 位整数,然后重新编码以自行使用它们。
  5. 将操作系统转换为 64 位,但也包括 32 位接口,以运行 64 位和 32 位应用程序。
  6. 创建所有系统库的 64 位版本。
  7. 在新发布的 64 位硬件上,以及希望在已经出货一段时间的早期 64 位硬件上,发布新的操作系统和编译器。这包括(至少)支持每个库的两种版本。
  8. 说服第三方软件供应商支持任何相关的程序的 64 位版本。在此过程的早期,已安装的基础不可避免地仍然主要是 32 位的,并且软件供应商会考虑潜在的市场规模与在同一平台上支持两个版本的成本。DEC 通过为许多 64 位 Alpha 端口付费,帮助行业解决了 32 位到 64 位可移植性问题。
  9. 停止出货 32 位系统(但继续支持它们多年)。
  10. 最终停止使用新的操作系统版本支持 32 位硬件。

从步骤 1 到步骤 6 通常需要两到三年的时间,而完成步骤 9 则需要更多年。行业尚未完成步骤 10。

操作系统供应商可以避免步骤 1,但除此之外,问题是相似的。许多程序永远不需要转换为 64 位,尤其是在许多操作系统已经为 32 位程序支持 64 位文件指针的情况下。

下一节将追溯 1990 年代发生的一些曲折,尤其是在 64/32 位 CPU 上 C 语言的实现方面。这个话题引发了无休止的,有时甚至是尖刻的讨论。感兴趣的读者应查阅参考文献 6-9。受虐狂读者可能会在新闻组 comp.std.c 中搜索“64 位”或“long long”。

C 语言:64/32 位 CPU 上的 64 位整数——技术和政治

人们使用了各种(并非总是前后一致的)表示法来描述 C 数据类型的选择。在表 1 中,就我所知,几个标签中的第一个标签是最常见的。在具有 8 位 char 的机器上,short 通常是 16 位,但其他数据类型可能会有所不同。常见选择如表 1 所示。

早期。早期的 C 整数 (1973) 仅包括 int 和 char;然后在 1976 年添加了 long 和 short,然后在 1977 年添加了 unsigned 和 typedef。在 1970 年代后期,16 位 PDP-11 的既有基础加入了较新的 32 位系统,这要求源代码在 16 位系统(使用 I16LP32)和 32 位系统 (ILP32) 之间是高效、可共享和兼容的,这种配对效果良好。PDP-11 仍然在大多数情况下使用(高效的)16 位 int,但可以根据需要使用 32 位 long。32 位系统在大多数情况下使用 32 位 int,这更有效,但可以通过 short 来表示 16 位。用于在机器之间通信的数据结构避免使用 int。32 位 long 在 PDP-11 上可用非常重要。在此之前,PDP-11 需要显式函数来操作 int[2](16 位 int 对),这样的代码不仅笨拙,而且也无法与 32 位系统简单地共享。这是一个极其重要的点——long 对于 32 位 CPU 来说并非绝对必要,但它对于在 16 位和 32 位环境之间实现代码共享非常重要。如果我们的所有系统都是 32 位的,我们本可以只使用 char、short 和 int。

重要的是要记住此时 C 语言的性质。typedef 花了一段时间才成为一种常见的用法。事后看来,提供一组标准的 typedef 来表示“快速整数”、“保证为精确 N 位整数”、“大到足以容纳指针的整数”等,并建议人们基于这些定义而不是基本类型构建自己的 typedef,这可能是明智之举。如果这样做,也许可以避免许多辛劳和麻烦。

然而,这将是非常反文化的,并且需要惊人的预知能力。贝尔实验室已经在 36 位 CPU 上运行 C 语言,并且正在努力实现可移植性,因此诸如“int16”之类的过于具体的构造会受到反对。C 编译器仍然必须在 64 KI + 64 KD PDP-11 上运行,因此语言的最小化受到了重视。C/Unix 社区相对较小(600 个系统),并且刚刚开始适应即将到来的 32 位小型计算机。在 1977 年末,已知的最大的 Unix 安装有七个 PDP-11,总共有 3.3 MB 的内存和 1.9 GB 的磁盘空间。没有人能够猜到 C 语言及其分支会变得多么普遍,而考虑 64 位 CPU 并不是首要问题。

32 位快乐时光。在 1980 年代,ILP32 成为规范,至少在基于 Unix 的系统中是如此。这些是快乐的时光:32 位对于多年来可购买的 DRAM 来说足够舒适。然而,事后看来,这可能导致一些人在假设中变得草率:

   sizeof(int) == sizeof(long) == sizeof(ptr) == 32.

大约在 1984 年左右,Amdahl UTS 和 Convex 为 64 位整数添加了 long long,前者在 32 位架构上,后者在 64 位架构上。UTS 特别将此用于长文件指针,这与 PDP-11 Unix (1977) 中 long 的动机之一相同。Algol 68 在 1968 年启发了 long long,并且在某个时候也将其添加到 GNU C 中。许多人嘲笑这种语法,但至少它没有消耗更多保留关键字。

当然,16 位 int 用于 Microsoft DOS 和 Apple Macintosh 系统,因为最初使用了英特尔 8086 或 MC68000,其中 32 位 int 的成本很高,尤其是在具有 8 位或 16 位数据路径的早期系统上,并且低内存成本尤其重要。

表 1 常见的 C 数据类型
int long ptr long long 标签 示例
16 -- 16 -- IP16 PDP-11 Unix(早期,1973 年)
16 32 16 -- IP16L32 PDP-11 Unix(后期,1977 年);long 的多条指令
16 32 32 -- I16LP32 早期 MC68000 (1982);Apple Macintosh 68K
微软操作系统(加上 X86 段的额外内容)
32 32 32 -- ILP32 IBM 370;VAX Unix;许多工作站
32 32 32 64 ILP32LL 或
ILP32LL64
Amdahl;Convex;1990 年代 Unix 系统
像 IP16L32 一样,原因相同;long long 的多条指令。
32 32 64 64 LLP64 或 IL32LLP64 或 P64 微软 Win64
32 64 64 *(64) LP64 或 I32LP64 大多数 64/32 Unix 系统
64 64 64 *(64) ILP64 HAL;ILP32 的逻辑模拟
*在这些情况下,LP64 和 ILP64 提供 64 位整数,并且 long long 似乎是多余的,但实际上,LP64 和 ILP64 的大多数支持者也包括 long long,原因稍后给出。ILP64 唯一需要一种新的 32 位类型,通常称为 _int32。

64 位在 1991/1992 年升温。MIPS R4000 和 DEC Alpha 在 1990 年代初期发布。在 1990-92 年期间,各公司之间就 64 位 C 语言的正确模型进行了广泛的电子邮件讨论,尤其是在仍然运行 32 位应用程序多年的系统上实现时。通常,在这种情况下,在为其他方面激烈的竞争对手工作的工程师之间存在非正式合作。

在 1992 年年中,Sun 公司的 Steve Chessin 发起了一个非正式的 64 位 C 语言工作组,旨在看看供应商是否可以避免实施随机不同的 64 位 C 语言数据模型和命名法。系统和操作系统供应商都担心客户和第三方软件供应商的愤怒。DEC 选择了 LP64,并且已经进展顺利,因为 Alpha 没有 32 位版本。SGI 正在交付 64 位硬件,并致力于 64 位编译器和操作系统;它也更喜欢 LP64。许多其他公司正在计划 64 位 CPU 或操作系统,并进行可移植性研究。

Chessin 的工作组没有正式地位,但拥有来自许多系统和操作系统供应商的备受尊敬的资深技术专家,其中包括几位 C 标准委员会的成员。凭借所有这些智力资源,人们可能希望会出现一个明确的答案,但事实并非如此。三个提案(LLP64、LP64 和 ILP64)中的每一个都破坏了不同类型的代码,这取决于在 32 位快乐时代所做的特定隐式假设。

该小组中备受尊敬的成员进行了可信的演示,引用了对代码和移植经验的大量分析,每个人都得出结论:“XXX 是答案。” 不幸的是,在每种情况下,XXX 都不相同,并且该小组仍然分裂成三派。那时,我建议我们或许可以就帮助程序员生存的头文件达成一致(从而产生了 <inttypes.h>)。大多数人确实同意 long long 是最不糟糕的替代表示法,并且已经有一些先前的用法。

我们担心我们领先于即将到来的 C 标准多年,但又不能等待它,C 标准委员会成员表示支持。如果我们对任何合理的事情达成一致,并且它成为普遍实践,那么至少会受到应有的考虑。不管喜欢与否,在那个时候,这个非官方小组可能使 long long 变得不可避免——或者也许这种不可避免性可以追溯到 1984 年。

到 1994 年,DEC 正在交付大型系统,并已为许多第三方软件移植付费,使用的是 LP64。SGI 也在交付大型系统,这些系统同时支持 ILP32LL 和 LP64,其中 long long 扮演着 1970 年代后期 long 所扮演的角色。

DEC 的努力证明,在不使其 32 位不兼容的情况下,使许多软件 64 位兼容是可行的。SGI 的努力证明,32 位和 64 位程序可以合理地在同一系统上得到支持,并具有合理的数据交换,这是大多数其他供应商的要求。实际上,这意味着应该避免在用于交换数据的结构中使用 long,这与 PDP-11/VAX 时代避免使用 int 非常相似。大约在 1995 年的这个时候,大型文件峰会同意使用 Unix API 将文件大小增加到 2 GB 以上,使用 long long 作为基本数据类型。

最后,1995 年的 Aspen Group 再次就 Unix 的 64 位 C 模型进行了讨论,并最终确定为 LP64,至少部分原因是它已被证明可以工作,并且大多数实际的 64 位软件都使用了 LP64。

在 1992 年的 64 位 C 工作组会议期间,微软尚未选择其模型,但后来选择了 LLP64,而不是 Unix 供应商首选的 LP64。我被告知,发生这种情况是因为 PC 中唯一的 32 位整数类型是 long;因此,人们经常使用 long 来比 Unix 代码更明确地表示 32 位。这意味着更改其大小往往会比在 Unix 中破坏更多的代码。这似乎是合理的。每个选择都会破坏一些代码;因此,人们查看了他们自己的代码库,这些代码库有所不同,从而导致了合理的意见分歧。

许多人鄙视 long long,并在新闻组中充斥着反对它的论点,即使在 1999 年它被纳入下一个 C 标准之后也是如此。任何想要了解详细血腥细节的人都可以查阅 C 标准的官方理由。

多年来,关于各种数据类型大小的不同隐式假设已经发展起来,并造成了极大的混乱。如果我们能够回到 1977 年,了解我们现在所知道的,我们本可以通过坚持更一致地使用 typedef 来使这一切变得更容易。然而,在 1977 年,很难提前 20 年想到 64 位微处理器——我们当时才刚刚开始接触 32 位小型机!

如果更多供应商在 1992 年在 64 位实现方面走得更远,这个过程也可能会更容易。许多人要么忘记了 PDP-11/VAX 时代的教训,要么当时没有参与其中。无论如何,64/32 系统有一个更严格的要求:64 位和 32 位程序将在同一系统中永远共存。

经验教训

结论

一些决策会持续很长时间。1964 年 S/360 的 24 位寻址仍然与我们同在,1970 年代中期 C 语言用法的一些副作用也是如此。向 64 位过渡可能比所需的时间要长,原因有很多。人们经常无法使用价格合理的内存来解决性能问题或避免繁琐的编程,这真是太糟糕了。

有这么多的辛劳和麻烦真是太糟糕了,但至少人们已经停止争论是否应该有 64 位微处理器了。

参考文献

  1. Mashey, J. 2004-05. 语言、级别、库、寿命。 2 (9): 32-38.
  2. Mashey, J. 1991. 64 位计算。BYTE (九月): 135-142. 完整文本可以通过搜索 Google Groups comp.arch: Mashey BYTE 1991 找到。
  3. Bell, C. G., Mudge, J. C. 1978. PDP-11 的演变。在计算机工程:DEC 计算机系统设计观点, ed. C. Gordon Bell, J. Craig Mudge, 和 John E. McNamara. Bedford, MA: Digital Press.
  4. Strecker, W. D. 1978. VAX-11/780:DEC PDP-11 系列的虚拟地址扩展。在 计算机工程:DEC 计算机系统设计观点, ed. C. Gordon Bell, J. Craig Mudge, 和 John E. McNamara. Bedford, MA: Digital Press.
  5. 参见参考文献 2。
  6. 国际标准——编程语言——C 的理由; http://www.open-std.org/jtc1/sc22/wg14/www/docs/n897.pdf (或其他网站)。
  7. Aspen 数据模型委员会。1997-1998. 64 位编程模型:为什么是 LP64? http://www.unix.org/version2/whatsnew/lp64_wp.html.
  8. Josey, A. 1997. 数据大小中立性和 64 位支持; http://www.usenix.org/publications/login/standards/10.data.html.
  9. 为单一 Unix 规范添加对任意文件大小的支持; http://www.unix.org/version2/whatsnew/lfs20mar.html.

JOHN MASHEY 是风险投资家和技术公司的顾问,在多家初创公司的技术顾问委员会任职,并且是计算机历史博物馆的受托人。他在贝尔实验室工作了 10 年,致力于 Unix 的程序员工作台版本。后来,他管理了基于 Unix 的应用程序和软件开发系统的开发。在贝尔实验室之后,他是 MIPS RISC 架构的设计者之一,SPEC 基准测试组的创始人之一,以及 Silicon Graphics 的首席科学家。他的兴趣长期以来包括硬件和软件之间的交互。他获得了宾夕法尼亚州立大学的数学学士学位和计算机科学的硕士和博士学位。

年表:多个相互交织的线索

1964 IBM S/360:32 位,具有 24 位寻址(总共 16 MB)的真实(核心)内存。

1968 Algol 68:包括 long long。

1970 DEC PDP-11/20:16 位,16 位寻址(总共 64 KB)。IBM S/370 系列:虚拟内存,24 位地址,但允许多个用户地址空间。

1971 IBM 370/145:主内存不再是磁芯,而是 DRAM,1 Kbit/芯片。

1973 DEC PDP-11/45:独立的指令+数据 (64 KI + 64 KD);最大 248 KB 真实内存。Unix:PDP-11/45,操作系统用 C 语言重写;IP16。C 语言:整数数据类型:int、char;其他机器上的 C 语言(36 位 Honeywell 6000、IBM 370 等)。

1975 Unix:第六版,最大文件大小 24 位 (16 MB)。

1976 DEC PDP-11/70:(64 KI + 64 KD),但更大的物理内存(巨大的 4 MB)。C 语言:添加了 short、long(部分原因是为 XDS Sigma 做 C 语言,尽管 long 在那里是 64 位)。

1977 Unix:移植到 32 位 Interdata 8/32。C 语言:unsigned、typedef、union;32 位 long 用于替换 16 位 PDP-11 上的 lseek、tell 中的 int[2];IP16L32。DEC VAX-11/780:32 位,32 位寻址(总共 4 GB,每个用户进程 2 GB)。C 语言:PDP-11:I16LP32;VAX(其他 32 位):ILP32。

1978 Unix:VAX-11/780 的 32V;C 语言是 ILP32。C 语言:《C 编程语言》,Brian Kernighan 和 Dennis Ritchie (Prentice-Hall)。Intel 8086:16 位,但具有用户可见的分段。

1979 Motorola MC68000:32 位 ISA,但 24 位寻址(例如,S/360)。

1982 C 语言:贝尔实验室 Blit 终端中的 MC68000 上的 I16LP32。Intel 80286:允许 16 MB 的真实内存,但限制使大多数系统保持在 1 MB。

1983 IBM 370/XA:为用户程序添加了 31 位模式;仍然支持 24 位模式。C 语言:Unix 工作站通常使用 ILP32,遵循 VAX 系统上的 Unix。

1984 Motorola MC68020:32 位;32 位寻址。C 语言:Amdahl UTS(32 位 S/370)使用 long long,特别是对于大型文件指针。C 语言:Convex(64 位向量小型超级计算机)使用 long long 表示 64 位整数。

1986 Intel:80386,32 位,支持 8086 模式。

1987 Apple Mac II:MC68020 的 32 位寻址给一些 MC68000 软件带来了麻烦。

1988 IBM ESA/370:每个用户的多个 31 位地址空间,尽管很复杂;24 位仍然存在。

1989 ANSI C (“C89”):努力始于 1983 年,ANSI X3J11。

1992 SGI:交付首款 64 位微处理器(MIPS R4000);仍在运行 32 位操作系统。64 位 C 语言工作组:讨论各种模型(LP64、ILP64、LLP64),但几乎没有达成共识。DEC:交付 64 位 Alpha 系统,运行 64 位操作系统;LP64。

1994 SGI:在 Power Challenge 上交付 IRIX 6(64/32 操作系统;ILP32LL + LP64);客户购买了 4 GB+ 内存,并使用了它。DEC:在 DEC 7000 SMP 中交付 4 GB+(可能稍早)。

1995 Sun UltraSPARC:64/32 位硬件,仅 32 位操作系统。HAL Computer 的 SPARC64:对 C 语言使用 ILP64 模型。
大型文件峰会:编纂了 >2 GB 文件的 64 位接口,即使在 32 位系统 (ILP32LL+LP64) 中也是如此。Aspen 小组:支持 C 语言的 LP64 模型,以便 Unix 供应商保持一致。

1996 HP:宣布 PA-RISC 2.0,64 位。

1997 HP:UP/UX 11.0 是 64/32 位操作系统;ILP32LL + LP64。IBM:RS64 PowerPC,AIX 4.3;ILP32LL + LP64。

1998 Sun:发布 64/32 Solaris 7;ILP32LL + LP64。

1999 C 语言:ISO/IEC C (WG14 的 “C99”);包括 long long,至少 64 位。

2001 IBM:64 位 zSeries (S/360 后代);仍然支持 24 位寻址。Intel:64 位 Itanium。

2002 Microsoft:用于 Itanium 的 Windows 64 位。

2003 AMD:64 位 X86(现在称为 AMD64)。

2004 Intel:64 位 X86(称为 EMT64),与 AMD 兼容。

2005 Microsoft:用于 X86 的 Windows XP Professional x64;LLP64 (或 IL32LLP64)。

acmqueue

最初发表于 Queue vol. 4, no. 8
数字图书馆 中评论本文





更多相关文章

Brendan Burns, Brian Grant, David Oppenheimer, Eric Brewer, John Wilkes - Borg、Omega 和 Kubernetes
虽然对软件容器的广泛兴趣是一个相对较新的现象,但在 Google,我们管理大规模 Linux 容器已经超过十年,并且在那段时间内构建了三个不同的容器管理系统。每个系统都深受其前身的影响,即使它们是出于不同的原因而开发的。本文描述了我们从开发和运营它们中学到的教训。


Rishiyur S. Nikhil - 硬件系统设计中的抽象
软件工程的历史是一个不断发展抽象机制的历史,旨在解决日益增长的复杂性。然而,硬件设计并没有那么先进。例如,两种最常用的 HDL 可以追溯到 1980 年代。标准的更新落后于现代编程语言在结构抽象方面,例如类型、封装和参数化。它们的行为语义甚至更加落后。它们是根据在单处理器冯·诺依曼机器上运行的事件驱动模拟器指定的。





© 保留所有权利。

© . All rights reserved.