diff options
Diffstat (limited to 'arch/mips/mm/mmap.c')
| -rw-r--r-- | arch/mips/mm/mmap.c | 193 | 
1 files changed, 170 insertions, 23 deletions
diff --git a/arch/mips/mm/mmap.c b/arch/mips/mm/mmap.c index ae3c20a9556e..9ff5d0fac556 100644 --- a/arch/mips/mm/mmap.c +++ b/arch/mips/mm/mmap.c @@ -10,6 +10,7 @@  #include <linux/mm.h>  #include <linux/mman.h>  #include <linux/module.h> +#include <linux/personality.h>  #include <linux/random.h>  #include <linux/sched.h> @@ -17,21 +18,65 @@ unsigned long shm_align_mask = PAGE_SIZE - 1;	/* Sane caches */  EXPORT_SYMBOL(shm_align_mask); +/* gap between mmap and stack */ +#define MIN_GAP (128*1024*1024UL) +#define MAX_GAP        ((TASK_SIZE)/6*5) + +static int mmap_is_legacy(void) +{ +	if (current->personality & ADDR_COMPAT_LAYOUT) +		return 1; + +	if (rlimit(RLIMIT_STACK) == RLIM_INFINITY) +		return 1; + +	return sysctl_legacy_va_layout; +} + +static unsigned long mmap_base(unsigned long rnd) +{ +	unsigned long gap = rlimit(RLIMIT_STACK); + +	if (gap < MIN_GAP) +		gap = MIN_GAP; +	else if (gap > MAX_GAP) +		gap = MAX_GAP; + +	return PAGE_ALIGN(TASK_SIZE - gap - rnd); +} + +static inline unsigned long COLOUR_ALIGN_DOWN(unsigned long addr, +					      unsigned long pgoff) +{ +	unsigned long base = addr & ~shm_align_mask; +	unsigned long off = (pgoff << PAGE_SHIFT) & shm_align_mask; + +	if (base + off <= addr) +		return base + off; + +	return base - off; +} +  #define COLOUR_ALIGN(addr,pgoff)				\  	((((addr) + shm_align_mask) & ~shm_align_mask) +	\  	 (((pgoff) << PAGE_SHIFT) & shm_align_mask)) -unsigned long arch_get_unmapped_area(struct file *filp, unsigned long addr, -	unsigned long len, unsigned long pgoff, unsigned long flags) +enum mmap_allocation_direction {UP, DOWN}; + +static unsigned long arch_get_unmapped_area_foo(struct file *filp, +	unsigned long addr0, unsigned long len, unsigned long pgoff, +	unsigned long flags, enum mmap_allocation_direction dir)  { -	struct vm_area_struct * vmm; +	struct mm_struct *mm = current->mm; +	struct vm_area_struct *vma; +	unsigned long addr = addr0;  	int do_color_align; -	if (len > TASK_SIZE) +	if (unlikely(len > TASK_SIZE))  		return -ENOMEM;  	if (flags & MAP_FIXED) { -		/* Even MAP_FIXED mappings must reside within TASK_SIZE.  */ +		/* Even MAP_FIXED mappings must reside within TASK_SIZE */  		if (TASK_SIZE - len < addr)  			return -EINVAL; @@ -48,34 +93,130 @@ unsigned long arch_get_unmapped_area(struct file *filp, unsigned long addr,  	do_color_align = 0;  	if (filp || (flags & MAP_SHARED))  		do_color_align = 1; + +	/* requesting a specific address */  	if (addr) {  		if (do_color_align)  			addr = COLOUR_ALIGN(addr, pgoff);  		else  			addr = PAGE_ALIGN(addr); -		vmm = find_vma(current->mm, addr); + +		vma = find_vma(mm, addr);  		if (TASK_SIZE - len >= addr && -		    (!vmm || addr + len <= vmm->vm_start)) +		   (!vma || addr + len <= vma->vm_start))  			return addr;  	} -	addr = current->mm->mmap_base; -	if (do_color_align) -		addr = COLOUR_ALIGN(addr, pgoff); -	else -		addr = PAGE_ALIGN(addr); -	for (vmm = find_vma(current->mm, addr); ; vmm = vmm->vm_next) { -		/* At this point:  (!vmm || addr < vmm->vm_end). */ -		if (TASK_SIZE - len < addr) -			return -ENOMEM; -		if (!vmm || addr + len <= vmm->vm_start) -			return addr; -		addr = vmm->vm_end; +	if (dir == UP) { +		addr = mm->mmap_base; +			if (do_color_align) +				addr = COLOUR_ALIGN(addr, pgoff); +			else +				addr = PAGE_ALIGN(addr); + +		for (vma = find_vma(current->mm, addr); ; vma = vma->vm_next) { +			/* At this point:  (!vma || addr < vma->vm_end). */ +			if (TASK_SIZE - len < addr) +				return -ENOMEM; +			if (!vma || addr + len <= vma->vm_start) +				return addr; +			addr = vma->vm_end; +			if (do_color_align) +				addr = COLOUR_ALIGN(addr, pgoff); +		 } +	 } else { +		/* check if free_area_cache is useful for us */ +		if (len <= mm->cached_hole_size) { +			mm->cached_hole_size = 0; +			mm->free_area_cache = mm->mmap_base; +		} + +		/* either no address requested or can't fit in requested address hole */ +		addr = mm->free_area_cache; +			if (do_color_align) { +				unsigned long base = +					COLOUR_ALIGN_DOWN(addr - len, pgoff); + +			addr = base + len; +		 } + +		/* make sure it can fit in the remaining address space */ +		if (likely(addr > len)) { +			vma = find_vma(mm, addr - len); +			if (!vma || addr <= vma->vm_start) { +				/* remember the address as a hint for next time */ +				return mm->free_area_cache = addr-len; +			} +		} + +		if (unlikely(mm->mmap_base < len)) +			goto bottomup; + +		addr = mm->mmap_base-len;  		if (do_color_align) -			addr = COLOUR_ALIGN(addr, pgoff); +			addr = COLOUR_ALIGN_DOWN(addr, pgoff); + +		do { +			/* +			 * Lookup failure means no vma is above this address, +			 * else if new region fits below vma->vm_start, +			 * return with success: +			 */ +			vma = find_vma(mm, addr); +			if (likely(!vma || addr+len <= vma->vm_start)) { +				/* remember the address as a hint for next time */ +				return mm->free_area_cache = addr; +			} + +			/* remember the largest hole we saw so far */ +			if (addr + mm->cached_hole_size < vma->vm_start) +				mm->cached_hole_size = vma->vm_start - addr; + +			/* try just below the current vma->vm_start */ +			addr = vma->vm_start-len; +			if (do_color_align) +				addr = COLOUR_ALIGN_DOWN(addr, pgoff); +		} while (likely(len < vma->vm_start)); + +bottomup: +		/* +		 * A failed mmap() very likely causes application failure, +		 * so fall back to the bottom-up function here. This scenario +		 * can happen with large stack limits and large mmap() +		 * allocations. +		 */ +		mm->cached_hole_size = ~0UL; +		mm->free_area_cache = TASK_UNMAPPED_BASE; +		addr = arch_get_unmapped_area(filp, addr0, len, pgoff, flags); +		/* +		 * Restore the topdown base: +		 */ +		mm->free_area_cache = mm->mmap_base; +		mm->cached_hole_size = ~0UL; + +		return addr;  	}  } +unsigned long arch_get_unmapped_area(struct file *filp, unsigned long addr0, +	unsigned long len, unsigned long pgoff, unsigned long flags) +{ +	return arch_get_unmapped_area_foo(filp, +			addr0, len, pgoff, flags, UP); +} + +/* + * There is no need to export this but sched.h declares the function as + * extern so making it static here results in an error. + */ +unsigned long arch_get_unmapped_area_topdown(struct file *filp, +	unsigned long addr0, unsigned long len, unsigned long pgoff, +	unsigned long flags) +{ +	return arch_get_unmapped_area_foo(filp, +			addr0, len, pgoff, flags, DOWN); +} +  void arch_pick_mmap_layout(struct mm_struct *mm)  {  	unsigned long random_factor = 0UL; @@ -89,9 +230,15 @@ void arch_pick_mmap_layout(struct mm_struct *mm)  			random_factor &= 0xffffffful;  	} -	mm->mmap_base = TASK_UNMAPPED_BASE + random_factor; -	mm->get_unmapped_area = arch_get_unmapped_area; -	mm->unmap_area = arch_unmap_area; +	if (mmap_is_legacy()) { +		mm->mmap_base = TASK_UNMAPPED_BASE + random_factor; +		mm->get_unmapped_area = arch_get_unmapped_area; +		mm->unmap_area = arch_unmap_area; +	} else { +		mm->mmap_base = mmap_base(random_factor); +		mm->get_unmapped_area = arch_get_unmapped_area_topdown; +		mm->unmap_area = arch_unmap_area_topdown; +	}  }  static inline unsigned long brk_rnd(void)  |