聊聊linux2038年问题(代码片段)

海枫 海枫     2023-03-14     119

关键词:

从Unix创世纪说起

创世纪一词来自于希伯来语:בראשית‎,意为“在开始之时”。每种文化都有它的创世纪一说,比如《创世纪》便是《圣纪》的第一卷,讲述了神创造,撒但败坏,人堕落,耶和华应许拯救的思想和故事。

对于Unix或类Unix系统,它关心时间从哪里开始,这便是它的创世纪。关于Unix和C语言创立背后的故事,我们在这里不重点介绍。

Unix操作系统的创世纪可以从中文Wikipedia(或英文wikipedia)中找到:

UNIX,一种计算机操作系统,具有多任务、多用户的特征。于1969年,在美国AT&T公司的贝尔实验室开发出来,参与开发的人有肯·汤普逊、丹尼斯·里奇等。
目前它的商标权由国际开放标准组织所拥有,只有匹配单一UNIX规范的UNIX系统才能使用UNIX这个名称,否则只能称为类UNIX(UNIX-like)。

Wikipedia中的图片更直观说明了整个Unix的发展和繁衍

由于Unix是从1970年开始广泛应用于商业和学术界,1970年被定义为Unix或类Unix系统的元纪。所有系统都以1970年1月1日 0点0分0秒作为时间的基准点,用秒数来表示系统时间,也即当前系统时间是从基准时间(1970年1月1日 0点0分0秒)走过多少秒之后的时间。

用简单公式来表即: 系统时间 = 基准时间 + 秒数

那么这个秒数应该保存在多宽的类型中呢?当时那个年代,16位字宽已是很大了。认为32位已经是“足够大”了,因此在POSIX标准中,将表示秒数的类型定义为time_t,而它是32位有符号整数类型。

下面是秒数与绝对时间的对照表:

time_t类型的秒数值系统时间(绝对时间)
01970-01-01 00:00:00
11970-01-01 00:00:01
0x7ffffffff2038-01-19 03:14:07

在32位系统上,time_t能表示的最大值为0x7ffffffff,当time_t取最大值时表示系统时间为2038-01-19 03:14:07,但时间再往后走时,那time_t会溢出变成一个负值,此时系统时间会倒流回到1901年,届时操作系统和上层软件都会运行错出。

