Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

Merge patch series "timekeeping/fs: multigrain timestamp redux"

Jeff Layton <jlayton@kernel.org> says:

The VFS has always used coarse-grained timestamps when updating the
ctime and mtime after a change. This has the benefit of allowing
filesystems to optimize away a lot metadata updates, down to around 1
per jiffy, even when a file is under heavy writes.

Unfortunately, this has always been an issue when we're exporting via
NFSv3, which relies on timestamps to validate caches. A lot of changes
can happen in a jiffy, so timestamps aren't sufficient to help the
client decide when to invalidate the cache. Even with NFSv4, a lot of
exported filesystems don't properly support a change attribute and are
subject to the same problems with timestamp granularity. Other
applications have similar issues with timestamps (e.g backup
applications).

If we were to always use fine-grained timestamps, that would improve the
situation, but that becomes rather expensive, as the underlying
filesystem would have to log a lot more metadata updates.

What we need is a way to only use fine-grained timestamps when they are
being actively queried. Use the (unused) top bit in inode->i_ctime_nsec
as a flag that indicates whether the current timestamps have been
queried via stat() or the like. When it's set, we allow the kernel to
use a fine-grained timestamp iff it's necessary to make the ctime show
a different value.

This solves the problem of being able to distinguish the timestamp
between updates, but introduces a new problem: it's now possible for a
file being changed to get a fine-grained timestamp. A file that is
altered just a bit later can then get a coarse-grained one that appears
older than the earlier fine-grained time. This violates timestamp
ordering guarantees.

To remedy this, keep a global monotonic atomic64_t value that acts as a
timestamp floor. When we go to stamp a file, we first get the latter of
the current floor value and the current coarse-grained time. If the
inode ctime hasn't been queried then we just attempt to stamp it with
that value.

If it has been queried, then first see whether the current coarse time
is later than the existing ctime. If it is, then we accept that value.
If it isn't, then we get a fine-grained time and try to swap that into
the global floor. Whether that succeeds or fails, we take the resulting
floor time, convert it to realtime and try to swap that into the ctime.

We take the result of the ctime swap whether it succeeds or fails, since
either is just as valid.

Filesystems can opt into this by setting the FS_MGTIME fstype flag.
Others should be unaffected (other than being subject to the same floor
value as multigrain filesystems).

* patches from https://lore.kernel.org/r/20241002-mgtime-v10-0-d1c4717f5284@kernel.org:
tmpfs: add support for multigrain timestamps
btrfs: convert to multigrain timestamps
ext4: switch to multigrain timestamps
xfs: switch to multigrain timestamps
Documentation: add a new file documenting multigrain timestamps
fs: add percpu counters for significant multigrain timestamp events
fs: tracepoints around multigrain timestamp events
fs: handle delegated timestamps in setattr_copy_mgtime
fs: have setattr_copy handle multigrain timestamps appropriately
fs: add infrastructure for multigrain timestamps

Link: https://lore.kernel.org/r/20241002-mgtime-v10-0-d1c4717f5284@kernel.org
Signed-off-by: Christian Brauner <brauner@kernel.org>

