Introduce fuse_perform_write. With fusexmp (a passthrough filesystem), large (1MB) writes into a backing tmpfs filesystem are sped up by almost 4 times (256MB/s vs 71MB/s). Signed-off-by: Nick Piggin Index: linux-2.6/fs/fuse/file.c =================================================================== --- linux-2.6.orig/fs/fuse/file.c +++ linux-2.6/fs/fuse/file.c @@ -443,15 +443,131 @@ static size_t fuse_send_write(struct fus return outarg.size; } +static ssize_t fuse_perform_write(struct file *file, + struct address_space *mapping, struct iov_iter *ii, + loff_t pos) +{ + struct inode *inode = mapping->host; + struct fuse_conn *fc; + size_t nmax; + ssize_t err = 0, res = 0; + + if (is_bad_inode(inode)) + return -EIO; + + fc = get_fuse_conn(inode); + nmax = fc->max_write; + + do { + struct fuse_req *req; + unsigned offset = pos & (PAGE_CACHE_SIZE - 1); + pgoff_t index = pos >> PAGE_CACHE_SHIFT; + size_t nres, maxbytes, nbytes; + unsigned i, npages; + + req = fuse_get_req(fc); + if (IS_ERR(req)) { + err = PTR_ERR(req); + break; + } + req->page_offset = offset; + + maxbytes = min(iov_iter_count(ii), nmax); + npages = (maxbytes + offset + PAGE_SIZE - 1) >> PAGE_CACHE_SHIFT; + BUG_ON(!npages); + nbytes = 0; + +again: + if (unlikely(iov_iter_fault_in_readable(ii))) { + err = -EFAULT; + break; + } + + for (i = 0; i < npages; i++) { + size_t bytes, tmp; + struct page *page; + + page = __grab_cache_page(mapping, index + i); + if (unlikely(!page)) { + if (i == 0) { + err = -ENOMEM; + goto out; + } + break; + } + bytes = min_t(size_t, PAGE_CACHE_SIZE - offset, maxbytes); + + pagefault_disable(); + tmp = iov_iter_copy_from_user_atomic(page, ii, + offset, bytes); + pagefault_enable(); + if (!tmp) { + unlock_page(page); + page_cache_release(page); + if (i == 0) + goto again; + break; + } + + flush_dcache_page(page); + if (tmp == PAGE_CACHE_SIZE) + SetPageChecked(page); /* bring it uptodate */ + req->pages[i] = page; + + iov_iter_advance(ii, tmp); + maxbytes -= tmp; + nbytes += tmp; + offset += tmp; + if (offset == PAGE_CACHE_SIZE) + offset = 0; + if (tmp < bytes) + break; + } + + req->num_pages = i; + nres = fuse_send_write(req, file, inode, pos, nbytes); + err = req->out.h.error; + + res += nres; + pos += nres; + + for (i = 0; i < req->num_pages; i++) { + struct page *page; + page = req->pages[i]; + if (PageChecked(page)) { + ClearPageChecked(page); + if (likely(!err)) + SetPageUptodate(page); + } + unlock_page(page); + page_cache_release(page); + } + fuse_put_request(fc, req); + if (nres < nbytes) + break; + } while (iov_iter_count(ii) && !err); + +out: + if (res > 0) { + spin_lock(&fc->lock); + if (pos > inode->i_size) + i_size_write(inode, pos); + spin_unlock(&fc->lock); + fuse_invalidate_attr(inode); + + return res; + } + return err; +} + static int fuse_write_begin(struct file *file, struct address_space *mapping, loff_t pos, unsigned len, unsigned flags, struct page **pagep, void **fsdata) { - pgoff_t index = pos >> PAGE_CACHE_SHIFT; - - *pagep = __grab_cache_page(mapping, index); + *pagep = __grab_cache_page(mapping, pos >> PAGE_CACHE_SHIFT); if (!*pagep) return -ENOMEM; + return 0; } @@ -827,6 +943,7 @@ static const struct file_operations fuse static const struct address_space_operations fuse_file_aops = { .readpage = fuse_readpage, + .perform_write = fuse_perform_write, .write_begin = fuse_write_begin, .write_end = fuse_write_end, .readpages = fuse_readpages,