下图同样来自于Wikipedia,它展示32系统time_t溢出前后系统时间的倒流:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mpnyI1nw-1591166703136)( https://upload.wikimedia.org/wikipedia/commons/e/e9/Year_2038_problem.gif)]

如果时间将近2038年时,还存在32位机器在世界中运行,那将会受到2038年问题的影响。

2038年问题冲击波

当前世界时钟走到了2016年,离2038年还有21年有多,估计很多人会持乐观态度。也许到2038年,32位的机器早已不存在了,2038年问题自动消失。然而,世界没有这么美好。

操作系统运行时影响

对于服务器来说,早早就换到了64系统操作系统,2038年问题不复存在。而对于嵌入式设备来说,现在还有大量32位系统在全球各地运行,谁也无法保证这些系统在2038年之前就能光荣退役。

另外对于64位操作系统,上面还会运行着32位的应用程序,它的2038年问题一样对人们造成威胁,不可小视。

所以32位的time_t问题,必须要解决,无法自动消失

持久化数据

一般听到2038年问题,想的最多的是time_t类型问题。事实上,2038年问题的范围远不止于此。
前面谈到的操作系统time_t类型问题是系统运行时表示数据的溢出,但还有一些数据是静静在躺在某个磁盘上,当时间走到2038之后再把它它们翻读出来,一样会出现问题。

我们知道类Unix下文件都有几种时间属性,比如创建时间最后一次访问时间最后一次修改时间。如果该时间类型也是32位有符号数(也即time_t的等价类型),那在2038之后的某个早晨,试想一下你和朋友喝着咖啡,回忆起2038年以前的某次旅游,你兴高采烈说着之前见闻,并拿出手提电脑打开之前拍下的照片,这时扫兴的事情将会发生,文件打不出或者出错。

下表是Linux社区对Linux下所有支持文件系统的时间类型和溢出时间作了分析和对比,为了不影响阅读,只取出重要的文件系统:

file systemtime typeexpiration year
btrfssigned 64-bit seconds, 32-bit nsnever
cramfsfixed1970
ext2signed 32-bit seconds2038
ext3signed 32-bit seconds2038
ext4 (good old inodes)signed 32-bit seconds2038
ext4 (new inodes34 bit seconds / 30-bit ns (but broken)2038
fat7-bit years since 1980, 2s resolution2107
fuse64-bit second/32-bit nsnever
gfs2u64 seconds/u32 nsnever
jffs2unsigned 32-bit seconds2106
logfssigned 64-bit ns2262
nfsv2,v3unsigned 32-bit seconds/ns2106
nfsv4u64 seconds/u32 nsnever
nfsdunsigned 32-bit seconds/ns2106
ntfs64-bit 100ns since 160130828
pstoreascii seconds2106
squashfsunsigned 32-bit seconds2106
sysvunsigned 32-bit seconds2106
xfssigned 32-bit seconds/ns2106

从上述表格中看到依然少数几个文件系统受2038年问题的影响。文件系统的时间类型和单位是由文件系统自己定义的,可以与系统的time_t以及基准时间不相同。如果一个文系统选定义的单位为秒数,并且使用32位有符号整数表示时间,那么尽管该文件系统运行在64位的操作系统之下,依然会有2038年问题。

Linux最广泛使用的文件系统要数ext系统(ext2/ext3/ext4),上表显示它们也存在2038年问题题。但我在查看系统代码时,发现表示时间的类型为32位无符号整数,所以它们的溢出时间应该是2106年。

无论如何,2038年问题要想彻底解决,文件系统脱不了干系。

协议交互

除于系统运行的数据表示,以及持久化数据,还有一类是需要关心的,那就是机器与机器之间通信约定是否有2038年问题,如果有那将会造成灾难。

前一段时间对部分开源软件代码做time_t搜索,没有发现协议相关的代码使用time_t作为协议类型,在google上搜索2038年问题也没有找到跟协议相关的说明。

从目前分析来看,协议交互不涉及2038年问题。

涅槃重生还是曲线解国

2038年问题的根源就是使用了32位有符号整数来表示时间,看起来它的解决方案非常的简单,直接粗暴地将time_t从32位有符号整数 修改成 64位有符号整数

如果真的这样做,那对这个世界会产生什么影响呢? 在修复2038年问题那一天,估计全世界人已都在做同一件事情:

  1. 所有应用程序统统重新编写代码,至少得重新编译才能在新系统上运行
  2. 所有受影2038年影响的文件系统对应的分区,得统统格式化掉
  3. 在那天有的互联网服务都统统下线了,整个应用网络处于瘫痪状态
  4. 更离奇的是,你在银行的存款被清零了;对于那个贷款的家伙来说是个好事情,因为他们不用向银行还钱了

所以,解决方案不是这么简单的,无法创建一个新世界,直接抛弃旧世界。

Linux社区在讨论2038年问题时谈到OpenBSD的解决方案还真是这样干, 它直接将time_t类型从32位修改成64位。OpenBSD之所以能这么干,是因为整个系统是自包含的,内核和应用程序是一起编译的,不存在软件供应商发行二进制场景,所以没有二进制兼容性问题。

而Linux却无法这样做。一旦将time_t从32位修改成64位之后,在此之前发布的所有应用程序几乎不能在新系统上运行。对于文件系统来说,一旦存储格式上做修改(不是扩展),那磁盘上的老数据必须要统统格掉(格式化)才能在新文件系统上运行。

所以Linux的解决方案必须是要解决兼容性问题

对于ttime_t类型的改造,既要支持旧的应用程序可以在新系统上运行,也要支持新开发(或者新编译的)应用程 序能解决2038年问题,那意味着要保留老(32位time_t)的二进制应用程序接口(ABI),同时要新增一套64位time_t的ABI接口。

很明显Linux的解决方案如下图所示:

原来整个系统中原来32位time_t的所有系统调用都保留下来。新增一个time64_t类型,与之相关的系统调用都提供一套新的64位系统调用ABI。

老用户态程序由于在之前已经编译老了,在新系统运行时,它执行的系统调用依然还是原来老的32位系统调用。 而新编译的应用程序,尽管在源代码层面上看到还是time_t类型,但是在新系统上,它已变成64位了,编译出来的二进制程,它实际上调用的函数者是64位的系统调用。

其实在Linux时间相关的函数中,不单单只有time_t一个类型,还有struct timeval等等。所有这些类型,都需要提供64位的定义。

时间相关的系统调用其实也一大堆,比如直接为时间操作函数(gettimeofday/settimeofday/adjtimex/clock_gettime/clock_settime/clock_adjtime/clock_nanosleep),也有文件操作相关的函数也带上时间属性(stat/lstat/fstatat),同样还有些大杂烩函数诸如ioctl很多命令字出现时间相关的参数,都需要实现64位的版本。

当前Linux社区进展

正如前面所说,当前离2038时间还有20+年,Linux社区最近几年才慢慢开始着手解决2038年问题。

linux kernel newbies专项门有个2038年项目来跟踪此问题。整个项目有分成以下几部分:

  1. 用户态glibc对64位新型time_t以及相应函数的支持
  2. 内核态对64位新型time_t以及系统调用实现的支持
  3. 文件系统解决2038年问题

上面3个部分之中,第2点是重为重要的,也是整个解决方案的基石所在。当前 Arnd Bergmann 正着手解决提出第2部分,可以从他的git看到他的解决方案。至文件系统部分,一直都有人有解决,但解决时间还有待进一步了解。

glibc部分支持必须要等内核的解决方案经过评审完成并合入到内核主线之后,glibc社区才能根据最终敲定的ABI来实现64位time_t和相关函数。

总结

2038年问题与之前的千年虫问题的杀伤力是不一样的,千年虫属于应用程序的问题,而2038年问题却是系统级的,有更大的杀伤力。幸好当前离2038还有20年时间,并且整个Linux社区已经开始解决的,离目标不远了,曙光在望。

手机日期只能设置到2038年?这背后有个大问题……

参考技术A世界末日到底是哪一天?这个无厘头的问题一直有着各种各样的离奇答案。当你打开手机,关闭自动设置时间,往未来的方向滑动数字时,你会发现时间停在了2038年。不仅是手机,在电脑上,当你尝试将时间从2037年再... 查看详情

PHP & mySQL:2038 年错误:它是啥?如何解决?

...:它是什么?如何解决?【发布时间】:2011-01-0200:36:52【问题描述】:我正在考虑使用TIMESTAMP来存储日期+时间,但我读到它有2038年的限制。我宁愿将问题分解成小部分,以便新手用户也容易理解,而不是大 查看详情

“千年虫问题”“2038年问题”什么是闰年

(1)先温习一下什么是闰年(LeapYear)闰年是公历中的名词。闰年分为普通闰年和世纪闰年。普通闰年:能被4整除但不能被100整除的年份为普通闰年。(如2004年就是闰年,1999年不是闰年);世纪闰年:能被400整除的为世纪闰年。(如200... 查看详情

64位MySQL的PHP​​ 2038年问题无法通过PHPMyAdmin插入日期

】64位MySQL的PHP​​2038年问题无法通过PHPMyAdmin插入日期【英文标题】:PHPyear2038problemwith64bitMySQLcan\'tinsertdatethroughPHPMyAdmin【发布时间】:2019-11-0423:33:03【问题描述】:我有一个MySQL表,它有一个名为“created_at”的列,它是一个TIME... 查看详情

32位unix时间戳哪一年耗尽

...凌晨03:14:07(北京时间:2038年1月19日中午11:14:07)。2038年问题可能会导致某些软件在2038年无法正常工作,所有使用POSIX时间表示时间的程序都将受其影响。因为它们的时间起点是格林尼治时间1970年1月1日0时0分0秒,依照此“time_t... 查看详情

2038,程序员史上最大危机!

...8年可能是程序员面临的一道坎,因为这关乎时间戳的问题。文章选自维基百科:2000年问题和2038年问题,感兴趣读者可以自行阅读英文版,信息量更大一些。2000年问题千年虫问题,是指由于计算机程序设计的... 查看详情

聊聊那些年遇到过的奇葩代码(代码片段)

📣📣📣📣📣📣📣🎍大家好,我是慕枫🎍前阿里巴巴高级工程师,InfoQ签约作者、阿里云专家博主,一直致力于用大白话讲解技术知识🎍在这里和大家分享一线互联网大厂面... 查看详情

洛谷p26552038年问题

P26552038年问题题目描述网络时代,机会与危机共存。“千年虫”解决之后,会不会有新的“虫”出现?回答是肯定的,“2038年”就是一个新的关卡。也许大家都已经知道计算机的2000年问题是什么概念,但是什... 查看详情

2038 年问题仍然发生在 Java 8 中日期反序列化的杰克逊身上 [重复]

】2038年问题仍然发生在Java8中日期反序列化的杰克逊身上[重复]【英文标题】:Year2038issuestillhappenswithJacksonofdatedeserializationinJava8[duplicate]【发布时间】:2017-12-0610:52:29【问题描述】:简单地说,我有以下类来获取从远程响应接收... 查看详情

leetcode刷题pythonleetcode2038.如果相邻两个颜色均相同则删除当前颜色(代码片段)

Kanaries雾角科技算法岗位笔试题之一笔试时间:2022年10月13号时长:120分钟中等难度1、LeetCode2038.如果相邻两个颜色均相同则删除当前颜色(1)题目总共有n个颜色片段排成一列,每个颜色片段要么是‘A’要么... 查看详情

2038年小行星真的会撞地球吗将会撞哪里

参考技术A会在美国不是科学家危言耸听2038年问题2038年问题演示在计算机应用上,2038年问题可能会导致某些软件在2038年无法正常工作。所有使用UNIX时间表示时间的程序都将受其影响,因为它们以自1970年1月1日经过的秒数(忽略... 查看详情

聊聊linux中线程和进程的联系与区别!(代码片段)

聊聊Linux中线程和进程的联系与区别!一、线程的创建方法二、内核中对线程的表示三、线程创建过程3.1回顾进程创建3.2线程的创建3.3进程线程创建异同四、揭秘do_fork系统调用4.1复制task_struct结构体4.2拷贝打开文件列表4.3拷... 查看详情

聊聊linux中线程和进程的联系与区别!(代码片段)

聊聊Linux中线程和进程的联系与区别!一、线程的创建方法二、内核中对线程的表示三、线程创建过程3.1回顾进程创建3.2线程的创建3.3进程线程创建异同四、揭秘do_fork系统调用4.1复制task_struct结构体4.2拷贝打开文件列表4.3拷... 查看详情

聊聊pulsar:在linux环境上搭建pulsar(代码片段)

一、环境准备jdk下载地址链接:jdk1.8,提取码:dv5hpulsar下载地址链接:pulsar2.9.1,提取码:84891.1Java环境为前提1.1.1上传jdk-8u261-linux-x64.rpm到服务器并安装#安装命令rpm-ivhjdk-8u261-linux-x64.rpm1.1.2配置环境变量#编辑配置文... 查看详情

聊聊pulsar:在linux环境上搭建pulsar(代码片段)

一、环境准备jdk下载地址链接:jdk1.8,提取码:dv5hpulsar下载地址链接:pulsar2.9.1,提取码:84891.1Java环境为前提1.1.1上传jdk-8u261-linux-x64.rpm到服务器并安装#安装命令rpm-ivhjdk-8u261-linux-x64.rpm1.1.2配置环境变量#编辑配置文... 查看详情

聊聊linux动态链接中的plt和got(2)——延迟重定位(代码片段)

在上文中(聊聊Linux动态链接中的PLT和GOT(1)—— 何谓PLT与GOT)介绍解决动态库函数调用使用GOT表技术,然后PLT从GOT中获取地址并完成调用。这个前提是GOT必须在PLT执行之前,所有函数都已完成运... 查看详情

聊聊kafka:在linux环境上搭建kafka(代码片段)

一、环境准备jdk下载地址链接:jdk1.8,提取码:dv5hzookeeper下载地址链接:zookeeper3.4.14,提取码:3dchkafka下载地址链接:kafka2.12,提取码:61bc1.1Java环境为前提1.1.1上传jdk-8u261-linux-x64.rpm到服务器并安装#安装命令r... 查看详情

聊聊kafka:在linux环境上搭建kafka(代码片段)

一、环境准备jdk下载地址链接:jdk1.8,提取码:dv5hzookeeper下载地址链接:zookeeper3.4.14,提取码:3dchkafka下载地址链接:kafka2.12,提取码:61bc1.1Java环境为前提1.1.1上传jdk-8u261-linux-x64.rpm到服务器并安装#安装命令r... 查看详情