サーバーサイドでは、見ることほとんど無くなった32bit Linux。
ただ、最近使うこと多くなっているRaspberry PiのOSである、Raspberry Pi OS(旧 Raspbian)は32bit Linuxです。
もし、Raspberry Piを配置して今後18年動き続ければ、2038年問題引っかかります。(可能性低そうですが)
ということで少し追っておきました。
【2022/02/03 追記】 リリースされました。 forest.watch.impress.co.jp
- 2038年問題
- 実際にRapberry Pi OS(32bit OS)で試してみる
- Raspberry Pi OS 64bitなら大丈夫
- さいごに
- (おまけ) settimeofday()は2232年までしか設定できない
2038年問題
2038年問題ですが、Wikipediaから引用です。 y2038
と書かれていたりもします。
協定世界時における1970年1月1日0時0分0秒(日本標準時では1970年1月1日9時0分0秒)から2,147,483,647秒を経過した、2038年1月19日3時14分7秒(日本標準時では2038年1月19日12時14分7秒、閏秒は考慮していない)を過ぎると、この値がオーバーフローし、負と扱われる[注釈 2]ため、もし時刻を正しく扱えていることを前提としたコードがあれば、誤動作する。
Wikipediaより引用
要は時間に使っている time_t
という32bit型が桁あふれします。
溢れるタイミングは、 2038 年 1 月 19 日 03:14:07
でUNIX時間で言えば 2,147,483,647秒
です。
この 2147483647
という数字、少しPython使いながら見てみます。 2進数で見るとわかりやすいですが、桁あふれ直前の数字となります。
>>> print(f"{2147483647:032b}") #32桁の2進数表示 01111111111111111111111111111111 >>> print(datetime.datetime.fromtimestamp(2147483647)) 2038-01-19 12:14:07
2147483647
に1を足して 2147483648
とすると、32bit目のMSB(most Significant Bit)と呼ばれるビットに1が立ちこれがマイナスを表すようになります。
1901 年 12 月 14 日 05:45:52
#-> 4バイトのlongに入れると最上位ビットが立って-2147483648を示すようになる >>> print(f"{2147483648:032b}") 10000000000000000000000000000000 # -2147483647なのでずっと昔の日付になってしまう >>> print(datetime.datetime.fromtimestamp(-2147483647)) 1901-12-14 05:45:52
実際にRapberry Pi OS(32bit OS)で試してみる
利用しているバージョンは以下です。
pi@raspberrypi:~ $ uname -a Linux raspberrypi 5.4.51-v7+ #1333 SMP Mon Aug 10 16:45:19 BST 2020 armv7l GNU/Linux
桁あふれの確認
以下のコマンドを実行してみます。 time_t
型に 2147483648
を代入してみています。
cat <<EOF > a.c && gcc a.c && ./a.out #include<stdio.h> #include <sys/time.h> int main(){ time_t t=2147483648; printf("%ld\n", t); } EOF
WSLのUbuntu上(当然64bit Linux)で実行すると 2147483648
が表示されます。
Raspberry Pi上で実行すると、以下の通り -2147483648
になってしまうことがわかります。
該当時間まで時間設定を進めてみる
該当の時間になると、実際にどういうことが起きるのか見てます。
まず、時間の同期をしているサービスを停止します。
sudo systemctl daemon-reload
sudo systemctl stop systemd-timesyncd
その上で、桁あふれ直前の時間に設定して挙動を見てみます。
# 時刻を10秒前に設定 pi@raspberrypi:~ $ sudo date -s "01/19 3:13:57 2038" Tue 19 Jan 03:13:57 UTC 2038 # まだ大丈夫 pi@raspberrypi:~ $ date +%s 2147483639 # まだ大丈夫 pi@raspberrypi:~ $ python3 -c "import datetime; print(datetime.datetime.now());" 2038-01-19 03:14:01.298023 # おかしくなった! pi@raspberrypi:~ $ date Fri 13 Dec 20:46:10 UTC 1901 pi@raspberrypi:~ $ date +%s -2147483585 # time_tがOverflowしているエラーが出たりする pi@raspberrypi:~ $ python3 -c "import datetime; print(datetime.datetime.now());" Traceback (most recent call last): File "<string>", line 1, in <module> OverflowError: timestamp out of range for platform time_t
ということで、当たり前ではあるのですが現バージョンのRaspberry PiのOSも2038年問題にかかることがわかりました。
Raspberry Pi OS 64bitなら大丈夫
数ヶ月前に出た以下の記事の通り、Raspberry Pi OSの64bit版が出ました。と言ってもまだBeta版です。
Raspberry Pi OS (64 bit) beta test version - Raspberry Pi Forums
こちらからダウンロードできます。
MicroSDに書き込んで起動します。今使っているのはこのバージョンです。
pi@raspberrypi:~ $ uname -a Linux raspberrypi 5.4.51-v8+ #1333 SMP PREEMPT Mon Aug 10 16:58:35 BST 2020 aarch64 GNU/Linux
これを使って問題が解消されるか見てみます。
まずは time_t
の桁あふれ無くなっているか確認。大丈夫そう。
問題の時間を超えても正常に動作していますので、大丈夫そう。
# 時刻を10秒前に設定 pi@raspberrypi:~ $ sudo date -s "01/19 3:13:57 2038" Tue 19 Jan 03:13:57 UTC 2038 # 問題の時間超えたけど大丈夫 pi@raspberrypi:~ $ date +%s 2147483677 pi@raspberrypi:~ $ python3 -c "import datetime; print(datetime.datetime.now());" 2038-01-19 03:14:50.028553
さいごに
しかし、冷静になると2038年となると18年後ですね。流石に32bitのRaspberry Piは動いて無さそうだなと思いながらも確認してみたい欲求から動かして確認してしまいました。
また、32bitのまま対応する対応もされているようなので、今のRaspberry Pi OSのバージョンでは動かないにしろ32bit Linuxが存続し続けるのかもしれません。
何にしろ、2038年となると18年後なので、私も50代半ば。その時に何やっているかは全くわからないですが、少し将来を考えるきっかけになりました。
(おまけ) settimeofday()は2232年までしか設定できない
64bitのRaspberry Piでずっと先の9999年の未来を設定してみたら、以下のようなエラーが出ました。
pi@raspberrypi:~ $ sudo date -s "12/31 23:59:59 9999" date: cannot set date: Invalid argument Fri 31 Dec 23:59:59 UTC 9999
どうも 2232 年 4 月 18 日 23:47:15
に境界がありそう。
pi@raspberrypi:~ $ sudo date -s "04/18 23:47:15 2232" Wed 18 Apr 23:47:15 UTC 2232 pi@raspberrypi:~ $ sudo date -s "04/18 23:47:16 2232" date: cannot set date: Invalid argument Wed 18 Apr 23:47:16 UTC 2232
この 8277292036
という数字が境界になってそう。
pi@raspberrypi:~ $ sudo strace date -s "04/18 23:47:16 2232" # 省略 clock_settime(CLOCK_REALTIME, {tv_sec=8277292036, tv_nsec=0}) = -1 EINVAL (Invalid argument) settimeofday({tv_sec=8277292036, tv_usec=0}, NULL) = -1 EINVAL (Invalid argument) # 省略
やっぱりここが境界になって settimeofday()
がエラーになるようです。
cat <<EOF > a.c && gcc a.c && sudo strace ./a.out #include <stdio.h> #include <sys/time.h> int main(void) { struct timeval t0, t1; t0.tv_sec = 8277292035; t0.tv_usec = 0; t1.tv_sec = 8277292036; t1.tv_usec = 0; settimeofday(&t0, NULL); settimeofday(&t1, NULL); } EOF # strace結果--抜粋 settimeofday({tv_sec=8277292035, tv_usec=0}, NULL) = 0 settimeofday({tv_sec=8277292036, tv_usec=0}, NULL) = -1 EINVAL (Invalid argument) # strace結果--抜粋
ソース見たらこんな記載がありました。
/* * Limits for settimeofday(): * * To prevent setting the time close to the wraparound point time setting * is limited so a reasonable uptime can be accomodated. Uptime of 30 years * should be really sufficient, which means the cutoff is 2232. At that * point the cutoff is just a small part of the larger problem. */
time64.h « linux « include - kernel/git/stable/linux.git - Linux kernel stable tree
2232年なんて流石に生きてないはずですし、当然32bitのLinuxが動いてない、またはこのソースで動いているシステムも無い・・と思うので。ただの豆知識です。