Commit f3996c8c authored by Andrew Morton's avatar Andrew Morton Committed by Linus Torvalds

[PATCH] Concurrent O_SYNC write support

In databases it is common to have multiple threads or processes performing
O_SYNC writes against different parts of the same file.

Our performance at this is poor, because each writer blocks access to the
file by waiting on I/O completion while holding i_sem: everything is
serialised.

The patch improves things by moving the writing and waiting outside i_sem.
So other threads can get in and submit their I/O and permit the disk
scheduler to optimise the IO patterns better.

Also, the O_SYNC writer only writes and waits on the pages which he wrote,
rather than writing and waiting on all dirty pages in the file.

The reason we haven't been able to do this before is that the required walk
of the address_space page lists is easily livelockable without the i_sem
serialisation.  But in this patch we perform the waiting via a radix-tree
walk of the affected pages.  This cannot be livelocked.

The sync of the inode's metadata is still performed inside i_sem.  This is
because it is list-based and is hence still livelockable.  However it is
usually the case that databases are overwriting existing file blocks and
there will be no dirty buffers attached to the address_space anyway.

The code is careful to ensure that the IO for the pages and the IO for the
metadata are nonblockingly scheduled at the same time.  This is am improvemtn
over the current code, which will issue two separate write-and-wait cycles:
one for metadata, one for pages.

