Skip to contentSkip to conversation

FUSE: implement direct mmap

PATCH New Tejun Heo 1 participant 7 messages Nov 20, 2008

Subsystems (4)
lore
Share & tools
lore

Tejun Heo

author
#0 lore raw

PATCH 1/1 FUSE: implement direct mmap

Hello,

This is the first take of fuse-implement-direct-mmap patchset. This patchset implements direct mmap support for FUSE (and CUSE). Each direct mmap area is backed by anonymous mapping (shmem_file) and the FUSE server can decide how they are shared.

mmap request is handled in two steps. MMAP first queries the server whether it wants to share the mapping with an existing one or create a new one, and if so, with which flags. MMAP_COMMIT notifies the server the result of mmap and if successful the fd the server can use to access the mmap region.

This patchset contains the following six patches.

0001-mmap-don-t-assume-f_op-mmap-doesn-t-change-vma.patch 0002-fdtable-export-alloc_fd.patch 0003-FUSE-don-t-let-fuse_req-end-put-the-base-referen.patch 0004-FUSE-make-request_wait_answer-wait-for-end-co.patch 0005-FUSE-implement-fuse_req-prep.patch 0006-FUSE-implement-direct-mmap.patch

0001-0002 update mm and fdtable for following FUSE changes. 0003-0005 update fuse_req->end() handling and add ->prep(). 0006 implements direct mmap.

Direct mmap implementation jumps through a few hoops to override vma->vm_file with shmem_file. It would be great if there's a cleaner way to achieve this. For details, please take a look at 0006.

Nick, can you please verify that 0001 doesn't break anything and replacing vma->vm_file in ->mmap() is okay?

Andrew, would 0002 be okay?

Thanks.

This patchset is on top of

master (ee2f6cc7f9ea2542ad46070ed62ba7aa04d08871)
+ [1] poll-allow-f_op_poll-to-sleep-take-2
+ [2] add-cdev_release-and-convert-cdev_alloc-to-use-it
+ [3] extend-FUSE patchset, take #2
+ [4] implement-CUSE patchset, take #2

This patchset is also available in the following git tree.

http://git.kernel.org/?p=linux/kernel/git/tj/misc.git;a=shortlog;h=fuse-mmap
git://git.kernel.org/pub/scm/linux/kernel/git/tj/misc.git fuse-mmap

and contains the following changes.

 fs/file.c            |    1 
 fs/fuse/cuse.c       |    6 
 fs/fuse/dev.c        |   75 ++++++---
 fs/fuse/file.c       |  412 ++++++++++++++++++++++++++++++++++++++++++++++++++-
 fs/fuse/fuse_i.h     |   19 ++
 fs/fuse/inode.c      |    1 
 include/linux/fuse.h |   47 +++++
 mm/mmap.c            |    1 
 8 files changed, 533 insertions(+), 29 deletions(-)

--
tejun

[1] http://lkml.org/lkml/2008/11/20/161
[2] http://article.gmane.org/gmane.linux.kernel/727133
[3] http://lkml.org/lkml/2008/11/20/171
[4] http://lkml.org/lkml/2008/11/20/179

author
#1 lore raw

PATCH 1/6 mmap: don't assume f_op->mmap() doesn't change vma->vm_file

mmap_region() assumes that vma->vm_file isn't changed by f_op->mmap() and continues to use cache file after f_op->mmap() returns. Don't assume that. This will be used by FUSE to redirect mmap to shmem_file.

---
 mm/mmap.c |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)
mm/mmap.c +1 -0
index d4855a6..2e4e0b5 100644
@@ -1177,6 +1177,7 @@ munmap_back:
 		vma->vm_file = file;
 		get_file(file);
 		error = file->f_op->mmap(file, vma);
+		file = vma->vm_file;
 		if (error)
 			goto unmap_and_free_vma;
 		if (vm_flags & VM_EXECUTABLE)
Signature · 1 line
1.5.6
author
#2 lore raw

PATCH 2/6 fdtable: export alloc_fd()

In reply to Tejun Heo

Export alloc_fd(). Will be used by FUSE.

---
 fs/file.c |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)
fs/file.c +1 -0
index f313314..806b3ad 100644
@@ -487,6 +487,7 @@ out:
 	spin_unlock(&files->file_lock);
 	return error;
 }
