結論から
Linux-3.18くらいをベースに、一部だけLinux-4.6くらいを見ています。
よくわからない場合
オプションは何も足すな。何も削るな。
mtimeを見るアプリを動かしたくて...
strictatimeを足す
(atimeでは手元ではダメだった)
時刻とか互換性とかいいから最速を目指すぜ
noatime lazytime くらい?
(lazytimeはLinux-4.0から)
はじめに
Linux-2.6.30から登場してデフォルトになったrelatimeは、RedHatのマニュアルにもあるくらい日本語のサイトでも結構情報が出回っているので、今更概要とかは書かなくてもよいかと思う。
noatime vs relatime
このへん(atime関連マウントオプション使用時のディスク性能比較)にテスト結果などあるとおり、パフォーマンス観点ではデフォルトのrelatimeをnoatimeに変更しても特に変わらない。英語サイトでも根拠なくnoatimeの方がいいと言ってるページが多いけど、ベンチマーク結果などの根拠は見つからなかった。
ただ、relatimeは1日に1回atimeを書くので、時刻が不安定なシステム(特に組み込み)や、書きこみ回数の観点が大事な小容量のeMMC/SSDといったところでは、noatimeの方が無難だろう。
mount optionの確認
mount(8)コマンドを引数なしで実行、もしくは/proc/mountsを読めば、今のmountのオプションがわかる。下記コードで確認する限り、必ず MNT_NOATIME, MNT_RELATIME, なにもなし(atime) のいずれか1つの状態になる。
lazytimeやnodiratimeは、上記とは独立して立てることができる。ただもちろんnoatimeの時はnodiratimeは実質意味をなさない。
lazytimeについてはこのへん(Introducing lazytime)でも。
コードを読む
参考、、
- relatimeがどこで実装されているのか調べてみた : 革命の日々 その2
- sys_mount()を読んでみる。 - φ(・・*)ゞ ウーン カーネルとか弄ったりのメモ
util-linuxを読む
mountのオプションとして渡される文字列はmount(8)コマンド(util-linuxの一部)が解釈し、MNT_NOATIMEなどのオプションに変換して、mount(2)システムコールを呼ぶ。util-linuxのoptmap.cより
static const struct libmnt_optmap linux_flags_map[] =
67{
68 { "ro", MS_RDONLY }, /* read-only */
69 { "rw", MS_RDONLY, MNT_INVERT }, /* read-write */
70 { "exec", MS_NOEXEC, MNT_INVERT }, /* permit execution of binaries */
71 { "noexec", MS_NOEXEC }, /* don't execute binaries */
72 { "suid", MS_NOSUID, MNT_INVERT }, /* honor suid executables */
73 { "nosuid", MS_NOSUID }, /* don't honor suid executables */
74 { "dev", MS_NODEV, MNT_INVERT }, /* interpret device files */
75 { "nodev", MS_NODEV }, /* don't interpret devices */
76
77 { "sync", MS_SYNCHRONOUS }, /* synchronous I/O */
78 { "async", MS_SYNCHRONOUS, MNT_INVERT },/* asynchronous I/O */
79
80 { "dirsync", MS_DIRSYNC }, /* synchronous directory modifications */
81 { "remount", MS_REMOUNT, MNT_NOMTAB }, /* alter flags of mounted FS */
82 { "bind", MS_BIND }, /* Remount part of the tree elsewhere */
83 { "rbind", MS_BIND | MS_REC }, /* Idem, plus mounted subtrees */
84#ifdef MS_NOSUB
85 { "sub", MS_NOSUB, MNT_INVERT }, /* allow submounts */
86 { "nosub", MS_NOSUB }, /* don't allow submounts */
87#endif
88#ifdef MS_SILENT
89 { "silent", MS_SILENT }, /* be quiet */
90 { "loud", MS_SILENT, MNT_INVERT }, /* print out messages. */
91#endif
92#ifdef MS_MANDLOCK
93 { "mand", MS_MANDLOCK }, /* Allow mandatory locks on this FS */
94 { "nomand", MS_MANDLOCK, MNT_INVERT }, /* Forbid mandatory locks on this FS */
95#endif
96#ifdef MS_NOATIME
97 { "atime", MS_NOATIME, MNT_INVERT }, /* Update access time */
98 { "noatime", MS_NOATIME }, /* Do not update access time */
99#endif
100#ifdef MS_I_VERSION
101 { "iversion", MS_I_VERSION }, /* Update inode I_version time */
102 { "noiversion", MS_I_VERSION, MNT_INVERT},/* Don't update inode I_version time */
103#endif
104#ifdef MS_NODIRATIME
105 { "diratime", MS_NODIRATIME, MNT_INVERT }, /* Update dir access times */
106 { "nodiratime", MS_NODIRATIME }, /* Do not update dir access times */
107#endif
108#ifdef MS_RELATIME
109 { "relatime", MS_RELATIME }, /* Update access times relative to mtime/ctime */
110 { "norelatime", MS_RELATIME, MNT_INVERT }, /* Update access time without regard to mtime/ctime */
111#endif
112#ifdef MS_STRICTATIME
113 { "strictatime", MS_STRICTATIME }, /* Strict atime semantics */
114 { "nostrictatime", MS_STRICTATIME, MNT_INVERT }, /* kernel default atime */
115#endif
116#ifdef MS_LAZYTIME
117 { "lazytime", MS_LAZYTIME }, /* Update {a,m,c}time on the in-memory inode only */
118 { "nolazytime", MS_LAZYTIME, MNT_INVERT },
119#endif
120#ifdef MS_PROPAGATION
121 { "unbindable", MS_UNBINDABLE, MNT_NOHLPS | MNT_NOMTAB }, /* Unbindable */
122 { "runbindable", MS_UNBINDABLE | MS_REC, MNT_NOHLPS | MNT_NOMTAB },
123 { "private", MS_PRIVATE, MNT_NOHLPS | MNT_NOMTAB }, /* Private */
124 { "rprivate", MS_PRIVATE | MS_REC, MNT_NOHLPS | MNT_NOMTAB },
125 { "slave", MS_SLAVE, MNT_NOHLPS | MNT_NOMTAB }, /* Slave */
126 { "rslave", MS_SLAVE | MS_REC, MNT_NOHLPS | MNT_NOMTAB },
127 { "shared", MS_SHARED, MNT_NOHLPS | MNT_NOMTAB }, /* Shared */
128 { "rshared", MS_SHARED | MS_REC, MNT_NOHLPS | MNT_NOMTAB },
129#endif
130 { NULL, 0, 0 }
131};
という感じになっている。ただ、どの文字列が実際にどう効くのかの動作詳細までは確認できなかった。straceやmountしたあとの/proc/mountsの値でしか確認できなかった。
lazytimeの対応はutil-linux-2.26.2かららしいので、使っているutil-linuxが古いとlazytimeは無視されるかもしれない。
kernelを読む
kernel/fs/namespace.c:do_mount()でオプションの解釈をしている。
2592 /* Default to relatime unless overriden */
2593 if (!(flags & MS_NOATIME))
2594 mnt_flags |= MNT_RELATIME;
2595
2596 /* Separate the per-mountpoint flags */
2597 if (flags & MS_NOSUID)
2598 mnt_flags |= MNT_NOSUID;
2599 if (flags & MS_NODEV)
2600 mnt_flags |= MNT_NODEV;
2601 if (flags & MS_NOEXEC)
2602 mnt_flags |= MNT_NOEXEC;
2603 if (flags & MS_NOATIME)
2604 mnt_flags |= MNT_NOATIME;
2605 if (flags & MS_NODIRATIME)
2606 mnt_flags |= MNT_NODIRATIME;
2607 if (flags & MS_STRICTATIME)
2608 mnt_flags &= ~(MNT_RELATIME | MNT_NOATIME);
2609 if (flags & MS_RDONLY)
2610 mnt_flags |= MNT_READONLY;
コードより、MNT_RELATIME, MNT_NOATIME, MS_STRICTATIME はいずれか1つしか立たないことがわかる。
立ったフラグはおおむねkernek/fs/inode.cのtouch_atime()周辺で使われる。
1517 /**
1518 * touch_atime - update the access time
1519 * @path: the &struct path to update
1520 *
1521 * Update the accessed time on an inode and mark it for writeback.
1522 * This function automatically handles read only file systems and media,
1523 * as well as the "noatime" flag and inode specific "noatime" markers.
1524 */
1525 void touch_atime(const struct path *path)
1526 {
1527 struct vfsmount *mnt = path->mnt;
1528 struct inode *inode = path->dentry->d_inode;
1529 struct timespec now;
1530
1531 if (inode->i_flags & S_NOATIME)
1532 return;
1533 if (IS_NOATIME(inode))
1534 return;
1535 if ((inode->i_sb->s_flags & MS_NODIRATIME) && S_ISDIR(inode->i_mode))
1536 return;
1537
1538 if (mnt->mnt_flags & MNT_NOATIME)
1539 return;
1540 if ((mnt->mnt_flags & MNT_NODIRATIME) && S_ISDIR(inode->i_mode))
1541 return;
1542
1543 now = current_fs_time(inode->i_sb);
1544
1545 if (!relatime_need_update(mnt, inode, now))
1546 return;
1547
1548 if (timespec_equal(&inode->i_atime, &now))
1549 return;
1550
1551 if (!sb_start_write_trylock(inode->i_sb))
1552 return;
1553
1554 if (__mnt_want_write(mnt))
1555 goto skip_update;
1556 /*
1557 * File systems can error out when updating inodes if they need to
1558 * allocate new space to modify an inode (such is the case for
1559 * Btrfs), but since we touch atime while walking down the path we
1560 * really don't care if we failed to update the atime of the file,
1561 * so just ignore the return value.
1562 * We may also fail on filesystems that have the ability to make parts
1563 * of the fs read only, e.g. subvolumes in Btrfs.
1564 */
1565 update_time(inode, &now, S_ATIME);
1566 __mnt_drop_write(mnt);
1567 skip_update:
1568 sb_end_write(inode->i_sb);
1569 }
remountごしにオプションの異なるinodeがまざっているかもしれないため、チェックがわかりにくいコードとなっている。current_fs_time()はFilesystemごとに持っている最小時間単位(s_time_gran,単位ナノ秒)で測る。s_time_granは今時のツールでフォーマットしたext4ならナノ秒になる(ハズ)。timespec_equal()はナノ秒単位で比較するため、atimeがnowと一致するケースはなかなかないんじゃないかと思う。結果、おおむねS_NOATIMEかMS_NODIRATIMEかrelatime_need_update()の結果かで、update_time()が呼ばれるかどうかが決まることになる。
1462 /*
1463 * With relative atime, only update atime if the previous atime is
1464 * earlier than either the ctime or mtime or if at least a day has
1465 * passed since the last atime update.
1466 */
1467 static int relatime_need_update(struct vfsmount *mnt, struct inode *inode,
1468 struct timespec now)
1469 {
1470
1471 if (!(mnt->mnt_flags & MNT_RELATIME))
1472 return 1;
1473 /*
1474 * Is mtime younger than atime? If yes, update atime:
1475 */
1476 if (timespec_compare(&inode->i_mtime, &inode->i_atime) >= 0)
1477 return 1;
1478 /*
1479 * Is ctime younger than atime? If yes, update atime:
1480 */
1481 if (timespec_compare(&inode->i_ctime, &inode->i_atime) >= 0)
1482 return 1;
1483
1484 /*
1485 * Is the previous atime value older than a day? If yes,
1486 * update atime:
1487 */
1488 if ((long)(now.tv_sec - inode->i_atime.tv_sec) >= 24*60*60)
1489 return 1;
1490 /*
1491 * Good, we can skip the atime update:
1492 */
1493 return 0;
1494 }
- MNT_RELATIMEじゃなかったら1
- mtimeがatimeより最近なら1
- ctimeがatimeより最近なら1
- mtimeが現在より1日前なら1
- それ以外なら0
となる。touch_atime()の動きと合わせると、「1なら更新する、0なら更新しない」となる。
RCU-safeバグ対応
3.18より後にRCU-safeの対応(8fa9dd24667f2d6997ec21341019657342859d31)が入ってコードが最新では少し変わっているのでご注意を。Linux-4.6で確認した限りでは、意味的には変わっていなかった。
ついでにlazytimeも確認する
fs/inode.cのgeneric_update_time()より、
1569 int generic_update_time(struct inode *inode, struct timespec *time, int flags)
1570 {
1571 int iflags = I_DIRTY_TIME;
1572
1573 if (flags & S_ATIME)
1574 inode->i_atime = *time;
1575 if (flags & S_VERSION)
1576 inode_inc_iversion(inode);
1577 if (flags & S_CTIME)
1578 inode->i_ctime = *time;
1579 if (flags & S_MTIME)
1580 inode->i_mtime = *time;
1581
1582 if (!(inode->i_sb->s_flags & MS_LAZYTIME) || (flags & S_VERSION))
1583 iflags |= I_DIRTY_SYNC;
1584 __mark_inode_dirty(inode, iflags);
1585 return 0;
1586 }
I_DIRTY_SYNCは「Inode is dirty, but doesn't have to be written on fdatasync(). i_atime is the usual cause.」らしいので、「RAM上のinode時刻は更新するが、ディスクへのライトバック(遅延書き込み)を行わない」という意図と思われる、がI_DIRTY_SYNCは該当箇所が広範囲にわたるので裏付けしづらい。
ちなみに、Linux-4.6のgeneric_update_time()は、Linux-3.18のupdate_time()がリファクタリングされた結果分離して作られた関数。せっかくEXPORT_SYMBOL(generic_update_time)されてるのに同じinode.c内からしか呼ばれていない模様。ぐぬぬ...
Filesystem次第
MNT_HOGEのたぐいは、Filesystemが個別にフラグを立てたりチェックしたりしていることがある。過去からそうやっていたとか、VFSでやってくれているとは知らないとか、Filesystem固有の事情でそうやっているとか、理由は様々のように見える。マウントオプションを確認するときは、ターゲットのFilesystemもセットで考えた方がいいと思われる。
ディスクレイアウト
いきなりext4前提の話になって申し訳ないが・・・
ext4のinodeのディスクレイアウトには4種類の時間があり、またそれぞれは秒までのフィールドとナノ秒までのフィールドの2種類に分かれている。ext4.hのstruct ext4_inodeより
636 /*
637 * Structure of an inode on the disk
638 */
639 struct ext4_inode {
640 __le16 i_mode; /* File mode */
641 __le16 i_uid; /* Low 16 bits of Owner Uid */
642 __le32 i_size_lo; /* Size in bytes */
643 __le32 i_atime; /* Access time */
644 __le32 i_ctime; /* Inode Change time */
645 __le32 i_mtime; /* Modification time */
646 __le32 i_dtime; /* Deletion Time */
647 __le16 i_gid; /* Low 16 bits of Group Id */
648 __le16 i_links_count; /* Links count */
649 __le32 i_blocks_lo; /* Blocks count */
650 __le32 i_flags; /* File flags */
651 union {
652 struct {
653 __le32 l_i_version;
654 } linux1;
655 struct {
656 __u32 h_i_translator;
657 } hurd1;
658 struct {
659 __u32 m_i_reserved1;
660 } masix1;
661 } osd1; /* OS dependent 1 */
662 __le32 i_block[EXT4_N_BLOCKS];/* Pointers to blocks */
663 __le32 i_generation; /* File version (for NFS) */
664 __le32 i_file_acl_lo; /* File ACL */
665 __le32 i_size_high;
666 __le32 i_obso_faddr; /* Obsoleted fragment address */
667 union {
668 struct {
669 __le16 l_i_blocks_high; /* were l_i_reserved1 */
670 __le16 l_i_file_acl_high;
671 __le16 l_i_uid_high; /* these 2 fields */
672 __le16 l_i_gid_high; /* were reserved2[0] */
673 __le16 l_i_checksum_lo;/* crc32c(uuid+inum+inode) LE */
674 __le16 l_i_reserved;
675 } linux2;
676 struct {
677 __le16 h_i_reserved1; /* Obsoleted fragment number/size which are removed in ext4 */
678 __u16 h_i_mode_high;
679 __u16 h_i_uid_high;
680 __u16 h_i_gid_high;
681 __u32 h_i_author;
682 } hurd2;
683 struct {
684 __le16 h_i_reserved1; /* Obsoleted fragment number/size which are removed in ext4 */
685 __le16 m_i_file_acl_high;
686 __u32 m_i_reserved2[2];
687 } masix2;
688 } osd2; /* OS dependent 2 */
689 __le16 i_extra_isize;
690 __le16 i_checksum_hi; /* crc32c(uuid+inum+inode) BE */
691 __le32 i_ctime_extra; /* extra Change time (nsec << 2 | epoch) */
692 __le32 i_mtime_extra; /* extra Modification time(nsec << 2 | epoch) */
693 __le32 i_atime_extra; /* extra Access time (nsec << 2 | epoch) */
694 __le32 i_crtime; /* File Creation time */
695 __le32 i_crtime_extra; /* extra FileCreationtime (nsec << 2 | epoch) */
696 __le32 i_version_hi; /* high 32 bits for 64-bit version */
697 };
- i_atime
- i_ctime
- i_mtime
- i_dtime
- i_crtime
- i_ctime_extra
- i_mtime_extra
- i_atime_extra
- i_crtime_extra
atime, ctime, mtimeがPOSIX互換の御三家。_extraのフィールドがあるのは、ext3までの時と互換性を持たせるためと思われる。i_crtimeは「File Creation time」とコメントにあるが、作成時間が入るだけで何に使われているかいまいちわからなかった。e2fsprogs(mkfs.ext4とかfsck.ext4とか)でも使われていない模様。 i_dtimeは、(journalなしモードの時に)inodeが消されてから再利用されるまでのディレイを持たせるための時間判定(ialloc.cのrecently_deleted()あたり)、orphanになったinodeの消しタイミング、の2つに使われているように見える、いまいち理解し切れてないけど。
あとがき
VFS層の整理が進んでいるためか、時刻がらみのコードはそれほど複雑ではなく読みやすそうだった。(読みやすかったとは言っていない(書きやすいとも言っていない))。ただ、タイミング問題や電源断を絡めてバグのないコードを書く(もしくはデバッグする)のは大変で、本当につらいですよねぇ(誰に向かってしゃべってるんだろ) 本当はXFSやbtrfsと比較しながら読むのがいいんだろうけど、今はまだext4だけで精一杯です。
次回、「ext4のjournalはトランザクションなのか?データ破壊バグの汚名を着せられたext4の潔白が今明らかに!?」です。なお予定は未定です。
おまけ:時刻変更の仕方
取得はstat(2)でできる。
変更は、utimes(2), futimes(3), futimens(2),utimensat(2) あたりと思われるが、どれが今時かまで含めると正直よくわからん、すまん。starceによると、toucn(1)はutimensat(2)を使っていた。
おまけ:2038年問題
time_tがsignedの32bitだと03:14:07 19 January 2038 (UTC)でオーバフローしてしまう例のヤツですが、Linux本体は対応を終えているものの、longが32bitなシステムはとっとと入れ替えてねという方針のように読めた。include/uapi/asm-generic/posix_types.hより、
13 #ifndef __kernel_long_t
14 typedef long __kernel_long_t;
15 typedef unsigned long __kernel_ulong_t;
16 #endif
88 typedef __kernel_long_t __kernel_time_t;
全部コンパイルし直せるならtime_tを64bitにしてしまうのが手っ取り早いが、ABIだバイナリ互換だ言い出すとそうはいかない。time_tを変更するくらいなら、struct timevalやtimespec_tに統一した方が現実的でわかりやすい。とはいえ時刻長はソフトウェアの中だけにとどまらず、ディスクレイアウトやらプロトコルやらに広がるから、やっぱりやっかいだ。とか考えてると、アプリが勝手にintにキャストしてたりすることも多いので、何もせずに対応できるわけはなく、結局全部見直さざるを得ないんじゃないかと思う。個人的には、NTPプロトコルの2036年問題(06:28:15 7 February 2036)の方が関心が薄いようで怖い。
ext3は時刻がsigned 32bitだったため2038年問題を抱えるが、ext4では上記で紹介した_extraのフィールドにepoch bitsを定義しして用いることでこれを解消している、ってkernelのサイトのWikiに書いてあった。
ちなみに私は定年退職では逃げ切れない人のようです、うん。
参考:
- 古賀政純の「攻めのITのためのDocker塾」:第25回 32ビット環境に迫る「2038年問題」 時計がおかしくなると
- 本の虫: Linuxカーネルを2038年問題に対応させるには
- time_t と 2038年問題 - Togetterまとめ