+656 -77
+1
Documentation/filesystems/index.rst
··· 29 29 fiemap 30 30 files 31 31 locks 32 + multigrain-ts 32 33 mount_api 33 34 quota 34 35 seq_file
+125
Documentation/filesystems/multigrain-ts.rst
··· 1 + .. SPDX-License-Identifier: GPL-2.0 2 + 3 + ===================== 4 + Multigrain Timestamps 5 + ===================== 6 + 7 + Introduction 8 + ============ 9 + Historically, the kernel has always used coarse time values to stamp inodes. 10 + This value is updated every jiffy, so any change that happens within that jiffy 11 + will end up with the same timestamp. 12 + 13 + When the kernel goes to stamp an inode (due to a read or write), it first gets 14 + the current time and then compares it to the existing timestamp(s) to see 15 + whether anything will change. If nothing changed, then it can avoid updating 16 + the inode's metadata. 17 + 18 + Coarse timestamps are therefore good from a performance standpoint, since they 19 + reduce the need for metadata updates, but bad from the standpoint of 20 + determining whether anything has changed, since a lot of things can happen in a 21 + jiffy. 22 + 23 + They are particularly troublesome with NFSv3, where unchanging timestamps can 24 + make it difficult to tell whether to invalidate caches. NFSv4 provides a 25 + dedicated change attribute that should always show a visible change, but not 26 + all filesystems implement this properly, causing the NFS server to substitute 27 + the ctime in many cases. 28 + 29 + Multigrain timestamps aim to remedy this by selectively using fine-grained 30 + timestamps when a file has had its timestamps queried recently, and the current 31 + coarse-grained time does not cause a change. 32 + 33 + Inode Timestamps 34 + ================ 35 + There are currently 3 timestamps in the inode that are updated to the current 36 + wallclock time on different activity: 37 + 38 + ctime: 39 + The inode change time. This is stamped with the current time whenever 40 + the inode's metadata is changed. Note that this value is not settable 41 + from userland. 42 + 43 + mtime: 44 + The inode modification time. This is stamped with the current time 45 + any time a file's contents change. 46 + 47 + atime: 48 + The inode access time. This is stamped whenever an inode's contents are 49 + read. Widely considered to be a terrible mistake. Usually avoided with 50 + options like noatime or relatime. 51 + 52 + Updating the mtime always implies a change to the ctime, but updating the 53 + atime due to a read request does not. 54 + 55 + Multigrain timestamps are only tracked for the ctime and the mtime. atimes are 56 + not affected and always use the coarse-grained value (subject to the floor). 57 + 58 + Inode Timestamp Ordering 59 + ======================== 60 + 61 + In addition to just providing info about changes to individual files, file 62 + timestamps also serve an important purpose in applications like "make". These 63 + programs measure timestamps in order to determine whether source files might be 64 + newer than cached objects. 65 + 66 + Userland applications like make can only determine ordering based on 67 + operational boundaries. For a syscall those are the syscall entry and exit 68 + points. For io_uring or nfsd operations, that's the request submission and 69 + response. In the case of concurrent operations, userland can make no 70 + determination about the order in which things will occur. 71 + 72 + For instance, if a single thread modifies one file, and then another file in 73 + sequence, the second file must show an equal or later mtime than the first. The 74 + same is true if two threads are issuing similar operations that do not overlap 75 + in time. 76 + 77 + If however, two threads have racing syscalls that overlap in time, then there 78 + is no such guarantee, and the second file may appear to have been modified 79 + before, after or at the same time as the first, regardless of which one was 80 + submitted first. 81 + 82 + Note that the above assumes that the system doesn't experience a backward jump 83 + of the realtime clock. If that occurs at an inopportune time, then timestamps 84 + can appear to go backward, even on a properly functioning system. 85 + 86 + Multigrain Timestamp Implementation 87 + =================================== 88 + Multigrain timestamps are aimed at ensuring that changes to a single file are 89 + always recognizable, without violating the ordering guarantees when multiple 90 + different files are modified. This affects the mtime and the ctime, but the 91 + atime will always use coarse-grained timestamps. 92 + 93 + It uses an unused bit in the i_ctime_nsec field to indicate whether the mtime 94 + or ctime has been queried. If either or both have, then the kernel takes 95 + special care to ensure the next timestamp update will display a visible change. 96 + This ensures tight cache coherency for use-cases like NFS, without sacrificing 97 + the benefits of reduced metadata updates when files aren't being watched. 98 + 99 + The Ctime Floor Value 100 + ===================== 101 + It's not sufficient to simply use fine or coarse-grained timestamps based on 102 + whether the mtime or ctime has been queried. A file could get a fine grained 103 + timestamp, and then a second file modified later could get a coarse-grained one 104 + that appears earlier than the first, which would break the kernel's timestamp 105 + ordering guarantees. 106 + 107 + To mitigate this problem, maintain a global floor value that ensures that 108 + this can't happen. The two files in the above example may appear to have been 109 + modified at the same time in such a case, but they will never show the reverse 110 + order. To avoid problems with realtime clock jumps, the floor is managed as a 111 + monotonic ktime_t, and the values are converted to realtime clock values as 112 + needed. 113 + 114 + Implementation Notes 115 + ==================== 116 + Multigrain timestamps are intended for use by local filesystems that get 117 + ctime values from the local clock. This is in contrast to network filesystems 118 + and the like that just mirror timestamp values from a server. 119 + 120 + For most filesystems, it's sufficient to just set the FS_MGTIME flag in the 121 + fstype->fs_flags in order to opt-in, providing the ctime is only ever set via 122 + inode_set_ctime_current(). If the filesystem has a ->getattr routine that 123 + doesn't call generic_fillattr, then it should call fill_mg_cmtime() to 124 + fill those values. For setattr, it should use setattr_copy() to update the 125 + timestamps, or otherwise mimic its behavior.
+55 -6
fs/attr.c
··· 272 272 EXPORT_SYMBOL(inode_newsize_ok); 273 273 274 274 /** 275 + * setattr_copy_mgtime - update timestamps for mgtime inodes 276 + * @inode: inode timestamps to be updated 277 + * @attr: attrs for the update 278 + * 279 + * With multigrain timestamps, take more care to prevent races when 280 + * updating the ctime. Always update the ctime to the very latest using 281 + * the standard mechanism, and use that to populate the atime and mtime 282 + * appropriately (unless those are being set to specific values). 283 + */ 284 + static void setattr_copy_mgtime(struct inode *inode, const struct iattr *attr) 285 + { 286 + unsigned int ia_valid = attr->ia_valid; 287 + struct timespec64 now; 288 + 289 + if (ia_valid & ATTR_CTIME) { 290 + /* 291 + * In the case of an update for a write delegation, we must respect 292 + * the value in ia_ctime and not use the current time. 293 + */ 294 + if (ia_valid & ATTR_DELEG) 295 + now = inode_set_ctime_deleg(inode, attr->ia_ctime); 296 + else 297 + now = inode_set_ctime_current(inode); 298 + } else { 299 + /* If ATTR_CTIME isn't set, then ATTR_MTIME shouldn't be either. */ 300 + WARN_ON_ONCE(ia_valid & ATTR_MTIME); 301 + now = current_time(inode); 302 + } 303 + 304 + if (ia_valid & ATTR_ATIME_SET) 305 + inode_set_atime_to_ts(inode, attr->ia_atime); 306 + else if (ia_valid & ATTR_ATIME) 307 + inode_set_atime_to_ts(inode, now); 308 + 309 + if (ia_valid & ATTR_MTIME_SET) 310 + inode_set_mtime_to_ts(inode, attr->ia_mtime); 311 + else if (ia_valid & ATTR_MTIME) 312 + inode_set_mtime_to_ts(inode, now); 313 + } 314 + 315 + /** 275 316 * setattr_copy - copy simple metadata updates into the generic inode 276 317 * @idmap: idmap of the mount the inode was found from 277 318 * @inode: the inode to be updated ··· 344 303 345 304 i_uid_update(idmap, attr, inode); 346 305 i_gid_update(idmap, attr, inode); 347 - if (ia_valid & ATTR_ATIME) 348 - inode_set_atime_to_ts(inode, attr->ia_atime); 349 - if (ia_valid & ATTR_MTIME) 350 - inode_set_mtime_to_ts(inode, attr->ia_mtime); 351 - if (ia_valid & ATTR_CTIME) 352 - inode_set_ctime_to_ts(inode, attr->ia_ctime); 353 306 if (ia_valid & ATTR_MODE) { 354 307 umode_t mode = attr->ia_mode; 355 308 if (!in_group_or_capable(idmap, inode, 356 309 i_gid_into_vfsgid(idmap, inode))) 357 310 mode &= ~S_ISGID; 358 311 inode->i_mode = mode; 312 + } 313 + 314 + if (is_mgtime(inode)) 315 + return setattr_copy_mgtime(inode, attr); 316 + 317 + if (ia_valid & ATTR_ATIME) 318 + inode_set_atime_to_ts(inode, attr->ia_atime); 319 + if (ia_valid & ATTR_MTIME) 320 + inode_set_mtime_to_ts(inode, attr->ia_mtime); 321 + if (ia_valid & ATTR_CTIME) { 322 + if (ia_valid & ATTR_DELEG) 323 + inode_set_ctime_deleg(inode, attr->ia_ctime); 324 + else 325 + inode_set_ctime_to_ts(inode, attr->ia_ctime); 359 326 } 360 327 } 361 328 EXPORT_SYMBOL(setattr_copy);
+4 -21
fs/btrfs/file.c
··· 1120 1120 btrfs_drew_write_unlock(&inode->root->snapshot_lock); 1121 1121 } 1122 1122 1123 - static void update_time_for_write(struct inode *inode) 1124 - { 1125 - struct timespec64 now, ts; 1126 - 1127 - if (IS_NOCMTIME(inode)) 1128 - return; 1129 - 1130 - now = current_time(inode); 1131 - ts = inode_get_mtime(inode); 1132 - if (!timespec64_equal(&ts, &now)) 1133 - inode_set_mtime_to_ts(inode, now); 1134 - 1135 - ts = inode_get_ctime(inode); 1136 - if (!timespec64_equal(&ts, &now)) 1137 - inode_set_ctime_to_ts(inode, now); 1138 - 1139 - if (IS_I_VERSION(inode)) 1140 - inode_inc_iversion(inode); 1141 - } 1142 - 1143 1123 int btrfs_write_check(struct kiocb *iocb, struct iov_iter *from, size_t count) 1144 1124 { 1145 1125 struct file *file = iocb->ki_filp; ··· 1150 1170 * need to start yet another transaction to update the inode as we will 1151 1171 * update the inode when we finish writing whatever data we write. 1152 1172 */ 1153 - update_time_for_write(inode); 1173 + if (!IS_NOCMTIME(inode)) { 1174 + inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode)); 1175 + inode_inc_iversion(inode); 1176 + } 1154 1177 1155 1178 start_pos = round_down(pos, fs_info->sectorsize); 1156 1179 oldsize = i_size_read(inode);
+2 -1
fs/btrfs/super.c
··· 2198 2198 .init_fs_context = btrfs_init_fs_context, 2199 2199 .parameters = btrfs_fs_parameters, 2200 2200 .kill_sb = btrfs_kill_super, 2201 - .fs_flags = FS_REQUIRES_DEV | FS_BINARY_MOUNTDATA | FS_ALLOW_IDMAP, 2201 + .fs_flags = FS_REQUIRES_DEV | FS_BINARY_MOUNTDATA | 2202 + FS_ALLOW_IDMAP | FS_MGTIME, 2202 2203 }; 2203 2204 2204 2205 MODULE_ALIAS_FS("btrfs");
+1 -1
fs/ext4/super.c
··· 7329 7329 .init_fs_context = ext4_init_fs_context, 7330 7330 .parameters = ext4_param_specs, 7331 7331 .kill_sb = ext4_kill_sb, 7332 - .fs_flags = FS_REQUIRES_DEV | FS_ALLOW_IDMAP, 7332 + .fs_flags = FS_REQUIRES_DEV | FS_ALLOW_IDMAP | FS_MGTIME, 7333 7333 }; 7334 7334 MODULE_ALIAS_FS("ext4"); 7335 7335
+264 -26
fs/inode.c
··· 21 21 #include <linux/list_lru.h> 22 22 #include <linux/iversion.h> 23 23 #include <linux/rw_hint.h> 24 + #include <linux/seq_file.h> 25 + #include <linux/debugfs.h> 24 26 #include <trace/events/writeback.h> 27 + #define CREATE_TRACE_POINTS 28 + #include <trace/events/timestamp.h> 29 + 25 30 #include "internal.h" 26 31 27 32 /* ··· 102 97 long nr_dirty = get_nr_inodes() - get_nr_inodes_unused(); 103 98 return nr_dirty > 0 ? nr_dirty : 0; 104 99 } 100 + 101 + #ifdef CONFIG_DEBUG_FS 102 + static DEFINE_PER_CPU(long, mg_ctime_updates); 103 + static DEFINE_PER_CPU(long, mg_fine_stamps); 104 + static DEFINE_PER_CPU(long, mg_ctime_swaps); 105 + 106 + static unsigned long get_mg_ctime_updates(void) 107 + { 108 + unsigned long sum = 0; 109 + int i; 110 + 111 + for_each_possible_cpu(i) 112 + sum += data_race(per_cpu(mg_ctime_updates, i)); 113 + return sum; 114 + } 115 + 116 + static unsigned long get_mg_fine_stamps(void) 117 + { 118 + unsigned long sum = 0; 119 + int i; 120 + 121 + for_each_possible_cpu(i) 122 + sum += data_race(per_cpu(mg_fine_stamps, i)); 123 + return sum; 124 + } 125 + 126 + static unsigned long get_mg_ctime_swaps(void) 127 + { 128 + unsigned long sum = 0; 129 + int i; 130 + 131 + for_each_possible_cpu(i) 132 + sum += data_race(per_cpu(mg_ctime_swaps, i)); 133 + return sum; 134 + } 135 + 136 + #define mgtime_counter_inc(__var) this_cpu_inc(__var) 137 + 138 + static int mgts_show(struct seq_file *s, void *p) 139 + { 140 + unsigned long ctime_updates = get_mg_ctime_updates(); 141 + unsigned long ctime_swaps = get_mg_ctime_swaps(); 142 + unsigned long fine_stamps = get_mg_fine_stamps(); 143 + unsigned long floor_swaps = timekeeping_get_mg_floor_swaps(); 144 + 145 + seq_printf(s, "%lu %lu %lu %lu\n", 146 + ctime_updates, ctime_swaps, fine_stamps, floor_swaps); 147 + return 0; 148 + } 149 + 150 + DEFINE_SHOW_ATTRIBUTE(mgts); 151 + 152 + static int __init mg_debugfs_init(void) 153 + { 154 + debugfs_create_file("multigrain_timestamps", S_IFREG | S_IRUGO, NULL, NULL, &mgts_fops); 155 + return 0; 156 + } 157 + late_initcall(mg_debugfs_init); 158 + 159 + #else /* ! CONFIG_DEBUG_FS */ 160 + 161 + #define mgtime_counter_inc(__var) do { } while (0) 162 + 163 + #endif /* CONFIG_DEBUG_FS */ 105 164 106 165 /* 107 166 * Handle nr_inode sysctl ··· 2278 2209 } 2279 2210 EXPORT_SYMBOL(file_remove_privs); 2280 2211 2212 + /** 2213 + * current_time - Return FS time (possibly fine-grained) 2214 + * @inode: inode. 2215 + * 2216 + * Return the current time truncated to the time granularity supported by 2217 + * the fs, as suitable for a ctime/mtime change. If the ctime is flagged 2218 + * as having been QUERIED, get a fine-grained timestamp, but don't update 2219 + * the floor. 2220 + * 2221 + * For a multigrain inode, this is effectively an estimate of the timestamp 2222 + * that a file would receive. An actual update must go through 2223 + * inode_set_ctime_current(). 2224 + */ 2225 + struct timespec64 current_time(struct inode *inode) 2226 + { 2227 + struct timespec64 now; 2228 + u32 cns; 2229 + 2230 + ktime_get_coarse_real_ts64_mg(&now); 2231 + 2232 + if (!is_mgtime(inode)) 2233 + goto out; 2234 + 2235 + /* If nothing has queried it, then coarse time is fine */ 2236 + cns = smp_load_acquire(&inode->i_ctime_nsec); 2237 + if (cns & I_CTIME_QUERIED) { 2238 + /* 2239 + * If there is no apparent change, then get a fine-grained 2240 + * timestamp. 2241 + */ 2242 + if (now.tv_nsec == (cns & ~I_CTIME_QUERIED)) 2243 + ktime_get_real_ts64(&now); 2244 + } 2245 + out: 2246 + return timestamp_truncate(now, inode); 2247 + } 2248 + EXPORT_SYMBOL(current_time); 2249 + 2281 2250 static int inode_needs_update_time(struct inode *inode) 2282 2251 { 2252 + struct timespec64 now, ts; 2283 2253 int sync_it = 0; 2284 - struct timespec64 now = current_time(inode); 2285 - struct timespec64 ts; 2286 2254 2287 2255 /* First try to exhaust all avenues to not sync */ 2288 2256 if (IS_NOCMTIME(inode)) 2289 2257 return 0; 2290 2258 2259 + now = current_time(inode); 2260 + 2291 2261 ts = inode_get_mtime(inode); 2292 2262 if (!timespec64_equal(&ts, &now)) 2293 - sync_it = S_MTIME; 2263 + sync_it |= S_MTIME; 2294 2264 2295 2265 ts = inode_get_ctime(inode); 2296 2266 if (!timespec64_equal(&ts, &now)) ··· 2706 2598 } 2707 2599 EXPORT_SYMBOL(inode_nohighmem); 2708 2600 2601 + struct timespec64 inode_set_ctime_to_ts(struct inode *inode, struct timespec64 ts) 2602 + { 2603 + trace_inode_set_ctime_to_ts(inode, &ts); 2604 + set_normalized_timespec64(&ts, ts.tv_sec, ts.tv_nsec); 2605 + inode->i_ctime_sec = ts.tv_sec; 2606 + inode->i_ctime_nsec = ts.tv_nsec; 2607 + return ts; 2608 + } 2609 + EXPORT_SYMBOL(inode_set_ctime_to_ts); 2610 + 2709 2611 /** 2710 2612 * timestamp_truncate - Truncate timespec to a granularity 2711 2613 * @t: Timespec ··· 2748 2630 EXPORT_SYMBOL(timestamp_truncate); 2749 2631 2750 2632 /** 2751 - * current_time - Return FS time 2752 - * @inode: inode. 2753 - * 2754 - * Return the current time truncated to the time granularity supported by 2755 - * the fs. 2756 - * 2757 - * Note that inode and inode->sb cannot be NULL. 2758 - * Otherwise, the function warns and returns time without truncation. 2759 - */ 2760 - struct timespec64 current_time(struct inode *inode) 2761 - { 2762 - struct timespec64 now; 2763 - 2764 - ktime_get_coarse_real_ts64(&now); 2765 - return timestamp_truncate(now, inode); 2766 - } 2767 - EXPORT_SYMBOL(current_time); 2768 - 2769 - /** 2770 2633 * inode_set_ctime_current - set the ctime to current_time 2771 2634 * @inode: inode 2772 2635 * 2773 - * Set the inode->i_ctime to the current value for the inode. Returns 2774 - * the current value that was assigned to i_ctime. 2636 + * Set the inode's ctime to the current value for the inode. Returns the 2637 + * current value that was assigned. If this is not a multigrain inode, then we 2638 + * set it to the later of the coarse time and floor value. 2639 + * 2640 + * If it is multigrain, then we first see if the coarse-grained timestamp is 2641 + * distinct from what is already there. If so, then use that. Otherwise, get a 2642 + * fine-grained timestamp. 2643 + * 2644 + * After that, try to swap the new value into i_ctime_nsec. Accept the 2645 + * resulting ctime, regardless of the outcome of the swap. If it has 2646 + * already been replaced, then that timestamp is later than the earlier 2647 + * unacceptable one, and is thus acceptable. 2775 2648 */ 2776 2649 struct timespec64 inode_set_ctime_current(struct inode *inode) 2777 2650 { 2778 - struct timespec64 now = current_time(inode); 2651 + struct timespec64 now; 2652 + u32 cns, cur; 2779 2653 2780 - inode_set_ctime_to_ts(inode, now); 2654 + ktime_get_coarse_real_ts64_mg(&now); 2655 + now = timestamp_truncate(now, inode); 2656 + 2657 + /* Just return that if this is not a multigrain fs */ 2658 + if (!is_mgtime(inode)) { 2659 + inode_set_ctime_to_ts(inode, now); 2660 + goto out; 2661 + } 2662 + 2663 + /* 2664 + * A fine-grained time is only needed if someone has queried 2665 + * for timestamps, and the current coarse grained time isn't 2666 + * later than what's already there. 2667 + */ 2668 + cns = smp_load_acquire(&inode->i_ctime_nsec); 2669 + if (cns & I_CTIME_QUERIED) { 2670 + struct timespec64 ctime = { .tv_sec = inode->i_ctime_sec, 2671 + .tv_nsec = cns & ~I_CTIME_QUERIED }; 2672 + 2673 + if (timespec64_compare(&now, &ctime) <= 0) { 2674 + ktime_get_real_ts64_mg(&now); 2675 + now = timestamp_truncate(now, inode); 2676 + mgtime_counter_inc(mg_fine_stamps); 2677 + } 2678 + } 2679 + mgtime_counter_inc(mg_ctime_updates); 2680 + 2681 + /* No need to cmpxchg if it's exactly the same */ 2682 + if (cns == now.tv_nsec && inode->i_ctime_sec == now.tv_sec) { 2683 + trace_ctime_xchg_skip(inode, &now); 2684 + goto out; 2685 + } 2686 + cur = cns; 2687 + retry: 2688 + /* Try to swap the nsec value into place. */ 2689 + if (try_cmpxchg(&inode->i_ctime_nsec, &cur, now.tv_nsec)) { 2690 + /* If swap occurred, then we're (mostly) done */ 2691 + inode->i_ctime_sec = now.tv_sec; 2692 + trace_ctime_ns_xchg(inode, cns, now.tv_nsec, cur); 2693 + mgtime_counter_inc(mg_ctime_swaps); 2694 + } else { 2695 + /* 2696 + * Was the change due to someone marking the old ctime QUERIED? 2697 + * If so then retry the swap. This can only happen once since 2698 + * the only way to clear I_CTIME_QUERIED is to stamp the inode 2699 + * with a new ctime. 2700 + */ 2701 + if (!(cns & I_CTIME_QUERIED) && (cns | I_CTIME_QUERIED) == cur) { 2702 + cns = cur; 2703 + goto retry; 2704 + } 2705 + /* Otherwise, keep the existing ctime */ 2706 + now.tv_sec = inode->i_ctime_sec; 2707 + now.tv_nsec = cur & ~I_CTIME_QUERIED; 2708 + } 2709 + out: 2781 2710 return now; 2782 2711 } 2783 2712 EXPORT_SYMBOL(inode_set_ctime_current); 2713 + 2714 + /** 2715 + * inode_set_ctime_deleg - try to update the ctime on a delegated inode 2716 + * @inode: inode to update 2717 + * @update: timespec64 to set the ctime 2718 + * 2719 + * Attempt to atomically update the ctime on behalf of a delegation holder. 2720 + * 2721 + * The nfs server can call back the holder of a delegation to get updated 2722 + * inode attributes, including the mtime. When updating the mtime, update 2723 + * the ctime to a value at least equal to that. 2724 + * 2725 + * This can race with concurrent updates to the inode, in which 2726 + * case the update is skipped. 2727 + * 2728 + * Note that this works even when multigrain timestamps are not enabled, 2729 + * so it is used in either case. 2730 + */ 2731 + struct timespec64 inode_set_ctime_deleg(struct inode *inode, struct timespec64 update) 2732 + { 2733 + struct timespec64 now, cur_ts; 2734 + u32 cur, old; 2735 + 2736 + /* pairs with try_cmpxchg below */ 2737 + cur = smp_load_acquire(&inode->i_ctime_nsec); 2738 + cur_ts.tv_nsec = cur & ~I_CTIME_QUERIED; 2739 + cur_ts.tv_sec = inode->i_ctime_sec; 2740 + 2741 + /* If the update is older than the existing value, skip it. */ 2742 + if (timespec64_compare(&update, &cur_ts) <= 0) 2743 + return cur_ts; 2744 + 2745 + ktime_get_coarse_real_ts64_mg(&now); 2746 + 2747 + /* Clamp the update to "now" if it's in the future */ 2748 + if (timespec64_compare(&update, &now) > 0) 2749 + update = now; 2750 + 2751 + update = timestamp_truncate(update, inode); 2752 + 2753 + /* No need to update if the values are already the same */ 2754 + if (timespec64_equal(&update, &cur_ts)) 2755 + return cur_ts; 2756 + 2757 + /* 2758 + * Try to swap the nsec value into place. If it fails, that means 2759 + * it raced with an update due to a write or similar activity. That 2760 + * stamp takes precedence, so just skip the update. 2761 + */ 2762 + retry: 2763 + old = cur; 2764 + if (try_cmpxchg(&inode->i_ctime_nsec, &cur, update.tv_nsec)) { 2765 + inode->i_ctime_sec = update.tv_sec; 2766 + mgtime_counter_inc(mg_ctime_swaps); 2767 + return update; 2768 + } 2769 + 2770 + /* 2771 + * Was the change due to another task marking the old ctime QUERIED? 2772 + * 2773 + * If so, then retry the swap. This can only happen once since 2774 + * the only way to clear I_CTIME_QUERIED is to stamp the inode 2775 + * with a new ctime. 2776 + */ 2777 + if (!(old & I_CTIME_QUERIED) && (cur == (old | I_CTIME_QUERIED))) 2778 + goto retry; 2779 + 2780 + /* Otherwise, it was a new timestamp. */ 2781 + cur_ts.tv_sec = inode->i_ctime_sec; 2782 + cur_ts.tv_nsec = cur & ~I_CTIME_QUERIED; 2783 + return cur_ts; 2784 + } 2785 + EXPORT_SYMBOL(inode_set_ctime_deleg); 2784 2786 2785 2787 /** 2786 2788 * in_group_or_capable - check whether caller is CAP_FSETID privileged
+44 -2
fs/stat.c
··· 23 23 #include <linux/uaccess.h> 24 24 #include <asm/unistd.h> 25 25 26 + #include <trace/events/timestamp.h> 27 + 26 28 #include "internal.h" 27 29 #include "mount.h" 30 + 31 + /** 32 + * fill_mg_cmtime - Fill in the mtime and ctime and flag ctime as QUERIED 33 + * @stat: where to store the resulting values 34 + * @request_mask: STATX_* values requested 35 + * @inode: inode from which to grab the c/mtime 36 + * 37 + * Given @inode, grab the ctime and mtime out if it and store the result 38 + * in @stat. When fetching the value, flag it as QUERIED (if not already) 39 + * so the next write will record a distinct timestamp. 40 + * 41 + * NB: The QUERIED flag is tracked in the ctime, but we set it there even 42 + * if only the mtime was requested, as that ensures that the next mtime 43 + * change will be distinct. 44 + */ 45 + void fill_mg_cmtime(struct kstat *stat, u32 request_mask, struct inode *inode) 46 + { 47 + atomic_t *pcn = (atomic_t *)&inode->i_ctime_nsec; 48 + 49 + /* If neither time was requested, then don't report them */ 50 + if (!(request_mask & (STATX_CTIME|STATX_MTIME))) { 51 + stat->result_mask &= ~(STATX_CTIME|STATX_MTIME); 52 + return; 53 + } 54 + 55 + stat->mtime = inode_get_mtime(inode); 56 + stat->ctime.tv_sec = inode->i_ctime_sec; 57 + stat->ctime.tv_nsec = (u32)atomic_read(pcn); 58 + if (!(stat->ctime.tv_nsec & I_CTIME_QUERIED)) 59 + stat->ctime.tv_nsec = ((u32)atomic_fetch_or(I_CTIME_QUERIED, pcn)); 60 + stat->ctime.tv_nsec &= ~I_CTIME_QUERIED; 61 + trace_fill_mg_cmtime(inode, &stat->ctime, &stat->mtime); 62 + } 63 + EXPORT_SYMBOL(fill_mg_cmtime); 28 64 29 65 /** 30 66 * generic_fillattr - Fill in the basic attributes from the inode struct ··· 94 58 stat->rdev = inode->i_rdev; 95 59 stat->size = i_size_read(inode); 96 60 stat->atime = inode_get_atime(inode); 97 - stat->mtime = inode_get_mtime(inode); 98 - stat->ctime = inode_get_ctime(inode); 61 + 62 + if (is_mgtime(inode)) { 63 + fill_mg_cmtime(stat, request_mask, inode); 64 + } else { 65 + stat->ctime = inode_get_ctime(inode); 66 + stat->mtime = inode_get_mtime(inode); 67 + } 68 + 99 69 stat->blksize = i_blocksize(inode); 100 70 stat->blocks = inode->i_blocks; 101 71
+3 -3
fs/xfs/libxfs/xfs_trans_inode.c
··· 62 62 ASSERT(tp); 63 63 xfs_assert_ilocked(ip, XFS_ILOCK_EXCL); 64 64 65 - tv = current_time(inode); 65 + /* If the mtime changes, then ctime must also change */ 66 + ASSERT(flags & XFS_ICHGTIME_CHG); 66 67 68 + tv = inode_set_ctime_current(inode); 67 69 if (flags & XFS_ICHGTIME_MOD) 68 70 inode_set_mtime_to_ts(inode, tv); 69 - if (flags & XFS_ICHGTIME_CHG) 70 - inode_set_ctime_to_ts(inode, tv); 71 71 if (flags & XFS_ICHGTIME_ACCESS) 72 72 inode_set_atime_to_ts(inode, tv); 73 73 if (flags & XFS_ICHGTIME_CREATE)
+3 -7
fs/xfs/xfs_iops.c
··· 597 597 stat->gid = vfsgid_into_kgid(vfsgid); 598 598 stat->ino = ip->i_ino; 599 599 stat->atime = inode_get_atime(inode); 600 - stat->mtime = inode_get_mtime(inode); 601 - stat->ctime = inode_get_ctime(inode); 600 + 601 + fill_mg_cmtime(stat, request_mask, inode); 602 + 602 603 stat->blocks = XFS_FSB_TO_BB(mp, ip->i_nblocks + ip->i_delayed_blks); 603 604 604 605 if (xfs_has_v3inodes(mp)) { ··· 607 606 stat->result_mask |= STATX_BTIME; 608 607 stat->btime = ip->i_crtime; 609 608 } 610 - } 611 - 612 - if ((request_mask & STATX_CHANGE_COOKIE) && IS_I_VERSION(inode)) { 613 - stat->change_cookie = inode_query_iversion(inode); 614 - stat->result_mask |= STATX_CHANGE_COOKIE; 615 609 } 616 610 617 611 /*
+1 -1
fs/xfs/xfs_super.c
··· 2063 2063 .init_fs_context = xfs_init_fs_context, 2064 2064 .parameters = xfs_fs_parameters, 2065 2065 .kill_sb = xfs_kill_sb, 2066 - .fs_flags = FS_REQUIRES_DEV | FS_ALLOW_IDMAP, 2066 + .fs_flags = FS_REQUIRES_DEV | FS_ALLOW_IDMAP | FS_MGTIME, 2067 2067 }; 2068 2068 MODULE_ALIAS_FS("xfs"); 2069 2069
+28 -8
include/linux/fs.h
··· 1584 1584 1585 1585 struct timespec64 current_time(struct inode *inode); 1586 1586 struct timespec64 inode_set_ctime_current(struct inode *inode); 1587 + struct timespec64 inode_set_ctime_deleg(struct inode *inode, 1588 + struct timespec64 update); 1587 1589 1588 1590 static inline time64_t inode_get_atime_sec(const struct inode *inode) 1589 1591 { ··· 1655 1653 return inode_set_mtime_to_ts(inode, ts); 1656 1654 } 1657 1655 1656 + /* 1657 + * Multigrain timestamps 1658 + * 1659 + * Conditionally use fine-grained ctime and mtime timestamps when there 1660 + * are users actively observing them via getattr. The primary use-case 1661 + * for this is NFS clients that use the ctime to distinguish between 1662 + * different states of the file, and that are often fooled by multiple 1663 + * operations that occur in the same coarse-grained timer tick. 1664 + */ 1665 + #define I_CTIME_QUERIED ((u32)BIT(31)) 1666 + 1658 1667 static inline time64_t inode_get_ctime_sec(const struct inode *inode) 1659 1668 { 1660 1669 return inode->i_ctime_sec; ··· 1673 1660 1674 1661 static inline long inode_get_ctime_nsec(const struct inode *inode) 1675 1662 { 1676 - return inode->i_ctime_nsec; 1663 + return inode->i_ctime_nsec & ~I_CTIME_QUERIED; 1677 1664 } 1678 1665 1679 1666 static inline struct timespec64 inode_get_ctime(const struct inode *inode) ··· 1684 1671 return ts; 1685 1672 } 1686 1673 1687 - static inline struct timespec64 inode_set_ctime_to_ts(struct inode *inode, 1688 - struct timespec64 ts) 1689 - { 1690 - inode->i_ctime_sec = ts.tv_sec; 1691 - inode->i_ctime_nsec = ts.tv_nsec; 1692 - return ts; 1693 - } 1674 + struct timespec64 inode_set_ctime_to_ts(struct inode *inode, struct timespec64 ts); 1694 1675 1695 1676 /** 1696 1677 * inode_set_ctime - set the ctime in the inode ··· 2549 2542 #define FS_USERNS_MOUNT 8 /* Can be mounted by userns root */ 2550 2543 #define FS_DISALLOW_NOTIFY_PERM 16 /* Disable fanotify permission events */ 2551 2544 #define FS_ALLOW_IDMAP 32 /* FS has been updated to handle vfs idmappings. */ 2545 + #define FS_MGTIME 64 /* FS uses multigrain timestamps */ 2552 2546 #define FS_RENAME_DOES_D_MOVE 32768 /* FS will handle d_move() during rename() internally. */ 2553 2547 int (*init_fs_context)(struct fs_context *); 2554 2548 const struct fs_parameter_spec *parameters; ··· 2572 2564 }; 2573 2565 2574 2566 #define MODULE_ALIAS_FS(NAME) MODULE_ALIAS("fs-" NAME) 2567 + 2568 + /** 2569 + * is_mgtime: is this inode using multigrain timestamps 2570 + * @inode: inode to test for multigrain timestamps 2571 + * 2572 + * Return true if the inode uses multigrain timestamps, false otherwise. 2573 + */ 2574 + static inline bool is_mgtime(const struct inode *inode) 2575 + { 2576 + return inode->i_sb->s_type->fs_flags & FS_MGTIME; 2577 + } 2575 2578 2576 2579 extern struct dentry *mount_bdev(struct file_system_type *fs_type, 2577 2580 int flags, const char *dev_name, void *data, ··· 3340 3321 extern int page_symlink(struct inode *inode, const char *symname, int len); 3341 3322 extern const struct inode_operations page_symlink_inode_operations; 3342 3323 extern void kfree_link(void *); 3324 + void fill_mg_cmtime(struct kstat *stat, u32 request_mask, struct inode *inode); 3343 3325 void generic_fillattr(struct mnt_idmap *, u32, struct inode *, struct kstat *); 3344 3326 void generic_fill_statx_attr(struct inode *inode, struct kstat *stat); 3345 3327 void generic_fill_statx_atomic_writes(struct kstat *stat,
+124
include/trace/events/timestamp.h
··· 1 + /* SPDX-License-Identifier: GPL-2.0 */ 2 + #undef TRACE_SYSTEM 3 + #define TRACE_SYSTEM timestamp 4 + 5 + #if !defined(_TRACE_TIMESTAMP_H) || defined(TRACE_HEADER_MULTI_READ) 6 + #define _TRACE_TIMESTAMP_H 7 + 8 + #include <linux/tracepoint.h> 9 + #include <linux/fs.h> 10 + 11 + #define CTIME_QUERIED_FLAGS \ 12 + { I_CTIME_QUERIED, "Q" } 13 + 14 + DECLARE_EVENT_CLASS(ctime, 15 + TP_PROTO(struct inode *inode, 16 + struct timespec64 *ctime), 17 + 18 + TP_ARGS(inode, ctime), 19 + 20 + TP_STRUCT__entry( 21 + __field(dev_t, dev) 22 + __field(ino_t, ino) 23 + __field(time64_t, ctime_s) 24 + __field(u32, ctime_ns) 25 + __field(u32, gen) 26 + ), 27 + 28 + TP_fast_assign( 29 + __entry->dev = inode->i_sb->s_dev; 30 + __entry->ino = inode->i_ino; 31 + __entry->gen = inode->i_generation; 32 + __entry->ctime_s = ctime->tv_sec; 33 + __entry->ctime_ns = ctime->tv_nsec; 34 + ), 35 + 36 + TP_printk("ino=%d:%d:%ld:%u ctime=%lld.%u", 37 + MAJOR(__entry->dev), MINOR(__entry->dev), __entry->ino, __entry->gen, 38 + __entry->ctime_s, __entry->ctime_ns 39 + ) 40 + ); 41 + 42 + DEFINE_EVENT(ctime, inode_set_ctime_to_ts, 43 + TP_PROTO(struct inode *inode, 44 + struct timespec64 *ctime), 45 + TP_ARGS(inode, ctime)); 46 + 47 + DEFINE_EVENT(ctime, ctime_xchg_skip, 48 + TP_PROTO(struct inode *inode, 49 + struct timespec64 *ctime), 50 + TP_ARGS(inode, ctime)); 51 + 52 + TRACE_EVENT(ctime_ns_xchg, 53 + TP_PROTO(struct inode *inode, 54 + u32 old, 55 + u32 new, 56 + u32 cur), 57 + 58 + TP_ARGS(inode, old, new, cur), 59 + 60 + TP_STRUCT__entry( 61 + __field(dev_t, dev) 62 + __field(ino_t, ino) 63 + __field(u32, gen) 64 + __field(u32, old) 65 + __field(u32, new) 66 + __field(u32, cur) 67 + ), 68 + 69 + TP_fast_assign( 70 + __entry->dev = inode->i_sb->s_dev; 71 + __entry->ino = inode->i_ino; 72 + __entry->gen = inode->i_generation; 73 + __entry->old = old; 74 + __entry->new = new; 75 + __entry->cur = cur; 76 + ), 77 + 78 + TP_printk("ino=%d:%d:%ld:%u old=%u:%s new=%u cur=%u:%s", 79 + MAJOR(__entry->dev), MINOR(__entry->dev), __entry->ino, __entry->gen, 80 + __entry->old & ~I_CTIME_QUERIED, 81 + __print_flags(__entry->old & I_CTIME_QUERIED, "|", CTIME_QUERIED_FLAGS), 82 + __entry->new, 83 + __entry->cur & ~I_CTIME_QUERIED, 84 + __print_flags(__entry->cur & I_CTIME_QUERIED, "|", CTIME_QUERIED_FLAGS) 85 + ) 86 + ); 87 + 88 + TRACE_EVENT(fill_mg_cmtime, 89 + TP_PROTO(struct inode *inode, 90 + struct timespec64 *ctime, 91 + struct timespec64 *mtime), 92 + 93 + TP_ARGS(inode, ctime, mtime), 94 + 95 + TP_STRUCT__entry( 96 + __field(dev_t, dev) 97 + __field(ino_t, ino) 98 + __field(time64_t, ctime_s) 99 + __field(time64_t, mtime_s) 100 + __field(u32, ctime_ns) 101 + __field(u32, mtime_ns) 102 + __field(u32, gen) 103 + ), 104 + 105 + TP_fast_assign( 106 + __entry->dev = inode->i_sb->s_dev; 107 + __entry->ino = inode->i_ino; 108 + __entry->gen = inode->i_generation; 109 + __entry->ctime_s = ctime->tv_sec; 110 + __entry->mtime_s = mtime->tv_sec; 111 + __entry->ctime_ns = ctime->tv_nsec; 112 + __entry->mtime_ns = mtime->tv_nsec; 113 + ), 114 + 115 + TP_printk("ino=%d:%d:%ld:%u ctime=%lld.%u mtime=%lld.%u", 116 + MAJOR(__entry->dev), MINOR(__entry->dev), __entry->ino, __entry->gen, 117 + __entry->ctime_s, __entry->ctime_ns, 118 + __entry->mtime_s, __entry->mtime_ns 119 + ) 120 + ); 121 + #endif /* _TRACE_TIMESTAMP_H */ 122 + 123 + /* This part must be outside protection */ 124 + #include <trace/define_trace.h>
+1 -1
mm/shmem.c
··· 4951 4951 .parameters = shmem_fs_parameters, 4952 4952 #endif 4953 4953 .kill_sb = kill_litter_super, 4954 - .fs_flags = FS_USERNS_MOUNT | FS_ALLOW_IDMAP, 4954 + .fs_flags = FS_USERNS_MOUNT | FS_ALLOW_IDMAP | FS_MGTIME, 4955 4955 }; 4956 4956 4957 4957 void __init shmem_init(void)