+EXPORT_SYMBOL_GPL(alloc_fd);
 
 int get_unused_fd(void)
 {
Signature · 1 line
1.5.6
author
#3 lore raw

PATCH 3/6 FUSE: don't let fuse_req->end() put the base reference

In reply to Tejun Heo

fuse_req->end() was supposed to be put the base reference but there's no reason why it should. It only makes things more complex. Move it out of ->end() and make it the responsibility of request_end().

---
 fs/fuse/dev.c   |    5 +----
 fs/fuse/file.c  |    4 +---
 fs/fuse/inode.c |    1 -
 3 files changed, 2 insertions(+), 8 deletions(-)
fs/fuse/dev.c +1 -4
index b8f70a0..25a134a 100644
@@ -296,8 +296,7 @@ static void request_end(struct fuse_conn *fc, struct fuse_req *req)
 	wake_up(&req->waitq);
 	if (end)
 		end(fc, req);
-	else
-		fuse_put_request(fc, req);
+	fuse_put_request(fc, req);
 }
 
 static void wait_answer_interruptible(struct fuse_conn *fc,
@@ -1052,8 +1051,6 @@ static void end_io_requests(struct fuse_conn *fc)
 		wake_up(&req->waitq);
 		if (end) {
 			req->end = NULL;
-			/* The end function will consume this reference */
-			__fuse_get_request(req);
 			spin_unlock(&fc->lock);
 			wait_event(req->waitq, !req->locked);
 			end(fc, req);
fs/fuse/file.c +1 -3
index 4d535ae..128356b 100644
@@ -85,7 +85,6 @@ static void fuse_release_end(struct fuse_conn *fc, struct fuse_req *req)
 {
 	dput(req->misc.release.dentry);
 	mntput(req->misc.release.vfsmount);
-	fuse_put_request(fc, req);
 }
 
 static void fuse_file_put(struct fuse_file *ff)
@@ -506,7 +505,6 @@ static void fuse_readpages_end(struct fuse_conn *fc, struct fuse_req *req)
 	}
 	if (req->ff)
 		fuse_file_put(req->ff);
-	fuse_put_request(fc, req);
 }
 
 static void fuse_send_readpages(struct fuse_req *req, struct file *file,
@@ -1056,7 +1054,6 @@ static void fuse_writepage_free(struct fuse_conn *fc, struct fuse_req *req)
 {
 	__free_page(req->pages[0]);
 	fuse_file_put(req->ff);
-	fuse_put_request(fc, req);
 }
 
 static void fuse_writepage_finish(struct fuse_conn *fc, struct fuse_req *req)
@@ -1100,6 +1097,7 @@ static void fuse_send_writepage(struct fuse_conn *fc, struct fuse_req *req)
 	fuse_writepage_finish(fc, req);
 	spin_unlock(&fc->lock);
 	fuse_writepage_free(fc, req);
+	fuse_put_request(fc, req);
 	spin_lock(&fc->lock);
 }
 
fs/fuse/inode.c +0 -1
index eae4ff9..75f0770 100644
@@ -781,7 +781,6 @@ static void process_init_reply(struct fuse_conn *fc, struct fuse_req *req)
 		fc->max_write = max_t(unsigned, 4096, fc->max_write);
 		fc->conn_init = 1;
 	}
-	fuse_put_request(fc, req);
 	fc->blocked = 0;
 	wake_up_all(&fc->blocked_waitq);
 }
Signature · 1 line
1.5.6
author
#4 lore raw

PATCH 4/6 FUSE: make request_wait_answer() wait for ->end() completion

In reply to Tejun Heo

Previously, a request was marked FINISHED before ->end() is executed and thus request_wait_answer() can return before it's done. This patch makes request_wait_answer() wait for ->end() to finish before returning.

Note that no current ->end() user waits for request completion, so this change doesn't cause any behavior difference.

While at it, beef up the comment above ->end() hook and clarify when and where it's called.

---
 fs/fuse/dev.c    |   41 +++++++++++++++++++++++++----------------
 fs/fuse/fuse_i.h |    5 ++++-
 2 files changed, 29 insertions(+), 17 deletions(-)
fs/fuse/dev.c +25 -16
index 25a134a..c83ff20 100644
@@ -278,7 +278,6 @@ static void request_end(struct fuse_conn *fc, struct fuse_req *req)
 	req->end = NULL;
 	list_del(&req->list);
 	list_del(&req->intr_entry);
-	req->state = FUSE_REQ_FINISHED;
 	if (req->background) {
 		if (fc->num_background == FUSE_MAX_BACKGROUND) {
 			fc->blocked = 0;
@@ -292,10 +291,21 @@ static void request_end(struct fuse_conn *fc, struct fuse_req *req)
 		fc->active_background--;
 		flush_bg_queue(fc);
 	}
+
 	spin_unlock(&fc->lock);
-	wake_up(&req->waitq);
-	if (end)
+
+	if (end) {
 		end(fc, req);
+		smp_wmb();
+	}
+
+	/*
+	 * We own this request and wake_up() has enough memory
+	 * barrier, no need to grab spin lock to set state.
+	 */
+	req->state = FUSE_REQ_FINISHED;
+
+	wake_up(&req->waitq);
 	fuse_put_request(fc, req);
 }
 
