LoginSignup
16
19

More than 5 years have passed since last update.

Linuxのrelatimeマウントオプションの詳細

Last updated at Posted at 2017-01-22

結論から

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つの状態になる。

lazytimenodiratimeは、上記とは独立して立てることができる。ただもちろんnoatimeの時はnodiratimeは実質意味をなさない。

lazytimeについてはこのへん(Introducing lazytime)でも。

コードを読む

参考、、
- relatimeがどこで実装されているのか調べてみた : 革命の日々 その2
- sys_mount()を読んでみる。 - φ(・・*)ゞ ウーン カーネルとか弄ったりのメモ

util-linuxを読む

mountのオプションとして渡される文字列はmount(8)コマンド(util-linuxの一部)が解釈し、MNT_NOATIMEなどのオプションに変換して、mount(2)システムコールを呼ぶ。util-linuxのoptmap.cより

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()でオプションの解釈をしている。

namespace.c
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()周辺で使われる。

inode.c
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_NOATIMEMS_NODIRATIMErelatime_need_update()の結果かで、update_time()が呼ばれるかどうかが決まることになる。

inode
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()より、

inode.c
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より

ext4.h
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より、

posix_types.h
 13 #ifndef __kernel_long_t
 14 typedef long            __kernel_long_t;
 15 typedef unsigned long   __kernel_ulong_t;
 16 #endif
posix_types.h
88 typedef __kernel_long_t __kernel_time_t;

全部コンパイルし直せるならtime_tを64bitにしてしまうのが手っ取り早いが、ABIだバイナリ互換だ言い出すとそうはいかない。time_tを変更するくらいなら、struct timevaltimespec_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まとめ

16
19
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
19