RHEL4/mm/mremap.c
<<
>>
Prefs
   1/*
   2 *      mm/mremap.c
   3 *
   4 *      (C) Copyright 1996 Linus Torvalds
   5 *
   6 *      Address space accounting code   <alan@redhat.com>
   7 *      (C) Copyright 2002 Red Hat Inc, All Rights Reserved
   8 */
   9
  10#include <linux/mm.h>
  11#include <linux/hugetlb.h>
  12#include <linux/slab.h>
  13#include <linux/shm.h>
  14#include <linux/mman.h>
  15#include <linux/swap.h>
  16#include <linux/fs.h>
  17#include <linux/highmem.h>
  18#include <linux/security.h>
  19
  20#include <asm/uaccess.h>
  21#include <asm/cacheflush.h>
  22#include <asm/tlbflush.h>
  23
  24static pte_t *get_one_pte_map_nested(struct mm_struct *mm, unsigned long addr)
  25{
  26        pgd_t *pgd;
  27        pmd_t *pmd;
  28        pte_t *pte = NULL;
  29
  30        pgd = pgd_offset(mm, addr);
  31        if (pgd_none(*pgd))
  32                goto end;
  33        if (pgd_bad(*pgd)) {
  34                pgd_ERROR(*pgd);
  35                pgd_clear(pgd);
  36                goto end;
  37        }
  38
  39        pmd = pmd_offset(pgd, addr);
  40        if (pmd_none(*pmd))
  41                goto end;
  42        if (pmd_bad(*pmd)) {
  43                pmd_ERROR(*pmd);
  44                pmd_clear(pmd);
  45                goto end;
  46        }
  47
  48        pte = pte_offset_map_nested(pmd, addr);
  49        if (pte_none(*pte)) {
  50                pte_unmap_nested(pte);
  51                pte = NULL;
  52        }
  53end:
  54        return pte;
  55}
  56
  57static pte_t *get_one_pte_map(struct mm_struct *mm, unsigned long addr)
  58{
  59        pgd_t *pgd;
  60        pmd_t *pmd;
  61
  62        pgd = pgd_offset(mm, addr);
  63        if (pgd_none(*pgd))
  64                return NULL;
  65        pmd = pmd_offset(pgd, addr);
  66        if (!pmd_present(*pmd))
  67                return NULL;
  68        return pte_offset_map(pmd, addr);
  69}
  70
  71static inline pte_t *alloc_one_pte_map(struct mm_struct *mm, unsigned long addr)
  72{
  73        pmd_t *pmd;
  74        pte_t *pte = NULL;
  75
  76        pmd = pmd_alloc(mm, pgd_offset(mm, addr), addr);
  77        if (pmd)
  78                pte = pte_alloc_map(mm, pmd, addr);
  79        return pte;
  80}
  81
  82static int
  83move_one_page(struct vm_area_struct *vma, unsigned long old_addr,
  84                unsigned long new_addr)
  85{
  86        struct address_space *mapping = NULL;
  87        struct mm_struct *mm = vma->vm_mm;
  88        int error = 0;
  89        pte_t *src, *dst;
  90
  91        if (vma->vm_file) {
  92                /*
  93                 * Subtle point from Rajesh Venkatasubramanian: before
  94                 * moving file-based ptes, we must lock vmtruncate out,
  95                 * since it might clean the dst vma before the src vma,
  96                 * and we propagate stale pages into the dst afterward.
  97                 */
  98                mapping = vma->vm_file->f_mapping;
  99                spin_lock(&mapping->i_mmap_lock);
 100        }
 101        spin_lock(&mm->page_table_lock);
 102
 103        src = get_one_pte_map_nested(mm, old_addr);
 104        if (src) {
 105                /*
 106                 * Look to see whether alloc_one_pte_map needs to perform a
 107                 * memory allocation.  If it does then we need to drop the
 108                 * atomic kmap
 109                 */
 110                dst = get_one_pte_map(mm, new_addr);
 111                if (unlikely(!dst)) {
 112                        pte_unmap_nested(src);
 113                        if (mapping)
 114                                spin_unlock(&mapping->i_mmap_lock);
 115                        dst = alloc_one_pte_map(mm, new_addr);
 116                        if (mapping && !spin_trylock(&mapping->i_mmap_lock)) {
 117                                spin_unlock(&mm->page_table_lock);
 118                                spin_lock(&mapping->i_mmap_lock);
 119                                spin_lock(&mm->page_table_lock);
 120                        }
 121                        src = get_one_pte_map_nested(mm, old_addr);
 122                }
 123                /*
 124                 * Since alloc_one_pte_map can drop and re-acquire
 125                 * page_table_lock, we should re-check the src entry...
 126                 */
 127                if (src) {
 128                        if (dst) {
 129                                pte_t pte;
 130                                pte = ptep_clear_flush(vma, old_addr, src);
 131                                set_pte(dst, pte);
 132                        } else
 133                                error = -ENOMEM;
 134                        pte_unmap_nested(src);
 135                }
 136                if (dst)
 137                        pte_unmap(dst);
 138        }
 139        spin_unlock(&mm->page_table_lock);
 140        if (mapping)
 141                spin_unlock(&mapping->i_mmap_lock);
 142        return error;
 143}
 144
 145static unsigned long move_page_tables(struct vm_area_struct *vma,
 146                unsigned long new_addr, unsigned long old_addr,
 147                unsigned long len)
 148{
 149        unsigned long offset;
 150
 151        flush_cache_range(vma, old_addr, old_addr + len);
 152
 153        /*
 154         * This is not the clever way to do this, but we're taking the
 155         * easy way out on the assumption that most remappings will be
 156         * only a few pages.. This also makes error recovery easier.
 157         */
 158        for (offset = 0; offset < len; offset += PAGE_SIZE) {
 159                if (move_one_page(vma, old_addr+offset, new_addr+offset) < 0)
 160                        break;
 161                cond_resched();
 162        }
 163        return offset;
 164}
 165
 166static unsigned long move_vma(struct vm_area_struct *vma,
 167                unsigned long old_addr, unsigned long old_len,
 168                unsigned long new_len, unsigned long new_addr)
 169{
 170        struct mm_struct *mm = vma->vm_mm;
 171        struct vm_area_struct *new_vma;
 172        unsigned long vm_flags = vma->vm_flags;
 173        unsigned long new_pgoff;
 174        unsigned long moved_len;
 175        unsigned long excess = 0;
 176        int split = 0;
 177
 178        /*
 179         * We'd prefer to avoid failure later on in do_munmap:
 180         * which may split one vma into three before unmapping.
 181         */
 182        if (mm->map_count >= sysctl_max_map_count - 3)
 183                return -ENOMEM;
 184
 185        new_pgoff = vma->vm_pgoff + ((old_addr - vma->vm_start) >> PAGE_SHIFT);
 186        new_vma = copy_vma(&vma, new_addr, new_len, new_pgoff);
 187        if (!new_vma)
 188                return -ENOMEM;
 189
 190        moved_len = move_page_tables(vma, new_addr, old_addr, old_len);
 191        if (moved_len < old_len) {
 192                /*
 193                 * On error, move entries back from new area to old,
 194                 * which will succeed since page tables still there,
 195                 * and then proceed to unmap new area instead of old.
 196                 */
 197                move_page_tables(new_vma, old_addr, new_addr, moved_len);
 198                vma = new_vma;
 199                old_len = new_len;
 200                old_addr = new_addr;
 201                new_addr = -ENOMEM;
 202        }
 203
 204        /* Conceal VM_ACCOUNT so old reservation is not undone */
 205        if (vm_flags & VM_ACCOUNT) {
 206                vma->vm_flags &= ~VM_ACCOUNT;
 207                excess = vma->vm_end - vma->vm_start - old_len;
 208                if (old_addr > vma->vm_start &&
 209                    old_addr + old_len < vma->vm_end)
 210                        split = 1;
 211        }
 212
 213        if (do_munmap(mm, old_addr, old_len) < 0) {
 214                /* OOM: unable to split vma, just get accounts right */
 215                vm_unacct_memory(excess >> PAGE_SHIFT);
 216                excess = 0;
 217        }
 218
 219        /* Restore VM_ACCOUNT if one or two pieces of vma left */
 220        if (excess) {
 221                vma->vm_flags |= VM_ACCOUNT;
 222                if (split)
 223                        vma->vm_next->vm_flags |= VM_ACCOUNT;
 224        }
 225
 226        mm->total_vm += new_len >> PAGE_SHIFT;
 227        __vm_stat_account(mm, vma->vm_flags, vma->vm_file, new_len>>PAGE_SHIFT);
 228        if (vm_flags & VM_LOCKED) {
 229                mm->locked_vm += new_len >> PAGE_SHIFT;
 230                if (new_len > old_len)
 231                        make_pages_present(new_addr + old_len,
 232                                           new_addr + new_len);
 233        }
 234
 235        return new_addr;
 236}
 237
 238/*
 239 * Expand (or shrink) an existing mapping, potentially moving it at the
 240 * same time (controlled by the MREMAP_MAYMOVE flag and available VM space)
 241 *
 242 * MREMAP_FIXED option added 5-Dec-1999 by Benjamin LaHaise
 243 * This option implies MREMAP_MAYMOVE.
 244 */
 245unsigned long do_mremap(unsigned long addr,
 246        unsigned long old_len, unsigned long new_len,
 247        unsigned long flags, unsigned long new_addr)
 248{
 249        struct vm_area_struct *vma;
 250        unsigned long ret = -EINVAL;
 251        unsigned long charged = 0;
 252
 253        if (flags & ~(MREMAP_FIXED | MREMAP_MAYMOVE))
 254                goto out;
 255
 256        if (addr & ~PAGE_MASK)
 257                goto out;
 258
 259        old_len = PAGE_ALIGN(old_len);
 260        new_len = PAGE_ALIGN(new_len);
 261
 262        /*
 263         * We allow a zero old-len as a special case
 264         * for DOS-emu "duplicate shm area" thing. But
 265         * a zero new-len is nonsensical.
 266         */
 267        if (!new_len)
 268                goto out;
 269
 270        /* new_addr is only valid if MREMAP_FIXED is specified */
 271        if (flags & MREMAP_FIXED) {
 272                if (new_addr & ~PAGE_MASK)
 273                        goto out;
 274                if (!(flags & MREMAP_MAYMOVE))
 275                        goto out;
 276
 277                if (new_len > TASK_SIZE || new_addr > TASK_SIZE - new_len)
 278                        goto out;
 279
 280                /* Check if the location we're moving into overlaps the
 281                 * old location at all, and fail if it does.
 282                 */
 283                if ((new_addr <= addr) && (new_addr+new_len) > addr)
 284                        goto out;
 285
 286                if ((addr <= new_addr) && (addr+old_len) > new_addr)
 287                        goto out;
 288
 289                ret = do_munmap(current->mm, new_addr, new_len);
 290                if (ret)
 291                        goto out;
 292        }
 293
 294        /*
 295         * Always allow a shrinking remap: that just unmaps
 296         * the unnecessary pages..
 297         * do_munmap does all the needed commit accounting
 298         */
 299        if (old_len >= new_len) {
 300                ret = do_munmap(current->mm, addr+new_len, old_len - new_len);
 301                if (ret && old_len != new_len)
 302                        goto out;
 303                ret = addr;
 304                if (!(flags & MREMAP_FIXED) || (new_addr == addr))
 305                        goto out;
 306                old_len = new_len;
 307        }
 308
 309        /*
 310         * Ok, we need to grow..  or relocate.
 311         */
 312        ret = -EFAULT;
 313        vma = find_vma(current->mm, addr);
 314        if (!vma || vma->vm_start > addr)
 315                goto out;
 316        if (is_vm_hugetlb_page(vma)) {
 317                ret = -EINVAL;
 318                goto out;
 319        }
 320        /* We can't remap across vm area boundaries */
 321        if (old_len > vma->vm_end - addr)
 322                goto out;
 323        if (vma->vm_flags & VM_DONTEXPAND) {
 324                if (new_len > old_len)
 325                        goto out;
 326        }
 327        if (vma->vm_flags & VM_LOCKED) {
 328                unsigned long locked, lock_limit;
 329                locked = current->mm->locked_vm << PAGE_SHIFT;
 330                lock_limit = current->rlim[RLIMIT_MEMLOCK].rlim_cur;
 331                locked += new_len - old_len;
 332                ret = -EAGAIN;
 333                if (locked > lock_limit && !capable(CAP_IPC_LOCK))
 334                        goto out;
 335        }
 336        ret = -ENOMEM;
 337        if ((current->mm->total_vm << PAGE_SHIFT) + (new_len - old_len)
 338            > current->rlim[RLIMIT_AS].rlim_cur)
 339                goto out;
 340
 341        if (vma->vm_flags & VM_ACCOUNT) {
 342                charged = (new_len - old_len) >> PAGE_SHIFT;
 343                if (security_vm_enough_memory(charged))
 344                        goto out_nc;
 345        }
 346
 347        /* old_len exactly to the end of the area..
 348         * And we're not relocating the area.
 349         */
 350        if (old_len == vma->vm_end - addr &&
 351            !((flags & MREMAP_FIXED) && (addr != new_addr)) &&
 352            (old_len != new_len || !(flags & MREMAP_MAYMOVE))) {
 353                unsigned long max_addr = TASK_SIZE;
 354                if (vma->vm_next)
 355                        max_addr = vma->vm_next->vm_start;
 356                /* can we just expand the current mapping? */
 357                if (max_addr - addr >= new_len) {
 358                        int pages = (new_len - old_len) >> PAGE_SHIFT;
 359
 360                        vma_adjust(vma, vma->vm_start,
 361                                addr + new_len, vma->vm_pgoff, NULL);
 362
 363                        current->mm->total_vm += pages;
 364                        __vm_stat_account(vma->vm_mm, vma->vm_flags,
 365                                                        vma->vm_file, pages);
 366                        if (vma->vm_flags & VM_LOCKED) {
 367                                current->mm->locked_vm += pages;
 368                                make_pages_present(addr + old_len,
 369                                                   addr + new_len);
 370                        }
 371                        ret = addr;
 372                        goto out;
 373                }
 374        }
 375
 376        /*
 377         * We weren't able to just expand or shrink the area,
 378         * we need to create a new one and move it..
 379         */
 380        ret = -ENOMEM;
 381        if (flags & MREMAP_MAYMOVE) {
 382                if (!(flags & MREMAP_FIXED)) {
 383                        unsigned long map_flags = 0;
 384                        if (vma->vm_flags & VM_MAYSHARE)
 385                                map_flags |= MAP_SHARED;
 386
 387                        new_addr = get_unmapped_area_prot(vma->vm_file, 0, new_len, 
 388                                vma->vm_pgoff, map_flags, vma->vm_flags & VM_EXEC);
 389                        ret = new_addr;
 390                        if (new_addr & ~PAGE_MASK)
 391                                goto out;
 392                }
 393                ret = move_vma(vma, addr, old_len, new_len, new_addr);
 394        }
 395out:
 396        if (ret & ~PAGE_MASK)
 397                vm_unacct_memory(charged);
 398out_nc:
 399        return ret;
 400}
 401
 402asmlinkage unsigned long sys_mremap(unsigned long addr,
 403        unsigned long old_len, unsigned long new_len,
 404        unsigned long flags, unsigned long new_addr)
 405{
 406        unsigned long ret;
 407
 408        down_write(&current->mm->mmap_sem);
 409        ret = do_mremap(addr, old_len, new_len, flags, new_addr);
 410        up_write(&current->mm->mmap_sem);
 411        return ret;
 412}
 413