@@ -369,17 +379,16 @@ static void request_wait_answer(struct fuse_conn *fc, struct fuse_req *req)
 		return;
 
  aborted:
-	BUG_ON(req->state != FUSE_REQ_FINISHED);
-	if (req->locked) {
-		/* This is uninterruptible sleep, because data is
-		   being copied to/from the buffers of req.  During
-		   locked state, there mustn't be any filesystem
-		   operation (e.g. page fault), since that could lead
-		   to deadlock */
-		spin_unlock(&fc->lock);
-		wait_event(req->waitq, !req->locked);
-		spin_lock(&fc->lock);
-	}
+	spin_unlock(&fc->lock);
+	wait_event(req->waitq, req->state == FUSE_REQ_FINISHED);
+	/*
+	 * This is uninterruptible sleep, because data is being copied
+	 * to/from the buffers of req.  During locked state, there
+	 * mustn't be any filesystem operation (e.g. page fault),
+	 * since that could lead to deadlock
+	 */
+	wait_event(req->waitq, !req->locked);
+	spin_lock(&fc->lock);
 }
 
 void fuse_request_send(struct fuse_conn *fc, struct fuse_req *req)
@@ -1046,9 +1055,7 @@ static void end_io_requests(struct fuse_conn *fc)
 
 		req->aborted = 1;
 		req->out.h.error = -ECONNABORTED;
-		req->state = FUSE_REQ_FINISHED;
 		list_del_init(&req->list);
-		wake_up(&req->waitq);
 		if (end) {
 			req->end = NULL;
 			spin_unlock(&fc->lock);
@@ -1056,6 +1063,8 @@ static void end_io_requests(struct fuse_conn *fc)
 			end(fc, req);
 			spin_lock(&fc->lock);
 		}
+		req->state = FUSE_REQ_FINISHED;
+		wake_up(&req->waitq);
 	}
 }
 
