diff mbox

[04/12] vrange: Add proper fork/exec semantics for volatile ranges

Message ID 1367605636-18284-5-git-send-email-john.stultz@linaro.org
State Superseded
Headers show

Commit Message

John Stultz May 3, 2013, 6:27 p.m. UTC
Volatile ranges should be copied on fork, and cleared on exec.
This patch tries to add these semantics.

The duplicating of the vranges on fork is a little ackward.
This is because we cannot allocate while holding the vrange_root
lock, since the allocation could cause reclaim, which may take
the vrange_root lock and deadlock. Thus we have to drop all
the vrange_root locks for each allocation.

Ideas for a better approach would be appreciated!

Signed-off-by: John Stultz <john.stultz@linaro.org>
---
 fs/exec.c              |  1 +
 include/linux/vrange.h |  5 ++++-
 kernel/fork.c          |  6 ++++++
 mm/vrange.c            | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 65 insertions(+), 1 deletion(-)
diff mbox

Patch

diff --git a/fs/exec.c b/fs/exec.c
index a96a488..417218d 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -55,6 +55,7 @@ 
 #include <linux/pipe_fs_i.h>
 #include <linux/oom.h>
 #include <linux/compat.h>
+#include <linux/vrange.h>
 
 #include <asm/uaccess.h>
 #include <asm/mmu_context.h>
diff --git a/include/linux/vrange.h b/include/linux/vrange.h
index 2c1c58a..4424b8d 100644
--- a/include/linux/vrange.h
+++ b/include/linux/vrange.h
@@ -34,12 +34,15 @@  static inline int vrange_type(struct vrange *vrange)
 
 void vrange_init(void);
 extern void vrange_root_cleanup(struct vrange_root *vroot);
-
+extern int vrange_root_duplicate(struct vrange_root *orig,
+					struct vrange_root *new);
 #else
 
 static inline void vrange_init(void) {};
 static inline void vrange_root_init(struct vrange_root *vroot, int type) {};
 static inline void vrange_root_cleanup(struct vrange_root *vroot) {};
+static inline int vrange_root_duplicate(struct vrange_root *orig,
+					struct vrange_root *new) {return 0};
 
 #endif
 #endif /* _LINIUX_VRANGE_H */
diff --git a/kernel/fork.c b/kernel/fork.c
index 360ad65..80d5bab 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -847,6 +847,12 @@  struct mm_struct *dup_mm(struct task_struct *tsk)
 	if (mm->binfmt && !try_module_get(mm->binfmt->module))
 		goto free_pt;
 
+	/* XXX - Shouldn't this be already done in mm_init? */
+	vrange_root_init(&mm->vroot, VRANGE_MM);
+
+	if (vrange_root_duplicate(&oldmm->vroot, &mm->vroot))
+		goto free_pt;
+
 	return mm;
 
 free_pt:
diff --git a/mm/vrange.c b/mm/vrange.c
index 537e3d5..4949152 100644
--- a/mm/vrange.c
+++ b/mm/vrange.c
@@ -148,6 +148,60 @@  static int vrange_remove(struct vrange_root *vroot,
 	return 0;
 }
 
+int vrange_root_duplicate(struct vrange_root *old, struct vrange_root *new)
+{
+	struct vrange *old_range, *new_range, *alloc_range;
+	struct rb_node *old_next, *new_next;
+	int ret = 0;
+
+	/*
+	 * This is awkward, as taking the vrange_lock here causes problems
+	 * since if call vrange_alloc, while holding the vrange_lock,
+	 * the allocation could then trigger direct reclaim which could
+	 * then try to take the vrange_lock() and deadlock.
+	 *
+	 * So instead, dance around this dropping locks & restarting when
+	 * we have to allocate.
+	 */
+again:
+	alloc_range = __vrange_alloc();
+	if (!alloc_range)
+		return -ENOMEM;
+
+	mutex_lock_nested(&old->v_lock, I_MUTEX_PARENT);
+	mutex_lock_nested(&new->v_lock, I_MUTEX_CHILD);
+
+	old_next = rb_first(&old->v_rb);
+	new_next = rb_first(&new->v_rb);
+	while (old_next) {
+		old_range = vrange_entry(old_next);
+		if (!new_next) {
+			new_range = alloc_range;
+			alloc_range = NULL;
+		} else {
+			new_range = vrange_entry(new_next);
+			__vrange_remove(new_range);
+		}
+		__vrange_set(new_range, old_range->node.start,
+				old_range->node.last, old_range->purged);
+		__vrange_add(new_range, new);
+
+		if (!alloc_range) {
+			vrange_unlock(new);
+			vrange_unlock(old);
+			goto again;
+		}
+
+		old_next = rb_next(old_next);
+		new_next = rb_next(new_next);
+	}
+	vrange_unlock(new);
+	vrange_unlock(old);
+
+	__vrange_free(alloc_range);
+
+	return ret;
+}
 
 void vrange_root_cleanup(struct vrange_root *vroot)
 {