Commit d0be89f6 authored by Jian Peng's avatar Jian Peng Committed by Ralf Baechle

MIPS: topdown mmap support

This patch introduced topdown mmap support in user process address
space allocation policy.

Recently, we ran some large applications that use mmap heavily and
lead to OOM due to inflexible mmap allocation policy on MIPS32.

Since most other major archs supported it for years, it is reasonable
to follow the trend and reduce the pain of porting applications.

Due to cache aliasing concern, arch_get_unmapped_area_topdown() and
other helper functions are implemented in arch/mips/kernel/syscall.c.
Signed-off-by: default avatarJian Peng <jipeng2005@gmail.com>
Cc: David Daney <ddaney@caviumnetworks.com>
Cc: linux-mips@linux-mips.org
Patchwork: https://patchwork.linux-mips.org/patch/2389/Signed-off-by: default avatarRalf Baechle <ralf@linux-mips.org>
parent 3a713660
...@@ -414,6 +414,7 @@ int phys_mem_access_prot_allowed(struct file *file, unsigned long pfn, ...@@ -414,6 +414,7 @@ int phys_mem_access_prot_allowed(struct file *file, unsigned long pfn,
* constraints placed on us by the cache architecture. * constraints placed on us by the cache architecture.
*/ */
#define HAVE_ARCH_UNMAPPED_AREA #define HAVE_ARCH_UNMAPPED_AREA
#define HAVE_ARCH_UNMAPPED_AREA_TOPDOWN
/* /*
* No page table caches to initialise * No page table caches to initialise
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#include <linux/mm.h> #include <linux/mm.h>
#include <linux/mman.h> #include <linux/mman.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/personality.h>
#include <linux/random.h> #include <linux/random.h>
#include <linux/sched.h> #include <linux/sched.h>
...@@ -17,21 +18,65 @@ unsigned long shm_align_mask = PAGE_SIZE - 1; /* Sane caches */ ...@@ -17,21 +18,65 @@ unsigned long shm_align_mask = PAGE_SIZE - 1; /* Sane caches */
EXPORT_SYMBOL(shm_align_mask); 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) \ #define COLOUR_ALIGN(addr,pgoff) \
((((addr) + shm_align_mask) & ~shm_align_mask) + \ ((((addr) + shm_align_mask) & ~shm_align_mask) + \
(((pgoff) << PAGE_SHIFT) & shm_align_mask)) (((pgoff) << PAGE_SHIFT) & shm_align_mask))
unsigned long arch_get_unmapped_area(struct file *filp, unsigned long addr, enum mmap_allocation_direction {UP, DOWN};
unsigned long len, unsigned long pgoff, unsigned long flags)
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; int do_color_align;
if (len > TASK_SIZE) if (unlikely(len > TASK_SIZE))
return -ENOMEM; return -ENOMEM;
if (flags & MAP_FIXED) { 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) if (TASK_SIZE - len < addr)
return -EINVAL; return -EINVAL;
...@@ -48,34 +93,130 @@ unsigned long arch_get_unmapped_area(struct file *filp, unsigned long addr, ...@@ -48,34 +93,130 @@ unsigned long arch_get_unmapped_area(struct file *filp, unsigned long addr,
do_color_align = 0; do_color_align = 0;
if (filp || (flags & MAP_SHARED)) if (filp || (flags & MAP_SHARED))
do_color_align = 1; do_color_align = 1;
/* requesting a specific address */
if (addr) { if (addr) {
if (do_color_align) if (do_color_align)
addr = COLOUR_ALIGN(addr, pgoff); addr = COLOUR_ALIGN(addr, pgoff);
else else
addr = PAGE_ALIGN(addr); addr = PAGE_ALIGN(addr);
vmm = find_vma(current->mm, addr);
vma = find_vma(mm, addr);
if (TASK_SIZE - len >= addr && if (TASK_SIZE - len >= addr &&
(!vmm || addr + len <= vmm->vm_start)) (!vma || addr + len <= vma->vm_start))
return addr; 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) { if (dir == UP) {
/* At this point: (!vmm || addr < vmm->vm_end). */ addr = mm->mmap_base;
if (TASK_SIZE - len < addr) if (do_color_align)
return -ENOMEM; addr = COLOUR_ALIGN(addr, pgoff);
if (!vmm || addr + len <= vmm->vm_start) else
return addr; addr = PAGE_ALIGN(addr);
addr = vmm->vm_end;
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) 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) void arch_pick_mmap_layout(struct mm_struct *mm)
{ {
unsigned long random_factor = 0UL; unsigned long random_factor = 0UL;
...@@ -89,9 +230,15 @@ void arch_pick_mmap_layout(struct mm_struct *mm) ...@@ -89,9 +230,15 @@ void arch_pick_mmap_layout(struct mm_struct *mm)
random_factor &= 0xffffffful; random_factor &= 0xffffffful;
} }
mm->mmap_base = TASK_UNMAPPED_BASE + random_factor; if (mmap_is_legacy()) {
mm->get_unmapped_area = arch_get_unmapped_area; mm->mmap_base = TASK_UNMAPPED_BASE + random_factor;
mm->unmap_area = arch_unmap_area; 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) static inline unsigned long brk_rnd(void)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment