
想象一下:现在是 2038 年 1 月 19 日,UTC 时间 03:14:07。在数据中心的某个地方,一台 Unix 系统悄悄地将内部时钟计数器调转了一次。但它并没有前进到 03:14:08,而是发生了一件奇怪的事情。系统突然认为现在是 1901 年 12 月 13 日。混乱随之而来。
欢迎来到 2038 年问题。它还有许多其他有趣的名字——Unix 千年虫、Epochalypse 或 Y2K38。这是另一个需要大量人工干预才能解决的基本计算限制的例子。
总体而言,关键系统的千年虫问题得到了提前处理。南特中央理工学院的这块告示牌就是一个有趣的千年虫问题案例,拍摄于2000年1月3日。图片来源: Bug de l’an 2000 ,CC BY-SA 3.0
千年虫问题其实很简单。许多计算系统将年份存储为两位数,通常是为了在内存和存储空间(或穿孔卡片上的空间)极其受限的时代最大限度地减少系统所需的空间。这通常限制了系统理解从1900年到1999年的日期;而当将2000年存储为两位数时,它实际上会显示为1900年。这必然会在各个方面造成混乱,尤其是在处理2000年及以后交易的金融系统中。
这个问题最早是由鲍勃·贝默(Bob Bemer)于1958年发现的,当时他正在用家谱软件处理更长的时间尺度数据。在20世纪80年代和90年代,随着关键日期的临近,以及诸如长期投资债券之类的资产开始逼近2000年,人们的意识逐渐增强。人们投入了巨大的精力来彻底检修和更新重要的计算机系统,使其能够以一种不会在1999年之后循环回溯到1900年的方式存储日期。
与主要涉及日期存储和显示方式的千年虫问题不同,2038 年问题根源于类 Unix 系统记录时间的基本方式。自 20 世纪 70 年代初以来,Unix 系统一直以自 1970 年 1 月 1 日 UTC 时间 00:00:00 以来经过的秒数来测量时间。这一时刻被称为“Unix 纪元”。在当时,以这种方式记录时间似乎是一种非常合理的方法。它为系统提供了一种简单、标准化的方式来处理时间戳和计划任务。
问题在于,这个时间戳传统上是以有符号的 32 位整数形式存储的。得益于二进制的魔力,一个有符号的 32 位整数可以表示从 -2,147,483,648 到 2,147,483,647 之间的值。如果以秒为单位来计算,那么它大约可以表示纪元前后 68 年的时间。算一下,你会发现 1970 年 1 月 1 日之后的 2,147,483,647 秒对应的是 2038 年 1 月 19 日 03:14:07 UTC。这是从 Unix 纪元开始,可以用 32 位有符号整数表示的最终时间。
接下来发生的事情就不太妙了。当计数器再次尝试递增时,它溢出了。在二进制补码运算中,第一位是有符号的。因此,时间戳从 2,147,483,647 滚动到 -2,147,483,648。这相当于 1901 年 12 月 13 日。到 2038 年 1 月,这大约是 136 年前。
对于使用有符号 32 位整数跟踪 Unix 时间的未打补丁的系统,直接后果可能非常严重。软件在尝试计算突然跨越一个多世纪的错误时间差时可能会出现故障,并且由于在无效日期执行操作,日志和数据库条目可能会迅速损坏。数据库可能会拒绝“历史”条目,文件系统可能会混淆哪些文件比其他文件更新,并且计划任务可能会停止运行或在不合适的时间运行。
这不仅仅是一个抽象的未来问题。如果你在20世纪长大,这听起来可能很遥远——但2038年距离现在只有13年了。事实上,2038年的漏洞如今已经引发了问题。任何试图处理2038年以后日期的软件——例如计算30年期抵押贷款的金融系统——现在都可能因为这个漏洞而失败。
2012年,NetBSD 6.0 在 32 位和 64 位架构上引入了 64 位 Unix 时间。此外,它还提供了一个二进制兼容层,用于运行较旧的应用程序,尽管这些应用程序在内部仍会受到 2038 年问题的影响。来源:NetBSD 更新日志
显而易见的解决方案是将时间戳从 32 位改为 64 位。64 位有符号整数可以表示遥远未来的时间戳——实际上大约是 2920 亿年,这应该可以覆盖到宇宙热寂之后的很长一段时间。在我们找到突破这一基本物理极限的方案之前,我们应该能够应对。
事实上,大多数现代基于 Unix 的操作系统已经完成了这一转变。Linux 多年前就在 64 位平台上迁移到了 64 位 time_t 值,并且自 2020 年 5.6 版本以来,即使在 32 位硬件上也支持 64 位时间戳。OpenBSD 自 2014 年 5 月起就开始使用 64 位时间戳,而 NetBSD 甚至早在 2012 年就完成了这一切换。
目前,大多数其他现代 Unix 文件系统、C 编译器和数据库系统都已切换到 64 位时间。话虽如此,有些人还是使用了更“黑客”的解决方案,这些方案更多的是拖延时间,而不是在可预见的时间内解决问题。例如,ext4 文件系统使用一个复杂的时间戳系统,涉及纳秒,在 2446 年后就会用完。XFS 的表现稍好一些,但只能到 2486。与此同时,微软 Windows 使用其自己的 64 位系统,以 100 纳秒为间隔跟踪自 1601 年 1 月 1 日起的时间。这最早会在 30828 年溢出。
然而,挑战不仅仅在于操作系统。这个问题也影响到软件和嵌入式系统。如今基于现代架构构建的大多数系统在应对2038年问题时可能都安然无恙。然而,那些十多年前构建的、旨在几乎无限期运行的系统可能会成为问题。企业软件、网络设备或工业控制器,如果不提前更新,都可能在2038年超出Unix的日期限制。此外,还有一些隐蔽的依赖项和代码片段,如果不加以注意,即使是现代应用程序也可能会遭遇这个问题。
2022年,一位名叫Silent的程序员发现了一段代码,该代码片段会将2038年漏洞重新引入到新软件中。图片来源: Silent博客截图
真正的工程挑战在于如何在过渡期间保持兼容性。文件格式需要更新,数据库迁移过程中必须确保数据不被篡改。对于工业、金融和商业领域的系统而言,停机是极其令人厌恶的,因此这项工作极具挑战性。在极端情况下,解决这个问题可能需要将整个系统移植到新的操作系统架构上,而这需要巨大的开发和维护成本才能完成转换。
2038年的问题实际上是一个关于技术债务和设计决策长期后果的案例研究。在1970年,当2038年还像科幻小说一样时,Unix时代似乎完全合理。当时开发这些系统的人很少会想到,当时的选择会在60多年后产生持久的影响。这提醒我们,今天务实的工程选择可能会成为明天的技术挑战。
好消息是,大多数面向消费者的系统应该不会有问题。你的智能手机、笔记本电脑和台式电脑几乎肯定已经使用 64 位时间戳了。真正的工作在后台进行——企业系统管理员更新服务器基础设施,嵌入式系统工程师规划淘汰周期,软件开发人员审核代码中与时间相关的假设。我们其他人只需放松一下,静待 2038 年 1 月 19 日(理想情况下)过去,静待那没有烟火的盛宴。
原文: https://hackaday.com/2025/07/22/the-epochalypse-y2k-but-38-years-later/