fs/fuse/fuse_i.h +4 -1
index 23df478..90eb42c 100644
@@ -282,7 +282,10 @@ struct fuse_req {
 	/** Link on fi->writepages */
 	struct list_head writepages_entry;
 
-	/** Request completion callback */
+	/** Request completion callback.  This function is called from
+	    the kernel context of the FUSE server if the request isn't
+	    being aborted.  If the request is being aborted, it's
+	    called from the kernel context of the aborting process. */
 	void (*end)(struct fuse_conn *, struct fuse_req *);
 
 	/** Request is stolen from fuse_file->reserved_req */
Signature · 1 line
1.5.6
author
#5 lore raw

PATCH 5/6 FUSE: implement fuse_req->prep()

In reply to Tejun Heo

Implement ->prep() which is the opposite equivalent of ->end(). It's called right before the request is passed to userland server in the kernel context of the server. ->prep() can fail the request without disrupting the whole channel.

This will be used by direct mmap implementation.

---
 fs/fuse/dev.c    |   29 ++++++++++++++++++++++++++---
 fs/fuse/fuse_i.h |    6 ++++++
 2 files changed, 32 insertions(+), 3 deletions(-)
fs/fuse/dev.c +26 -3
index c83ff20..05414b8 100644
@@ -747,6 +747,7 @@ static ssize_t fuse_dev_read(struct kiocb *iocb, const struct iovec *iov,
 			      unsigned long nr_segs, loff_t pos)
 {
 	int err;
+	bool restart;
 	struct fuse_req *req;
 	struct fuse_in *in;
 	struct fuse_copy_state cs;
@@ -793,12 +794,32 @@ static ssize_t fuse_dev_read(struct kiocb *iocb, const struct iovec *iov,
 		goto restart;
 	}
 	spin_unlock(&fc->lock);
+
+	restart = false;
 	fuse_copy_init(&cs, fc, 1, req, iov, nr_segs);
+
+	/*
+	 * Execute prep if available.  Failure from prep doesn't
+	 * indicate faulty channel.  On failure, fail the current
+	 * request and proceed to the next one.
+	 */
+	if (req->prep) {
+		err = req->prep(fc, req);
+		if (err) {
+			restart = true;
+			goto finish;
+		}
+	}
+
 	err = fuse_copy_one(&cs, &in->h, sizeof(in->h));
-	if (!err)
-		err = fuse_copy_args(&cs, in->numargs, in->argpages,
-				     (struct fuse_arg *) in->args, 0);
+	if (err)
+		goto finish;
+
+	err = fuse_copy_args(&cs, in->numargs, in->argpages,
+			     (struct fuse_arg *) in->args, 0);
+ finish:
 	fuse_copy_finish(&cs);
+
 	spin_lock(&fc->lock);
 	req->locked = 0;
 	if (req->aborted) {
@@ -808,6 +829,8 @@ static ssize_t fuse_dev_read(struct kiocb *iocb, const struct iovec *iov,
 	if (err) {
 		req->out.h.error = -EIO;
 		request_end(fc, req);
+		if (restart)
+			goto restart;
 		return err;
 	}
 	if (!req->isreply)
fs/fuse/fuse_i.h +6 -0
index 90eb42c..9d3becb 100644
@@ -282,6 +282,12 @@ struct fuse_req {
 	/** Link on fi->writepages */
 	struct list_head writepages_entry;
 
+	/** Request preparation callback.  Called from the kernel
+	    context of the FUSE server before passing the request to
+	    the FUSE server.  Non-zero return from this function will
+	    fail the request. */
+	int (*prep)(struct fuse_conn *, struct fuse_req *);
+
 	/** Request completion callback.  This function is called from
 	    the kernel context of the FUSE server if the request isn't
 	    being aborted.  If the request is being aborted, it's
Signature · 1 line
1.5.6
author
#6 lore raw

PATCH 6/6 FUSE: implement direct mmap

In reply to Tejun Heo

This patch implements direct mmap. It allows FUSE server to honor each mmap request with anonymous mapping. FUSE server can make multiple mmap requests share a single anonymous mapping or separate mappings as it sees fit.

mmap request is handled in two steps. MMAP first queries the server whether it wants to share the mapping with an existing one or create a new one, and if so, with which flags. MMAP_COMMIT notifies the server the result of mmap and if successful the fd the server can use to access the mmap region.

Internally, shmem_file is used to back the mmap areas and vma->vm_file is overridden from the FUSE file to the shmem_file.

For details, please read the comment on top of
fuse_file_direct_mmap().

---
 fs/fuse/cuse.c       |    6 +
 fs/fuse/file.c       |  408 +++++++++++++++++++++++++++++++++++++++++++++++++-
 fs/fuse/fuse_i.h     |    8 +
 include/linux/fuse.h |   47 ++++++
 4 files changed, 468 insertions(+), 1 deletions(-)
fs/fuse/cuse.c +6 -0
index 048e67d..c4102df 100644
@@ -183,6 +183,11 @@ static long cuse_file_compat_ioctl(struct file *file, unsigned int cmd,
 	return fuse_file_do_ioctl(file->private_data, cmd, arg, flags);
 }
 
+static int cuse_file_direct_mmap(struct file *file, struct vm_area_struct *vma)
+{
+	return fuse_file_direct_mmap(file->private_data, vma);
+}
+
 static const struct file_operations cuse_frontend_fops = {
 	.read			= cuse_direct_read,
 	.write			= cuse_direct_write,
@@ -193,6 +198,7 @@ static const struct file_operations cuse_frontend_fops = {
 	.poll			= cuse_file_poll,
 	.unlocked_ioctl		= cuse_file_ioctl,
 	.compat_ioctl		= cuse_file_compat_ioctl,
+	.mmap			= cuse_file_direct_mmap,
 };
 
 
fs/fuse/file.c +407 -1

426 lines not on this page · raw message

fs/fuse/fuse_i.h +8 -0
index 9d3becb..016ed54 100644
@@ -262,6 +262,13 @@ struct fuse_req {
 			struct fuse_write_out out;
 		} write;
 		struct fuse_lk_in lk_in;
+		struct {
+			/** to move filp for mmap between client and server */
+			struct file *file;
+		} mmap;
+		struct {
+			struct fuse_munmap_in in;
+		} munmap;
 	} misc;
 
 	/** page vector */
@@ -572,6 +579,7 @@ int fuse_file_lock(struct file *file, int cmd, struct file_lock *fl);
 int fuse_file_flock(struct file *file, int cmd, struct file_lock *fl);
 long fuse_file_do_ioctl(struct file *file, unsigned int cmd, unsigned long arg,
 			unsigned int flags);
+int fuse_file_direct_mmap(struct file *file, struct vm_area_struct *vma);
 
 /**
  * Notify poll wakeup
include/linux/fuse.h +47 -0

65 lines not on this page · raw message

Signature · 1 line
1.5.6