iomap: don't mark the inode dirty in iomap_write_end
Subsystems (3)
Andreas Gruenbacher
maintainerPATCH v2 1/3 iomap: don't mark the inode dirty in iomap_write_end
Marking the inode dirty for each page copied into the page cache can be very inefficient for file systems that use the VFS dirty inode tracking, and is completely pointless for those that don't use the VFS dirty inode tracking. So instead, only set an iomap flag when changing the in-core inode size, and open code the rest of __generic_write_end.
Partially based on code from Christoph Hellwig.
--- fs/gfs2/bmap.c | 2 ++ fs/iomap.c | 15 ++++++++++++++- include/linux/iomap.h | 1 + 3 files changed, 17 insertions(+), 1 deletion(-)
fs/gfs2/bmap.c +2 -0
@@ -1182,6 +1182,8 @@ static int gfs2_iomap_end(struct inode *inode, loff_t pos, loff_t length,if (ip->i_qadata && ip->i_qadata->qa_qd_num)gfs2_quota_unlock (ip); +if (iomap->flags &IOMAP_F_SIZE_CHANGED ) +mark_inode_dirty (inode);gfs2_write_unlock (inode); out:
fs/iomap.c +14 -1
@@ -777,6 +777,7 @@ iomap_write_end(struct inode *inode, loff_t pos, unsigned len, unsigned copied, struct page *page, struct iomap *iomap) { const struct iomap_page_ops *page_ops = iomap->page_ops; +loff_t old_size = inode->i_size;int ret;if (iomap->type ==IOMAP_INLINE ) {
@@ -788,7 +789,19 @@ iomap_write_end(struct inode *inode, loff_t pos, unsigned len, ret =__iomap_write_end (inode, pos, len, copied, page, iomap); } -__generic_write_end (inode, pos, ret, page); +/* +* Update the in-memory inode size after copying the data into the page +* cache. It's up to the file system to write the updated size to disk, +* preferably after I/O completion so that no stale data is exposed. +*/ +if (pos + ret > old_size) { +i_size_write (inode, pos + ret); + iomap->flags |=IOMAP_F_SIZE_CHANGED ; + } +unlock_page (page); + +if (old_size < pos) +pagecache_isize_extended (inode, old_size, pos);if (page_ops && page_ops->page_done) page_ops->page_done (inode, pos, copied, page, iomap);put_page (page);
include/linux/iomap.h +1 -0
@@ -35,6 +35,7 @@ struct vm_fault;#define IOMAP_F_NEW 0x01/* blocks have been newly allocated */ #define IOMAP_F_DIRTY 0x02/* uncommitted metadata */ #define IOMAP_F_BUFFER_HEAD 0x04/* file system requires buffer heads */ +#define IOMAP_F_SIZE_CHANGED 0x08/* file size has changed */ /* * Flags that only need to be reported for IOMAP_REPORT requests:
Signature · 1 line
2.20.1
Andreas Gruenbacher
maintainerPATCH v2 2/3 fs: fold __generic_write_end back into generic_write_end
From: Christoph Hellwig <[email protected]>
This effectively reverts a6d639da63ae ("fs: factor out a __generic_write_end helper") as we now open code what is left of that helper in iomap.
--- fs/buffer.c | 62 ++++++++++++++++++++++++--------------------------- fs/internal.h | 2 -- 2 files changed, 29 insertions(+), 35 deletions(-)
fs/buffer.c +29 -33
@@ -2086,38 +2086,6 @@ int block_write_begin(struct address_space *mapping, loff_t pos, unsigned len, }EXPORT_SYMBOL (block_write_begin); -void __generic_write_end (struct inode *inode,loff_t pos,unsigned copied, -struct page *page) -{ -loff_t old_size = inode->i_size; -bool i_size_changed = false; - -/* -* No need to use i_size_read() here, the i_size cannot change under us -* because we hold i_rwsem. -* -* But it's important to update i_size while still holding page lock: -* page writeout could otherwise come in and zero beyond i_size. -*/ -if (pos + copied > inode->i_size) { -i_size_write (inode, pos + copied); - i_size_changed = true; - } - -unlock_page (page); - -if (old_size < pos) -pagecache_isize_extended (inode, old_size, pos); -/* -* Don't mark the inode dirty under page lock. First, it unnecessarily -* makes the holding time of page lock longer. Second, it forces lock -* ordering of page lock and transaction start for journaling -* filesystems. -*/ -if (i_size_changed) -mark_inode_dirty (inode); -} -int block_write_end (struct file *file,struct address_space *mapping,loff_t pos,unsigned len,unsigned copied,struct page *page,void *fsdata)
@@ -2158,9 +2126,37 @@ int generic_write_end(struct file *file, struct address_space *mapping, loff_t pos, unsigned len, unsigned copied, struct page *page, void *fsdata) { + struct inode *inode = mapping->host; +loff_t old_size = inode->i_size; +bool i_size_changed = false; + copied =block_write_end (file, mapping, pos, len, copied, page, fsdata); -__generic_write_end (mapping->host, pos, copied, page); + +/* +* No need to use i_size_read() here, the i_size cannot change under us +* because we hold i_rwsem. +* +* But it's important to update i_size while still holding page lock: +* page writeout could otherwise come in and zero beyond i_size. +*/ +if (pos + copied > inode->i_size) { +i_size_write (inode, pos + copied); + i_size_changed = true; + } + +unlock_page (page);put_page (page); + +if (old_size < pos) +pagecache_isize_extended (inode, old_size, pos); +/* +* Don't mark the inode dirty under page lock. First, it unnecessarily +* makes the holding time of page lock longer. Second, it forces lock +* ordering of page lock and transaction start for journaling +* filesystems. +*/ +if (i_size_changed) +mark_inode_dirty (inode);return copied; }EXPORT_SYMBOL (generic_write_end);
fs/internal.h +0 -2
@@ -40,8 +40,6 @@ static inline int __sync_blockdev(struct block_device *bdev, int wait)extern void guard_bio_eod (int rw,struct bio *bio);extern int __block_write_begin_int (struct page *page,loff_t pos,unsigned len,get_block_t *get_block,struct iomap *iomap); -void __generic_write_end (struct inode *inode,loff_t pos,unsigned copied, -struct page *page);/* * char_dev.c
Signature · 1 line
2.20.1
Re: [PATCH v2 2/3] fs: fold __generic_write_end back into generic_write_end
Quoted 8 lines · Andreas Gruenbacher
From: Christoph Hellwig <[email protected]>
This effectively reverts
a6d639da63ae("fs: factor out a__generic_write_endhelper") as we now open code what is left of that helper in iomap.Signed-off-by Christoph Hellwig <[email protected]>Signed-off-by Andreas Gruenbacher <[email protected]>
Looks ok,
--D
Quoted 103 lines
--- fs/buffer.c | 62 ++++++++++++++++++++++++--------------------------- fs/internal.h | 2 -- 2 files changed, 29 insertions(+), 35 deletions(-)fs/buffer.c +29
-33@@ -2086,38 +2086,6 @@ int block_write_begin(struct address_space *mapping, loff_t pos, unsigned len, }EXPORT_SYMBOL (block_write_begin); -void __generic_write_end (struct inode *inode,loff_t pos,unsigned copied, -struct page *page) -{ -loff_t old_size = inode->i_size; -bool i_size_changed = false; - -/* -* No need to use i_size_read() here, the i_size cannot change under us -* because we hold i_rwsem. -* -* But it's important to update i_size while still holding page lock: -* page writeout could otherwise come in and zero beyond i_size. -*/ -if (pos + copied > inode->i_size) { -i_size_write (inode, pos + copied); - i_size_changed = true; - } - -unlock_page (page); - -if (old_size < pos) -pagecache_isize_extended (inode, old_size, pos); -/* -* Don't mark the inode dirty under page lock. First, it unnecessarily -* makes the holding time of page lock longer. Second, it forces lock -* ordering of page lock and transaction start for journaling -* filesystems. -*/ -if (i_size_changed) -mark_inode_dirty (inode); -} -int block_write_end (struct file *file,struct address_space *mapping,loff_t pos,unsigned len,unsigned copied,struct page *page,void *fsdata)@@ -2158,9 +2126,37 @@ int generic_write_end(struct file *file, struct address_space *mapping, loff_t pos, unsigned len, unsigned copied, struct page *page, void *fsdata) { + struct inode *inode = mapping->host; +loff_t old_size = inode->i_size; +bool i_size_changed = false; + copied =block_write_end (file, mapping, pos, len, copied, page, fsdata); -__generic_write_end (mapping->host, pos, copied, page); + +/* +* No need to use i_size_read() here, the i_size cannot change under us +* because we hold i_rwsem. +* +* But it's important to update i_size while still holding page lock: +* page writeout could otherwise come in and zero beyond i_size. +*/ +if (pos + copied > inode->i_size) { +i_size_write (inode, pos + copied); + i_size_changed = true; + } + +unlock_page (page);put_page (page); + +if (old_size < pos) +pagecache_isize_extended (inode, old_size, pos); +/* +* Don't mark the inode dirty under page lock. First, it unnecessarily +* makes the holding time of page lock longer. Second, it forces lock +* ordering of page lock and transaction start for journaling +* filesystems. +*/ +if (i_size_changed) +mark_inode_dirty (inode);return copied; }EXPORT_SYMBOL (generic_write_end);fs/internal.h +0
-2@@ -40,8 +40,6 @@ static inline int __sync_blockdev(struct block_device *bdev, int wait)extern void guard_bio_eod (int rw,struct bio *bio);extern int __block_write_begin_int (struct page *page,loff_t pos,unsigned len,get_block_t *get_block,struct iomap *iomap); -void __generic_write_end (struct inode *inode,loff_t pos,unsigned copied, -struct page *page);/* * char_dev.c Signature · 1 line
2.20.1
Andreas Gruenbacher
maintainerPATCH v2 3/3 iomap: fix page_done callback for short writes
In reply to Andreas GruenbacherWhen we truncate a short write to have it retried, pass the truncated length to the page_done callback instead of the full length.
--- fs/iomap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
fs/iomap.c +1 -1
@@ -803,7 +803,7 @@ iomap_write_end(struct inode *inode, loff_t pos, unsigned len,if (old_size < pos)pagecache_isize_extended (inode, old_size, pos);if (page_ops && page_ops->page_done) - page_ops->page_done (inode, pos, copied, page, iomap); + page_ops->page_done (inode, pos, ret, page, iomap);put_page (page);if (ret < len)
Signature · 1 line
2.20.1
Re: [PATCH v2 3/3] iomap: fix page_done callback for short writes
Quoted 4 lines · Andreas Gruenbacher
When we truncate a short write to have it retried, pass the truncated length to the
page_donecallback instead of the full length.Signed-off-by Andreas Gruenbacher <[email protected]>
Looks good:
Re: [PATCH v2 3/3] iomap: fix page_done callback for short writes
In reply to Andreas GruenbacherQuoted 4 lines · Andreas Gruenbacher
When we truncate a short write to have it retried, pass the truncated length to the
page_donecallback instead of the full length.Signed-off-by Andreas Gruenbacher <[email protected]>
LGTM,
--D
Quoted 20 lines
--- fs/iomap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)fs/iomap.c +1
-1@@ -803,7 +803,7 @@ iomap_write_end(struct inode *inode, loff_t pos, unsigned len,if (old_size < pos)pagecache_isize_extended (inode, old_size, pos);if (page_ops && page_ops->page_done) - page_ops->page_done (inode, pos, copied, page, iomap); + page_ops->page_done (inode, pos, ret, page, iomap);put_page (page);if (ret < len)Signature · 1 line
2.20.1
Quoted 10 lines · Andreas Gruenbacher
Marking the inode dirty for each page copied into the page cache can be very inefficient for file systems that use the VFS dirty inode tracking, and is completely pointless for those that don't use the VFS dirty inode tracking. So instead, only set an iomap flag when changing the in-core inode size, and open code the rest of
__generic_write_end.Partially based on code from Christoph Hellwig.
Signed-off-by Andreas Gruenbacher <[email protected]>Reviewed-by Christoph Hellwig <[email protected]>
Looks ok,
--D
Quoted 67 lines
--- fs/gfs2/bmap.c | 2 ++ fs/iomap.c | 15 ++++++++++++++- include/linux/iomap.h | 1 + 3 files changed, 17 insertions(+), 1 deletion(-)fs/gfs2/bmap.c +2
-0@@ -1182,6 +1182,8 @@ static int gfs2_iomap_end(struct inode *inode, loff_t pos, loff_t length,if (ip->i_qadata && ip->i_qadata->qa_qd_num)gfs2_quota_unlock (ip); +if (iomap->flags &IOMAP_F_SIZE_CHANGED ) +mark_inode_dirty (inode);gfs2_write_unlock (inode); out:fs/iomap.c +14
-1@@ -777,6 +777,7 @@ iomap_write_end(struct inode *inode, loff_t pos, unsigned len, unsigned copied, struct page *page, struct iomap *iomap) { const struct iomap_page_ops *page_ops = iomap->page_ops; +loff_t old_size = inode->i_size;int ret;if (iomap->type ==IOMAP_INLINE ) {@@ -788,7 +789,19 @@ iomap_write_end(struct inode *inode, loff_t pos, unsigned len, ret =__iomap_write_end (inode, pos, len, copied, page, iomap); } -__generic_write_end (inode, pos, ret, page); +/* +* Update the in-memory inode size after copying the data into the page +* cache. It's up to the file system to write the updated size to disk, +* preferably after I/O completion so that no stale data is exposed. +*/ +if (pos + ret > old_size) { +i_size_write (inode, pos + ret); + iomap->flags |=IOMAP_F_SIZE_CHANGED ; + } +unlock_page (page); + +if (old_size < pos) +pagecache_isize_extended (inode, old_size, pos);if (page_ops && page_ops->page_done) page_ops->page_done (inode, pos, copied, page, iomap);put_page (page);include/linux/iomap.h +1
-0@@ -35,6 +35,7 @@ struct vm_fault;#define IOMAP_F_NEW 0x01/* blocks have been newly allocated */ #define IOMAP_F_DIRTY 0x02/* uncommitted metadata */ #define IOMAP_F_BUFFER_HEAD 0x04/* file system requires buffer heads */ +#define IOMAP_F_SIZE_CHANGED 0x08/* file size has changed */ /* * Flags that only need to be reported for IOMAP_REPORT requests: Signature · 1 line
2.20.1