1 vfs: add support for a lazytime mount option
3 Add a new mount option which enables a new "lazytime" mode. This mode
4 causes atime, mtime, and ctime updates to only be made to the
5 in-memory version of the inode. The on-disk times will only get
6 updated when (a) if the inode needs to be updated for some non-time
7 related change, (b) if userspace calls fsync(), syncfs() or sync(), or
8 (c) just before an undeleted inode is evicted from memory.
10 This is OK according to POSIX because there are no guarantees after a
11 crash unless userspace explicitly requests via a fsync(2) call.
13 For workloads which feature a large number of random write to a
14 preallocated file, the lazytime mount option significantly reduces
15 writes to the inode table. The repeated 4k writes to a single block
16 will result in undesirable stress on flash devices and SMR disk
17 drives. Even on conventional HDD's, the repeated writes to the inode
18 table block will trigger Adjacent Track Interference (ATI) remediation
19 latencies, which very negatively impact 99.9 percentile latencies ---
20 which is a very big deal for web serving tiers (for example).
22 Google-Bug-Id: 18297052
24 Signed-off-by: Theodore Ts'o <tytso@mit.edu>
26 fs/fs-writeback.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++++------
27 fs/inode.c | 43 +++++++++++++++++++++++++++++++++++++------
28 fs/proc_namespace.c | 1 +
29 fs/sync.c | 8 ++++++++
30 include/linux/backing-dev.h | 1 +
31 include/linux/fs.h | 11 +++++++++--
32 include/uapi/linux/fs.h | 1 +
33 mm/backing-dev.c | 9 +++++++--
34 8 files changed, 113 insertions(+), 16 deletions(-)
36 diff --git a/fs/fs-writeback.c b/fs/fs-writeback.c
37 index ef9bef1..ef8c5d8 100644
38 --- a/fs/fs-writeback.c
39 +++ b/fs/fs-writeback.c
40 @@ -397,7 +397,7 @@ static void requeue_inode(struct inode *inode, struct bdi_writeback *wb,
41 * shot. If still dirty, it will be redirty_tail()'ed below. Update
42 * the dirty time to prevent enqueue and sync it again.
44 - if ((inode->i_state & I_DIRTY) &&
45 + if ((inode->i_state & I_DIRTY_WB) &&
46 (wbc->sync_mode == WB_SYNC_ALL || wbc->tagged_writepages))
47 inode->dirtied_when = jiffies;
49 @@ -428,13 +428,15 @@ static void requeue_inode(struct inode *inode, struct bdi_writeback *wb,
51 redirty_tail(inode, wb);
53 - } else if (inode->i_state & I_DIRTY) {
54 + } else if (inode->i_state & I_DIRTY_WB) {
56 * Filesystems can dirty the inode during writeback operations,
57 * such as delayed allocation during submission or metadata
58 * updates after data IO completion.
60 redirty_tail(inode, wb);
61 + } else if (inode->i_state & I_DIRTY_TIME) {
62 + list_move(&inode->i_wb_list, &wb->b_dirty_time);
64 /* The inode is clean. Remove from writeback lists. */
65 list_del_init(&inode->i_wb_list);
66 @@ -482,11 +484,11 @@ __writeback_single_inode(struct inode *inode, struct writeback_control *wbc)
67 /* Clear I_DIRTY_PAGES if we've written out all dirty pages */
68 if (!mapping_tagged(mapping, PAGECACHE_TAG_DIRTY))
69 inode->i_state &= ~I_DIRTY_PAGES;
70 - dirty = inode->i_state & I_DIRTY;
71 - inode->i_state &= ~(I_DIRTY_SYNC | I_DIRTY_DATASYNC);
72 + dirty = inode->i_state & I_DIRTY_INODE;
73 + inode->i_state &= ~I_DIRTY_INODE;
74 spin_unlock(&inode->i_lock);
75 /* Don't write the inode if only I_DIRTY_PAGES was set */
76 - if (dirty & (I_DIRTY_SYNC | I_DIRTY_DATASYNC)) {
78 int err = write_inode(inode, wbc);
81 @@ -1162,7 +1164,7 @@ void __mark_inode_dirty(struct inode *inode, int flags)
83 spin_lock(&inode->i_lock);
84 if ((inode->i_state & flags) != flags) {
85 - const int was_dirty = inode->i_state & I_DIRTY;
86 + const int was_dirty = inode->i_state & I_DIRTY_WB;
88 inode->i_state |= flags;
90 @@ -1224,6 +1226,24 @@ out_unlock_inode:
92 EXPORT_SYMBOL(__mark_inode_dirty);
94 +void inode_requeue_dirtytime(struct inode *inode)
96 + struct backing_dev_info *bdi = inode_to_bdi(inode);
98 + spin_lock(&bdi->wb.list_lock);
99 + spin_lock(&inode->i_lock);
100 + if ((inode->i_state & I_DIRTY_WB) == 0) {
101 + if (inode->i_state & I_DIRTY_TIME)
102 + list_move(&inode->i_wb_list, &bdi->wb.b_dirty_time);
104 + list_del_init(&inode->i_wb_list);
106 + spin_unlock(&inode->i_lock);
107 + spin_unlock(&bdi->wb.list_lock);
110 +EXPORT_SYMBOL(inode_requeue_dirtytime);
112 static void wait_sb_inodes(struct super_block *sb)
114 struct inode *inode, *old_inode = NULL;
115 @@ -1277,6 +1297,28 @@ static void wait_sb_inodes(struct super_block *sb)
120 + * Take all of the indoes on the dirty_time list, and mark them as
121 + * dirty, so they will be written out.
123 +static void flush_sb_dirty_time(struct super_block *sb)
125 + struct bdi_writeback *wb = &sb->s_bdi->wb;
128 + spin_lock(&wb->list_lock);
129 + list_cut_position(&tmp, &wb->b_dirty_time, wb->b_dirty_time.prev);
130 + while (!list_empty(&tmp)) {
131 + struct inode *inode = wb_inode(tmp.prev);
133 + list_del_init(&inode->i_wb_list);
134 + spin_unlock(&wb->list_lock);
135 + mark_inode_dirty_sync(inode);
136 + spin_lock(&wb->list_lock);
138 + spin_unlock(&wb->list_lock);
142 * writeback_inodes_sb_nr - writeback dirty inodes from given super_block
143 * @sb: the superblock
144 @@ -1388,6 +1430,7 @@ void sync_inodes_sb(struct super_block *sb)
146 WARN_ON(!rwsem_is_locked(&sb->s_umount));
148 + flush_sb_dirty_time(sb);
149 bdi_queue_work(sb->s_bdi, &work);
150 wait_for_completion(&done);
152 diff --git a/fs/inode.c b/fs/inode.c
153 index 8f5c4b5..9e464cc 100644
157 * inode_sb_list_lock protects:
158 * sb->s_inodes, inode->i_sb_list
159 * bdi->wb.list_lock protects:
160 - * bdi->wb.b_{dirty,io,more_io}, inode->i_wb_list
161 + * bdi->wb.b_{dirty,io,more_io,dirty_time}, inode->i_wb_list
162 * inode_hash_lock protects:
163 * inode_hashtable, inode->i_hash
165 @@ -1430,11 +1430,22 @@ static void iput_final(struct inode *inode)
167 void iput(struct inode *inode)
170 - BUG_ON(inode->i_state & I_CLEAR);
172 - if (atomic_dec_and_lock(&inode->i_count, &inode->i_lock))
176 + BUG_ON(inode->i_state & I_CLEAR);
178 + if (atomic_dec_and_lock(&inode->i_count, &inode->i_lock)) {
179 + if (inode->i_nlink && (inode->i_state & I_DIRTY_TIME)) {
180 + atomic_inc(&inode->i_count);
181 + inode->i_state &= ~I_DIRTY_TIME;
182 + spin_unlock(&inode->i_lock);
183 + if (inode->i_op->write_time)
184 + inode->i_op->write_time(inode);
185 + else if (inode->i_sb->s_op->write_inode)
186 + mark_inode_dirty_sync(inode);
193 @@ -1515,6 +1526,26 @@ static int update_time(struct inode *inode, struct timespec *time, int flags)
195 inode->i_mtime = *time;
197 + if ((inode->i_sb->s_flags & MS_LAZYTIME) &&
198 + !(flags & S_VERSION) &&
199 + !(inode->i_state & (I_DIRTY_SYNC | I_DIRTY_DATASYNC))) {
200 + if (inode->i_state & I_DIRTY_TIME)
202 + spin_lock(&inode->i_lock);
203 + if (inode->i_state & (I_DIRTY_SYNC | I_DIRTY_DATASYNC)) {
204 + spin_unlock(&inode->i_lock);
207 + if (inode->i_state & I_DIRTY_TIME) {
208 + spin_unlock(&inode->i_lock);
211 + inode->i_state |= I_DIRTY_TIME;
212 + spin_unlock(&inode->i_lock);
213 + inode_requeue_dirtytime(inode);
217 if (inode->i_op->write_time)
218 return inode->i_op->write_time(inode);
219 mark_inode_dirty_sync(inode);
220 diff --git a/fs/proc_namespace.c b/fs/proc_namespace.c
221 index 73ca174..f98234a 100644
222 --- a/fs/proc_namespace.c
223 +++ b/fs/proc_namespace.c
224 @@ -44,6 +44,7 @@ static int show_sb_opts(struct seq_file *m, struct super_block *sb)
225 { MS_SYNCHRONOUS, ",sync" },
226 { MS_DIRSYNC, ",dirsync" },
227 { MS_MANDLOCK, ",mand" },
228 + { MS_LAZYTIME, ",lazytime" },
231 const struct proc_fs_info *fs_infop;
232 diff --git a/fs/sync.c b/fs/sync.c
233 index bdc729d..6ac7bf0 100644
236 @@ -177,8 +177,16 @@ SYSCALL_DEFINE1(syncfs, int, fd)
238 int vfs_fsync_range(struct file *file, loff_t start, loff_t end, int datasync)
240 + struct inode *inode = file->f_mapping->host;
242 if (!file->f_op->fsync)
244 + if (!datasync && (inode->i_state & I_DIRTY_TIME)) {
245 + spin_lock(&inode->i_lock);
246 + inode->i_state &= ~I_DIRTY_TIME;
247 + spin_unlock(&inode->i_lock);
248 + mark_inode_dirty_sync(inode);
250 return file->f_op->fsync(file, start, end, datasync);
252 EXPORT_SYMBOL(vfs_fsync_range);
253 diff --git a/include/linux/backing-dev.h b/include/linux/backing-dev.h
254 index 5da6012..4cdf733 100644
255 --- a/include/linux/backing-dev.h
256 +++ b/include/linux/backing-dev.h
257 @@ -55,6 +55,7 @@ struct bdi_writeback {
258 struct list_head b_dirty; /* dirty inodes */
259 struct list_head b_io; /* parked for writeback */
260 struct list_head b_more_io; /* parked for more writeback */
261 + struct list_head b_dirty_time; /* time stamps are dirty */
262 spinlock_t list_lock; /* protects the b_* lists */
265 diff --git a/include/linux/fs.h b/include/linux/fs.h
266 index 3633239..55cf34d 100644
267 --- a/include/linux/fs.h
268 +++ b/include/linux/fs.h
269 @@ -1721,19 +1721,26 @@ struct super_operations {
270 #define __I_DIO_WAKEUP 9
271 #define I_DIO_WAKEUP (1 << I_DIO_WAKEUP)
272 #define I_LINKABLE (1 << 10)
273 +#define I_DIRTY_TIME (1 << 11)
275 -#define I_DIRTY (I_DIRTY_SYNC | I_DIRTY_DATASYNC | I_DIRTY_PAGES)
276 +/* Inode should be on the b_dirty/b_io/b_more_io lists */
277 +#define I_DIRTY_WB (I_DIRTY_SYNC | I_DIRTY_DATASYNC | I_DIRTY_PAGES)
278 +/* Inode should be on the b_dirty/b_io/b_more_io/b_dirty_time lists */
279 +#define I_DIRTY (I_DIRTY_SYNC | I_DIRTY_DATASYNC | I_DIRTY_PAGES | I_DIRTY_TIME)
280 +/* The inode itself is dirty */
281 +#define I_DIRTY_INODE (I_DIRTY_SYNC | I_DIRTY_DATASYNC | I_DIRTY_TIME)
283 extern void __mark_inode_dirty(struct inode *, int);
284 static inline void mark_inode_dirty(struct inode *inode)
286 - __mark_inode_dirty(inode, I_DIRTY);
287 + __mark_inode_dirty(inode, I_DIRTY_WB);
290 static inline void mark_inode_dirty_sync(struct inode *inode)
292 __mark_inode_dirty(inode, I_DIRTY_SYNC);
294 +extern void inode_requeue_dirtytime(struct inode *);
296 extern void inc_nlink(struct inode *inode);
297 extern void drop_nlink(struct inode *inode);
298 diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h
299 index 3735fa0..cc9713a 100644
300 --- a/include/uapi/linux/fs.h
301 +++ b/include/uapi/linux/fs.h
302 @@ -90,6 +90,7 @@ struct inodes_stat_t {
303 #define MS_KERNMOUNT (1<<22) /* this is a kern_mount call */
304 #define MS_I_VERSION (1<<23) /* Update inode I_version field */
305 #define MS_STRICTATIME (1<<24) /* Always perform atime updates */
306 +#define MS_LAZYTIME (1<<25) /* Update the on-disk [acm]times lazily */
308 /* These sb flags are internal to the kernel */
309 #define MS_NOSEC (1<<28)
310 diff --git a/mm/backing-dev.c b/mm/backing-dev.c
311 index 0ae0df5..14851fe 100644
312 --- a/mm/backing-dev.c
313 +++ b/mm/backing-dev.c
314 @@ -69,10 +69,10 @@ static int bdi_debug_stats_show(struct seq_file *m, void *v)
315 unsigned long background_thresh;
316 unsigned long dirty_thresh;
317 unsigned long bdi_thresh;
318 - unsigned long nr_dirty, nr_io, nr_more_io;
319 + unsigned long nr_dirty, nr_io, nr_more_io, nr_dirty_time;
322 - nr_dirty = nr_io = nr_more_io = 0;
323 + nr_dirty = nr_io = nr_more_io = nr_dirty_time = 0;
324 spin_lock(&wb->list_lock);
325 list_for_each_entry(inode, &wb->b_dirty, i_wb_list)
327 @@ -80,6 +80,8 @@ static int bdi_debug_stats_show(struct seq_file *m, void *v)
329 list_for_each_entry(inode, &wb->b_more_io, i_wb_list)
331 + list_for_each_entry(inode, &wb->b_dirty_time, i_wb_list)
333 spin_unlock(&wb->list_lock);
335 global_dirty_limits(&background_thresh, &dirty_thresh);
336 @@ -98,6 +100,7 @@ static int bdi_debug_stats_show(struct seq_file *m, void *v)
340 + "b_dirty_time: %10lu\n"
343 (unsigned long) K(bdi_stat(bdi, BDI_WRITEBACK)),
344 @@ -111,6 +114,7 @@ static int bdi_debug_stats_show(struct seq_file *m, void *v)
349 !list_empty(&bdi->bdi_list), bdi->state);
352 @@ -418,6 +422,7 @@ static void bdi_wb_init(struct bdi_writeback *wb, struct backing_dev_info *bdi)
353 INIT_LIST_HEAD(&wb->b_dirty);
354 INIT_LIST_HEAD(&wb->b_io);
355 INIT_LIST_HEAD(&wb->b_more_io);
356 + INIT_LIST_HEAD(&wb->b_dirty_time);
357 spin_lock_init(&wb->list_lock);
358 INIT_DELAYED_WORK(&wb->dwork, bdi_writeback_workfn);