patch-2.1.51 linux/mm/mmap.c

Next file: linux/net/ipv4/fib.c
Previous file: linux/mm/memory.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.1.50/linux/mm/mmap.c linux/mm/mmap.c
@@ -373,10 +373,12 @@
  *    Unmapping between to intermediate points, making a hole.
  *
  * Case 4 involves the creation of 2 new areas, for each side of
- * the hole.
+ * the hole.  If possible, we reuse the existing area rather than
+ * allocate a new one, and the return indicates whether the old
+ * area was reused.
  */
-static void unmap_fixup(struct vm_area_struct *area,
-		 unsigned long addr, size_t len)
+static int unmap_fixup(struct vm_area_struct *area, unsigned long addr,
+			 size_t len, struct vm_area_struct **extra)
 {
 	struct vm_area_struct *mpnt;
 	unsigned long end = addr + len;
@@ -391,7 +393,7 @@
 			area->vm_ops->close(area);
 		if (area->vm_dentry)
 			dput(area->vm_dentry);
-		return;
+		return 0;
 	}
 
 	/* Work out to one of the ends. */
@@ -403,17 +405,16 @@
 	} else {
 	/* Unmapping a hole: area->vm_start < addr <= end < area->vm_end */
 		/* Add end mapping -- leave beginning for below */
-		mpnt = kmem_cache_alloc(vm_area_cachep, SLAB_KERNEL);
+		mpnt = *extra;
+		*extra = NULL;
 
-		if (!mpnt)
-			return;
 		mpnt->vm_mm = area->vm_mm;
 		mpnt->vm_start = end;
 		mpnt->vm_end = area->vm_end;
 		mpnt->vm_page_prot = area->vm_page_prot;
 		mpnt->vm_flags = area->vm_flags;
 		mpnt->vm_ops = area->vm_ops;
-		mpnt->vm_offset += (end - area->vm_start);
+		mpnt->vm_offset = area->vm_offset + (end - area->vm_start);
 		mpnt->vm_dentry = dget(area->vm_dentry);
 		if (mpnt->vm_ops && mpnt->vm_ops->open)
 			mpnt->vm_ops->open(mpnt);
@@ -421,18 +422,18 @@
 		insert_vm_struct(current->mm, mpnt);
 	}
 
-	/* Construct whatever mapping is needed. */
-	mpnt = kmem_cache_alloc(vm_area_cachep, SLAB_KERNEL);
-	if (!mpnt)
-		return;
-	*mpnt = *area;
-	if (mpnt->vm_ops && mpnt->vm_ops->open)
-		mpnt->vm_ops->open(mpnt);
+	/* Close the current area ... */
 	if (area->vm_ops && area->vm_ops->close) {
+		end = area->vm_end; /* save new end */
 		area->vm_end = area->vm_start;
 		area->vm_ops->close(area);
+		area->vm_end = end;
 	}
-	insert_vm_struct(current->mm, mpnt);
+	/* ... then reopen and reinsert. */
+	if (area->vm_ops && area->vm_ops->open)
+		area->vm_ops->open(area);
+	insert_vm_struct(current->mm, area);
+	return 1;
 }
 
 asmlinkage int sys_munmap(unsigned long addr, size_t len)
@@ -452,7 +453,8 @@
  */
 int do_munmap(unsigned long addr, size_t len)
 {
-	struct vm_area_struct *mpnt, *next, *free;
+	struct vm_area_struct *mpnt, *next, *free, *extra;
+	int freed;
 
 	if ((addr & ~PAGE_MASK) || addr > TASK_SIZE || len > TASK_SIZE-addr)
 		return -EINVAL;
@@ -471,6 +473,14 @@
 	if (!mpnt)
 		return 0;
 
+	/*
+	 * We may need one additional vma to fix up the mappings ... 
+	 * and this is the last chance for an easy error exit.
+	 */
+	extra = kmem_cache_alloc(vm_area_cachep, SLAB_KERNEL);
+	if (!extra)
+		return -ENOMEM;
+
 	next = mpnt->vm_next;
 
 	/* we have mpnt->vm_next = next and addr < mpnt->vm_end */
@@ -486,19 +496,18 @@
 		free = mpnt;
 		mpnt = next;
 	}
-	if (free == NULL)
-		return 0;
 
 	/* Ok - we have the memory areas we should free on the 'free' list,
 	 * so release them, and unmap the page range..
 	 * If the one of the segments is only being partially unmapped,
 	 * it will put new vm_area_struct(s) into the address space.
 	 */
-	do {
+	freed = 0;
+	while ((mpnt = free) != NULL) {
 		unsigned long st, end, size;
 
-		mpnt = free;
 		free = free->vm_next;
+		freed = 1;
 
 		remove_shared_vm_struct(mpnt);
 
@@ -514,12 +523,19 @@
 		zap_page_range(current->mm, st, size);
 		flush_tlb_range(current->mm, st, end);
 
-		unmap_fixup(mpnt, st, size);
+		/*
+		 * Fix the mapping, and free the old area if it wasn't reused.
+		 */
+		if (!unmap_fixup(mpnt, st, size, &extra))
+			kmem_cache_free(vm_area_cachep, mpnt);
+	}
 
-		kmem_cache_free(vm_area_cachep, mpnt);
-	} while (free);
+	/* Release the extra vma struct if it wasn't used */
+	if (extra)
+		kmem_cache_free(vm_area_cachep, extra);
 
-	current->mm->mmap_cache = NULL;		/* Kill the cache. */
+	if (freed)
+		current->mm->mmap_cache = NULL;	/* Kill the cache. */
 	return 0;
 }
 

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov