聊聊Linux2038年问题
从Unix创世纪说起
ש
ית,意为“在开始之时”。每种⽂化都有它的创世纪⼀说,⽐如《创世纪》便是《圣纪》的第⼀卷,讲
א
ר
创世纪⼀词来⾃于希伯来语:ב
述了神创造,撒但败坏,⼈堕落,耶和华应许拯救的思想和故事。
对于Unix或类Unix系统,它关⼼时间从哪⾥开始,这便是它的创世纪。关于Unix和C语⾔创⽴背后的故事,我们在这⾥不重点介绍。
Unix操作系统的创世纪可以从(或)中到:
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年,届时操作系统和上层软件都会运⾏错出。
下图同样来⾃于,它展⽰32系统time_t溢出前后系统时间的倒流:
如果时间将近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下所有⽀持⽂件系统的时间类型和溢出时间作了分析和对⽐,为了不影响阅读,只取出重要的⽂件系统:
file system time type expiration year
btrfs signed 64-bit seconds, 32-bit ns never
cramfs fixed1970
ext2signed 32-bit seconds2038
linux下的sleep函数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 ns never
gfs2u64 seconds/u32 ns never
jffs2unsigned 32-bit seconds2106
logfs signed 64-bit ns2262
nfsv2,v3unsigned 32-bit seconds/ns2106
nfsv4u64 seconds/u32 ns never
nfsd unsigned 32-bit seconds/ns2106
ntfs64-bit 100ns since 160130828
pstore ascii seconds2106
squashfs unsigned 32-bit seconds2106
sysv unsigned 32-bit seconds2106
xfs signed 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社区在讨论时谈到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专项门有个来跟踪此问题。整个项⽬有分成以下⼏部分:
1. ⽤户态glibc对64位新型time_t以及相应函数的⽀持
2. 内核态对64位新型time_t以及系统调⽤实现的⽀持
3. ⽂件系统解决2038年问题
上⾯3个部分之中,第2点是重为重要的,也是整个解决⽅案的基⽯所在。当前 Arnd Bergmann 正着⼿解决提出第2部分,可以从看到他的解决⽅案。⾄⽂件系统部分,⼀直都有⼈有解决,但解决时间还有待进⼀步了解。
glibc部分⽀持必须要等内核的解决⽅案经过评审完成并合⼊到内核主线之后,glibc社区才能根据最终敲定的ABI来实现64位time_t和相关函数。
总结
2038年问题与之前的千年⾍问题的杀伤⼒是不⼀样的,千年⾍属于应⽤程序的问题,⽽2038年问题却是系统级的,有更⼤的杀伤⼒。幸好当前离2038还有20年时间,并且整个Linux社区已经开始解决的,离⽬标不远了,曙光在望。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论