| .. SPDX-License-Identifier: GPL-2.0 | |
| ===================== | |
| Multigrain Timestamps | |
| ===================== | |
| Introduction | |
| ============ | |
| Historically, the kernel has always used coarse time values to stamp inodes. | |
| This value is updated every jiffy, so any change that happens within that jiffy | |
| will end up with the same timestamp. | |
| When the kernel goes to stamp an inode (due to a read or write), it first gets | |
| the current time and then compares it to the existing timestamp(s) to see | |
| whether anything will change. If nothing changed, then it can avoid updating | |
| the inode's metadata. | |
| Coarse timestamps are therefore good from a performance standpoint, since they | |
| reduce the need for metadata updates, but bad from the standpoint of | |
| determining whether anything has changed, since a lot of things can happen in a | |
| jiffy. | |
| They are particularly troublesome with NFSv3, where unchanging timestamps can | |
| make it difficult to tell whether to invalidate caches. NFSv4 provides a | |
| dedicated change attribute that should always show a visible change, but not | |
| all filesystems implement this properly, causing the NFS server to substitute | |
| the ctime in many cases. | |
| Multigrain timestamps aim to remedy this by selectively using fine-grained | |
| timestamps when a file has had its timestamps queried recently, and the current | |
| coarse-grained time does not cause a change. | |
| Inode Timestamps | |
| ================ | |
| There are currently 3 timestamps in the inode that are updated to the current | |
| wallclock time on different activity: | |
| ctime: | |
| The inode change time. This is stamped with the current time whenever | |
| the inode's metadata is changed. Note that this value is not settable | |
| from userland. | |
| mtime: | |
| The inode modification time. This is stamped with the current time | |
| any time a file's contents change. | |
| atime: | |
| The inode access time. This is stamped whenever an inode's contents are | |
| read. Widely considered to be a terrible mistake. Usually avoided with | |
| options like noatime or relatime. | |
| Updating the mtime always implies a change to the ctime, but updating the | |
| atime due to a read request does not. | |
| Multigrain timestamps are only tracked for the ctime and the mtime. atimes are | |
| not affected and always use the coarse-grained value (subject to the floor). | |
| Inode Timestamp Ordering | |
| ======================== | |
| In addition to just providing info about changes to individual files, file | |
| timestamps also serve an important purpose in applications like "make". These | |
| programs measure timestamps in order to determine whether source files might be | |
| newer than cached objects. | |
| Userland applications like make can only determine ordering based on | |
| operational boundaries. For a syscall those are the syscall entry and exit | |
| points. For io_uring or nfsd operations, that's the request submission and | |
| response. In the case of concurrent operations, userland can make no | |
| determination about the order in which things will occur. | |
| For instance, if a single thread modifies one file, and then another file in | |
| sequence, the second file must show an equal or later mtime than the first. The | |
| same is true if two threads are issuing similar operations that do not overlap | |
| in time. | |
| If however, two threads have racing syscalls that overlap in time, then there | |
| is no such guarantee, and the second file may appear to have been modified | |
| before, after or at the same time as the first, regardless of which one was | |
| submitted first. | |
| Note that the above assumes that the system doesn't experience a backward jump | |
| of the realtime clock. If that occurs at an inopportune time, then timestamps | |
| can appear to go backward, even on a properly functioning system. | |
| Multigrain Timestamp Implementation | |
| =================================== | |
| Multigrain timestamps are aimed at ensuring that changes to a single file are | |
| always recognizable, without violating the ordering guarantees when multiple | |
| different files are modified. This affects the mtime and the ctime, but the | |
| atime will always use coarse-grained timestamps. | |
| It uses an unused bit in the i_ctime_nsec field to indicate whether the mtime | |
| or ctime has been queried. If either or both have, then the kernel takes | |
| special care to ensure the next timestamp update will display a visible change. | |
| This ensures tight cache coherency for use-cases like NFS, without sacrificing | |
| the benefits of reduced metadata updates when files aren't being watched. | |
| The Ctime Floor Value | |
| ===================== | |
| It's not sufficient to simply use fine or coarse-grained timestamps based on | |
| whether the mtime or ctime has been queried. A file could get a fine grained | |
| timestamp, and then a second file modified later could get a coarse-grained one | |
| that appears earlier than the first, which would break the kernel's timestamp | |
| ordering guarantees. | |
| To mitigate this problem, maintain a global floor value that ensures that | |
| this can't happen. The two files in the above example may appear to have been | |
| modified at the same time in such a case, but they will never show the reverse | |
| order. To avoid problems with realtime clock jumps, the floor is managed as a | |
| monotonic ktime_t, and the values are converted to realtime clock values as | |
| needed. | |
| Implementation Notes | |
| ==================== | |
| Multigrain timestamps are intended for use by local filesystems that get | |
| ctime values from the local clock. This is in contrast to network filesystems | |
| and the like that just mirror timestamp values from a server. | |
| For most filesystems, it's sufficient to just set the FS_MGTIME flag in the | |
| fstype->fs_flags in order to opt-in, providing the ctime is only ever set via | |
| inode_set_ctime_current(). If the filesystem has a ->getattr routine that | |
| doesn't call generic_fillattr, then it should call fill_mg_cmtime() to | |
| fill those values. For setattr, it should use setattr_copy() to update the | |
| timestamps, or otherwise mimic its behavior. |