Note from Suparna:
Reworked to use the tagged radix-tree based writeback infrastructure.
Signed-off-by: default avatarSuparna Bhattacharya <suparna@in.ibm.com>
Signed-off-by: default avatarAndrew Morton <akpm@osdl.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@osdl.org>
parent db205bd6
...@@ -206,12 +206,6 @@ int nobh_prepare_write(struct page*, unsigned, unsigned, get_block_t*); ...@@ -206,12 +206,6 @@ int nobh_prepare_write(struct page*, unsigned, unsigned, get_block_t*);
int nobh_commit_write(struct file *, struct page *, unsigned, unsigned); int nobh_commit_write(struct file *, struct page *, unsigned, unsigned);
int nobh_truncate_page(struct address_space *, loff_t); int nobh_truncate_page(struct address_space *, loff_t);
#define OSYNC_METADATA (1<<0)
#define OSYNC_DATA (1<<1)
#define OSYNC_INODE (1<<2)
int generic_osync_inode(struct inode *, struct address_space *, int);
/* /*
* inline definitions * inline definitions
*/ */
......
...@@ -827,6 +827,11 @@ extern int vfs_rename(struct inode *, struct dentry *, struct inode *, struct de ...@@ -827,6 +827,11 @@ extern int vfs_rename(struct inode *, struct dentry *, struct inode *, struct de
#define DT_SOCK 12 #define DT_SOCK 12
#define DT_WHT 14 #define DT_WHT 14
#define OSYNC_METADATA (1<<0)
#define OSYNC_DATA (1<<1)
#define OSYNC_INODE (1<<2)
int generic_osync_inode(struct inode *, struct address_space *, int);
/* /*
* This is the "filldir" function type, used by readdir() to let * This is the "filldir" function type, used by readdir() to let
* the kernel specify what kind of dirent layout it wants to have. * the kernel specify what kind of dirent layout it wants to have.
......
...@@ -103,6 +103,8 @@ void page_writeback_init(void); ...@@ -103,6 +103,8 @@ void page_writeback_init(void);
void balance_dirty_pages_ratelimited(struct address_space *mapping); void balance_dirty_pages_ratelimited(struct address_space *mapping);
int pdflush_operation(void (*fn)(unsigned long), unsigned long arg0); int pdflush_operation(void (*fn)(unsigned long), unsigned long arg0);
int do_writepages(struct address_space *mapping, struct writeback_control *wbc); int do_writepages(struct address_space *mapping, struct writeback_control *wbc);
int sync_page_range(struct inode *inode, struct address_space *mapping,
loff_t pos, size_t count);
/* pdflush.c */ /* pdflush.c */
extern int nr_pdflush_threads; /* Global so it can be exported to sysctl extern int nr_pdflush_threads; /* Global so it can be exported to sysctl
......
...@@ -247,6 +247,34 @@ static int wait_on_page_writeback_range(struct address_space *mapping, ...@@ -247,6 +247,34 @@ static int wait_on_page_writeback_range(struct address_space *mapping,
return ret; return ret;
} }
/*
* Write and wait upon all the pages in the passed range. This is a "data
* integrity" operation. It waits upon in-flight writeout before starting and
* waiting upon new writeout. If there was an IO error, return it.
*
* We need to re-take i_sem during the generic_osync_inode list walk because
* it is otherwise livelockable.
*/
int sync_page_range(struct inode *inode, struct address_space *mapping,
loff_t pos, size_t count)
{
pgoff_t start = pos >> PAGE_CACHE_SHIFT;
pgoff_t end = (pos + count - 1) >> PAGE_CACHE_SHIFT;
int ret;
if (mapping->backing_dev_info->memory_backed || !count)
return 0;
ret = filemap_fdatawrite_range(mapping, pos, pos + count - 1);
if (ret == 0) {
down(&inode->i_sem);
ret = generic_osync_inode(inode, mapping, OSYNC_METADATA);
up(&inode->i_sem);
}
if (ret == 0)
ret = wait_on_page_writeback_range(mapping, start, end);
return ret;
}
/** /**
* filemap_fdatawait - walk the list of under-writeback pages of the given * filemap_fdatawait - walk the list of under-writeback pages of the given
* address space and wait for all of them. * address space and wait for all of them.
...@@ -257,7 +285,6 @@ int filemap_fdatawait(struct address_space *mapping) ...@@ -257,7 +285,6 @@ int filemap_fdatawait(struct address_space *mapping)
{ {
return wait_on_page_writeback_range(mapping, 0, -1); return wait_on_page_writeback_range(mapping, 0, -1);
} }
EXPORT_SYMBOL(filemap_fdatawait); EXPORT_SYMBOL(filemap_fdatawait);
int filemap_write_and_wait(struct address_space *mapping) int filemap_write_and_wait(struct address_space *mapping)
...@@ -1965,11 +1992,13 @@ generic_file_buffered_write(struct kiocb *iocb, const struct iovec *iov, ...@@ -1965,11 +1992,13 @@ generic_file_buffered_write(struct kiocb *iocb, const struct iovec *iov,
/* /*
* For now, when the user asks for O_SYNC, we'll actually give O_DSYNC * For now, when the user asks for O_SYNC, we'll actually give O_DSYNC
*/ */
if (status >= 0) { if (likely(status >= 0)) {
if ((file->f_flags & O_SYNC) || IS_SYNC(inode)) if (unlikely((file->f_flags & O_SYNC) || IS_SYNC(inode))) {
status = generic_osync_inode(inode, mapping, if (!a_ops->writepage || !is_sync_kiocb(iocb))
OSYNC_METADATA|OSYNC_DATA); status = generic_osync_inode(inode, mapping,
} OSYNC_METADATA|OSYNC_DATA);
}
}
/* /*
* If we get here for O_DIRECT writes then we must have fallen through * If we get here for O_DIRECT writes then we must have fallen through
...@@ -2082,36 +2111,52 @@ ssize_t generic_file_aio_write(struct kiocb *iocb, const char __user *buf, ...@@ -2082,36 +2111,52 @@ ssize_t generic_file_aio_write(struct kiocb *iocb, const char __user *buf,
size_t count, loff_t pos) size_t count, loff_t pos)
{ {
struct file *file = iocb->ki_filp; struct file *file = iocb->ki_filp;
struct inode *inode = file->f_mapping->host; struct address_space *mapping = file->f_mapping;
ssize_t err; struct inode *inode = mapping->host;
struct iovec local_iov = { .iov_base = (void __user *)buf, .iov_len = count }; ssize_t ret;
struct iovec local_iov = { .iov_base = (void __user *)buf,
.iov_len = count };
BUG_ON(iocb->ki_pos != pos); BUG_ON(iocb->ki_pos != pos);
down(&inode->i_sem); down(&inode->i_sem);
err = generic_file_aio_write_nolock(iocb, &local_iov, 1, ret = generic_file_aio_write_nolock(iocb, &local_iov, 1,
&iocb->ki_pos); &iocb->ki_pos);
up(&inode->i_sem); up(&inode->i_sem);
return err; if (ret > 0 && ((file->f_flags & O_SYNC) || IS_SYNC(inode))) {
} ssize_t err;
err = sync_page_range(inode, mapping, pos, ret);
if (err < 0)
ret = err;
}
return ret;
}
EXPORT_SYMBOL(generic_file_aio_write); EXPORT_SYMBOL(generic_file_aio_write);
ssize_t generic_file_write(struct file *file, const char __user *buf, ssize_t generic_file_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos) size_t count, loff_t *ppos)
{ {
struct inode *inode = file->f_mapping->host; struct address_space *mapping = file->f_mapping;
ssize_t err; struct inode *inode = mapping->host;
struct iovec local_iov = { .iov_base = (void __user *)buf, .iov_len = count }; ssize_t ret;
struct iovec local_iov = { .iov_base = (void __user *)buf,
.iov_len = count };
down(&inode->i_sem); down(&inode->i_sem);
err = generic_file_write_nolock(file, &local_iov, 1, ppos); ret = generic_file_write_nolock(file, &local_iov, 1, ppos);
up(&inode->i_sem); up(&inode->i_sem);
return err; if (ret > 0 && ((file->f_flags & O_SYNC) || IS_SYNC(inode))) {
} ssize_t err;
err = sync_page_range(inode, mapping, *ppos - ret, ret);
if (err < 0)
ret = err;
}
return ret;
}
EXPORT_SYMBOL(generic_file_write); EXPORT_SYMBOL(generic_file_write);
ssize_t generic_file_readv(struct file *filp, const struct iovec *iov, ssize_t generic_file_readv(struct file *filp, const struct iovec *iov,
...@@ -2130,14 +2175,23 @@ ssize_t generic_file_readv(struct file *filp, const struct iovec *iov, ...@@ -2130,14 +2175,23 @@ ssize_t generic_file_readv(struct file *filp, const struct iovec *iov,
EXPORT_SYMBOL(generic_file_readv); EXPORT_SYMBOL(generic_file_readv);
ssize_t generic_file_writev(struct file *file, const struct iovec *iov, ssize_t generic_file_writev(struct file *file, const struct iovec *iov,
unsigned long nr_segs, loff_t * ppos) unsigned long nr_segs, loff_t *ppos)
{ {
struct inode *inode = file->f_mapping->host; struct address_space *mapping = file->f_mapping;
struct inode *inode = mapping->host;
ssize_t ret; ssize_t ret;
down(&inode->i_sem); down(&inode->i_sem);
ret = generic_file_write_nolock(file, iov, nr_segs, ppos); ret = generic_file_write_nolock(file, iov, nr_segs, ppos);
up(&inode->i_sem); up(&inode->i_sem);
if (ret > 0 && ((file->f_flags & O_SYNC) || IS_SYNC(inode))) {
int err;
err = sync_page_range(inode, mapping, *ppos - ret, ret);
if (err < 0)
ret = err;
}
return ret; return ret;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment