进程地址空间[3]

   内核常常要判断进程地址空间中的内存区域是否满足某些条件,为了方便执行,内核定义了许多辅助函数,它们都声明在linux/mm.h中。 

  • find_vma()

    在mm/mmap.c中

 

  1. /* Look up the first VMA which satisfies  addr < vm_end,  NULL if none. */
  2. struct vm_area_struct * find_vma(struct mm_struct * mm, unsigned long addr)
  3. {
  4.     struct vm_area_struct *vma = NULL;
  5.     if (mm) {
  6.         /* Check the cache first. */
  7.         /* (Cache hit rate is typically around 35%.) */
  8.         vma = mm->mmap_cache;
  9. //如果缓存中并未包含希望的VMA,则搜索红黑树
  10.         if (!(vma && vma->vm_end > addr && vma->vm_start <= addr)) {
  11.             struct rb_node * rb_node;
  12.             rb_node = mm->mm_rb.rb_node;
  13.             vma = NULL;
  14.             while (rb_node) {
  15.                 struct vm_area_struct * vma_tmp;
  16.                 vma_tmp = rb_entry(rb_node,
  17.                         struct vm_area_struct, vm_rb);
  18.                 if (vma_tmp->vm_end > addr) {
  19.                     vma = vma_tmp;
  20.                     if (vma_tmp->vm_start <= addr)
  21.                         break;
  22.                     rb_node = rb_node->rb_left;
  23.                 } else
  24.                     rb_node = rb_node->rb_right;
  25.             }
  26.             if (vma)
  27.                 mm->mmap_cache = vma;
  28.         }
  29.     }
  30.     return vma;  //返回第一个大于addr的VMA
  31. }

    该函数在指定的地址空间中搜索第一个vm_end大于addr的内存区域。换句话说,该函数寻找第一个包含addr或首地址大于addr的内存区域。

   注意,由于返回的VMA首地址可能大于addr,所以指定的地址并不一定就包含在返回的VMA中。因为肯那个在对某个VMA执行操作之后,还有其他操作会对该VMA继续进行,所以该函数返回的结果被缓存在内存描述符mm_struct中的mmap_cache域中。如果指定的地址不在缓存中,那么必须搜索和内存描述符相关的所有内存区域,这种搜索通过红黑树进行。

find_vma_prev()

   find_vma_prev()函数和find_vma()工作方式相同,但是它返回第一个小于addr的VMA。

 

  1. /* Same as find_vma, but also return a pointer to the previous VMA in *pprev. */
  2. struct vm_area_struct *
  3. find_vma_prev(struct mm_struct *mm, unsigned long addr,
  4.             struct vm_area_struct **pprev)
  5. {
  6.     struct vm_area_struct *vma = NULL, *prev = NULL;
  7.     struct rb_node * rb_node;
  8.     if (!mm)
  9.         goto out;
  10.     /* Guard against addr being lower than the first VMA */
  11.     vma = mm->mmap;
  12.     /* Go through the RB tree quickly. */
  13.     rb_node = mm->mm_rb.rb_node;
  14.     while (rb_node) {
  15.         struct vm_area_struct *vma_tmp;
  16.         vma_tmp = rb_entry(rb_node, struct vm_area_struct, vm_rb);
  17.         if (addr < vma_tmp->vm_end) {
  18.             rb_node = rb_node->rb_left;
  19.         } else {
  20.             prev = vma_tmp;
  21.             if (!prev->vm_next || (addr < prev->vm_next->vm_end))
  22.                 break;
  23.             rb_node = rb_node->rb_right;
  24.         }
  25.     }
  26. out:
  27.     *pprev = prev;
  28.     return prev ? prev->vm_next : vma;
  29. }
  • find_vma_intersection()

   find_vma_intersection()函数返回第一个和指定区间相交的VMA。因为该函数是内联函数,故定义在linux/mm.h中:

  1. /* Look up the first VMA which intersects the interval start_addr..end_addr-1,
  2.    NULL if none.  Assume start_addr < end_addr. */
  3. static inline struct vm_area_struct * find_vma_intersection(struct mm_struct * mm, unsigned long start_addr, unsigned long end_addr)
  4. {
  5.     struct vm_area_struct * vma = find_vma(mm,start_addr);
  6.     if (vma && end_addr <= vma->vm_start)
  7.         vma = NULL;
  8.     return vma;
  9. }
  • mmap()和do_mmap():创建地址空间

   内核使用do_mmap()函数创建一个新的线性地址空间。如果说该函数创建了一个新的VMA并不非常准确,因为如果创建的地址区间和益而高已经存在的地址空间相邻,并且它们具有同样的访问权限的话,那么两个区间将合并为一个。如果不能合并,那么就确实要创建一个新的VMA了。无论哪种情况,do_mmap()都会将一个地址区间加入到进程的地址空间中(无论是扩展已存在的内存空间还是创建一个新的区域)。

  1. 在mm.h中
  2. static inline unsigned long do_mmap(struct file *file, unsigned long addr,
  3.     unsigned long len, unsigned long prot,
  4.     unsigned long flag, unsigned long offset)
  5. {
  6.     unsigned long ret = -EINVAL;
  7.     if ((offset + PAGE_ALIGN(len)) < offset)
  8.         goto out;
  9.     if (!(offset & ~PAGE_MASK))
  10.         ret = do_mmap_pgoff(file, addr, len, prot, flag, offset >> PAGE_SHIFT);
  11. out:
  12.     return ret;
  13. }

 

   如果file参数是NULL并且offset参数也是0,那么就代表这次映射没有和文件相关,该情况被称作匿名映射;如果指定了文件名和偏移量,那么该映射被称为文件映射。

   addr是可选参数,它指定搜索空闲区域的起始位置。

   prot参数指定内存区域中页面的访问权限,不同的体系结构标志的定义不同。  flag参数指定了VMA标志。

  1. /*
  2.  Author: Michael S. Tsirkin <mst@mellanox.co.il>, Mellanox Technologies Ltd.
  3.  Based on: asm-xxx/mman.h
  4. */
  5. #define PROT_READ   0x1     /* page can be read */
  6. #define PROT_WRITE  0x2     /* page can be written */
  7. #define PROT_EXEC   0x4     /* page can be executed */
  8. #define PROT_SEM    0x8     /* page may be used for atomic ops */
  9. #define PROT_NONE   0x0     /* page can not be accessed */
  10. #define PROT_GROWSDOWN  0x01000000  /* mprotect flag: extend change to start of growsdown vma */
  11. #define PROT_GROWSUP    0x02000000  /* mprotect flag: extend change to end of growsup vma */
  12. #define MAP_SHARED  0x01        /* Share changes */
  13. #define MAP_PRIVATE 0x02        /* Changes are private */
  14. #define MAP_TYPE    0x0f        /* Mask for type of mapping */
  15. #define MAP_FIXED   0x10        /* Interpret addr exactly */
  16. #define MAP_ANONYMOUS   0x20        /* don't use a file */
  1. 在mm/mmap.c中
  2. /*
  3.  * The caller must hold down_write(current->mm->mmap_sem).
  4.  */
  5. unsigned long do_mmap_pgoff(struct file * file, unsigned long addr,
  6.             unsigned long len, unsigned long prot,
  7.             unsigned long flags, unsigned long pgoff)
  8. {
  9.     struct mm_struct * mm = current->mm;
  10.     struct vm_area_struct * vma, * prev;
  11.     struct inode *inode;
  12.     unsigned int vm_flags;
  13.     int correct_wcount = 0;
  14.     int error;
  15.     struct rb_node ** rb_link, * rb_parent;
  16.     int accountable = 1;
  17.     unsigned long charged = 0, reqprot = prot;
  18.     /*
  19.      * Does the application expect PROT_READ to imply PROT_EXEC?
  20.      *
  21.      * (the exception is when the underlying filesystem is noexec
  22.      *  mounted, in which case we dont add PROT_EXEC.)
  23.      */
  24.     if ((prot & PROT_READ) && (current->personality & READ_IMPLIES_EXEC))
  25.         if (!(file && (file->f_path.mnt->mnt_flags & MNT_NOEXEC)))
  26.             prot |= PROT_EXEC;
  27.     if (!len)
  28.         return -EINVAL;
  29.     error = arch_mmap_check(addr, len, flags);
  30.     if (error)
  31.         return error;
  32.     /* Careful about overflows.. */
  33.     len = PAGE_ALIGN(len);
  34.     if (!len || len > TASK_SIZE)
  35.         return -ENOMEM;
  36.     /* offset overflow? */
  37.     if ((pgoff + (len >> PAGE_SHIFT)) < pgoff)
  38.                return -EOVERFLOW;
  39.     /* Too many mappings? */
  40.     if (mm->map_count > sysctl_max_map_count)
  41.         return -ENOMEM;
  42.     /* Obtain the address to map to. we verify (or select) it and ensure
  43.      * that it represents a valid section of the address space.
  44.      */
  45.     addr = get_unmapped_area(file, addr, len, pgoff, flags);
  46.     if (addr & ~PAGE_MASK)
  47.         return addr;
  48.     /* Do simple checking here so the lower-level routines won't have
  49.      * to. we assume access permissions have been handled by the open
  50.      * of the memory object, so we don't do any here.
  51.      */
  52.     vm_flags = calc_vm_prot_bits(prot) | calc_vm_flag_bits(flags) |
  53.             mm->def_flags | VM_MAYREAD | VM_MAYWRITE | VM_MAYEXEC;
  54.     if (flags & MAP_LOCKED) {
  55.         if (!can_do_mlock())
  56.             return -EPERM;
  57.         vm_flags |= VM_LOCKED;
  58.     }
  59.     /* mlock MCL_FUTURE? */
  60.     if (vm_flags & VM_LOCKED) {
  61.         unsigned long locked, lock_limit;
  62.         locked = len >> PAGE_SHIFT;
  63.         locked += mm->locked_vm;
  64.         lock_limit = current->signal->rlim[RLIMIT_MEMLOCK].rlim_cur;
  65.         lock_limit >>= PAGE_SHIFT;
  66.         if (locked > lock_limit && !capable(CAP_IPC_LOCK))
  67.             return -EAGAIN;
  68.     }
  69.     inode = file ? file->f_path.dentry->d_inode : NULL;
  70.     if (file) {
  71.         switch (flags & MAP_TYPE) {
  72.         case MAP_SHARED:
  73.             if ((prot&PROT_WRITE) && !(file->f_mode&FMODE_WRITE))
  74.                 return -EACCES;
  75.             /*
  76.              * Make sure we don't allow writing to an append-only
  77.              * file..
  78.              */
  79.             if (IS_APPEND(inode) && (file->f_mode & FMODE_WRITE))
  80.                 return -EACCES;
  81.             /*
  82.              * Make sure there are no mandatory locks on the file.
  83.              */
  84.             if (locks_verify_locked(inode))
  85.                 return -EAGAIN;
  86.             vm_flags |= VM_SHARED | VM_MAYSHARE;
  87.             if (!(file->f_mode & FMODE_WRITE))
  88.                 vm_flags &= ~(VM_MAYWRITE | VM_SHARED);
  89.             /* fall through */
  90.         case MAP_PRIVATE:
  91.             if (!(file->f_mode & FMODE_READ))
  92.                 return -EACCES;
  93.             if (file->f_path.mnt->mnt_flags & MNT_NOEXEC) {
  94.                 if (vm_flags & VM_EXEC)
  95.                     return -EPERM;
  96.                 vm_flags &= ~VM_MAYEXEC;
  97.             }
  98.             if (is_file_hugepages(file))
  99.                 accountable = 0;
  100.             if (!file->f_op || !file->f_op->mmap)
  101.                 return -ENODEV;
  102.             break;
  103.         default:
  104.             return -EINVAL;
  105.         }
  106.     } else {
  107.         switch (flags & MAP_TYPE) {
  108.         case MAP_SHARED:
  109.             vm_flags |= VM_SHARED | VM_MAYSHARE;
  110.             break;
  111.         case MAP_PRIVATE:
  112.             /*
  113.              * Set pgoff according to addr for anon_vma.
  114.              */
  115.             pgoff = addr >> PAGE_SHIFT;
  116.             break;
  117.         default:
  118.             return -EINVAL;
  119.         }
  120.     }
  121.     error = security_file_mmap(file, reqprot, prot, flags);
  122.     if (error)
  123.         return error;
  124.         
  125.     /* Clear old maps */
  126.     error = -ENOMEM;
  127. munmap_back:
  128.     vma = find_vma_prepare(mm, addr, &prev, &rb_link, &rb_parent);
  129.     if (vma && vma->vm_start < addr + len) {
  130.         if (do_munmap(mm, addr, len))
  131.             return -ENOMEM;
  132.         goto munmap_back;
  133.     }
  134.     /* Check against address space limit. */
  135.     if (!may_expand_vm(mm, len >> PAGE_SHIFT))
  136.         return -ENOMEM;
  137.     if (accountable && (!(flags & MAP_NORESERVE) ||
  138.                 sysctl_overcommit_memory == OVERCOMMIT_NEVER)) {
  139.         if (vm_flags & VM_SHARED) {
  140.             /* Check memory availability in shmem_file_setup? */
  141.             vm_flags |= VM_ACCOUNT;
  142.         } else if (vm_flags & VM_WRITE) {
  143.             /*
  144.              * Private writable mapping: check memory availability
  145.              */
  146.             charged = len >> PAGE_SHIFT;
  147.             if (security_vm_enough_memory(charged))
  148.                 return -ENOMEM;
  149.             vm_flags |= VM_ACCOUNT;
  150.         }
  151.     }
  152.     /*
  153.      * Can we just expand an old private anonymous mapping?
  154.      * The VM_SHARED test is necessary because shmem_zero_setup
  155.      * will create the file object for a shared anonymous map below.
  156.      */
  157.     if (!file && !(vm_flags & VM_SHARED) &
  158.         vma_merge(mm, prev, addr, addr + len, vm_flags,
  159.                     NULL, NULL, pgoff, NULL))
  160.         goto out;
  161.     /*
  162.      * Determine the object being mapped and call the appropriate
  163.      * specific mapper. the address has already been validated, but
  164.      * not unmapped, but the maps are removed from the list.
  165.      */
  166.     vma = kmem_cache_zalloc(vm_area_cachep, GFP_KERNEL);
  167.     if (!vma) {
  168.         error = -ENOMEM;
  169.         goto unacct_error;
  170.     }
  171.     vma->vm_mm = mm;
  172.     vma->vm_start = addr;
  173.     vma->vm_end = addr + len;
  174.     vma->vm_flags = vm_flags;
  175.     vma->vm_page_prot = protection_map[vm_flags 
  176.                 (VM_READ|VM_WRITE|VM_EXEC|VM_SHARED)];
  177.     vma->vm_pgoff = pgoff;
  178.     if (file) {
  179.         error = -EINVAL;
  180.         if (vm_flags & (VM_GROWSDOWN|VM_GROWSUP))
  181.             goto free_vma;
  182.         if (vm_flags & VM_DENYWRITE) {
  183.             error = deny_write_access(file);
  184.             if (error)
  185.                 goto free_vma;
  186.             correct_wcount = 1;
  187.         }
  188.         vma->vm_file = file;
  189.         get_file(file);
  190.         error = file->f_op->mmap(file, vma);
  191.         if (error)
  192.             goto unmap_and_free_vma;
  193.     } else if (vm_flags & VM_SHARED) {
  194.         error = shmem_zero_setup(vma);
  195.         if (error)
  196.             goto free_vma;
  197.     }
  198.     /* We set VM_ACCOUNT in a shared mapping's vm_flags, to inform
  199.      * shmem_zero_setup (perhaps called through /dev/zero's ->mmap)
  200.      * that memory reservation must be checked; but that reservation
  201.      * belongs to shared memory object, not to vma: so now clear it.
  202.      */
  203.     if ((vm_flags & (VM_SHARED|VM_ACCOUNT)) == (VM_SHARED|VM_ACCOUNT))
  204.         vma->vm_flags &= ~VM_ACCOUNT;
  205.     /* Can addr have changed??
  206.      *
  207.      * Answer: Yes, several device drivers can do it in their
  208.      *         f_op->mmap method. -DaveM
  209.      */
  210.     addr = vma->vm_start;
  211.     pgoff = vma->vm_pgoff;
  212.     vm_flags = vma->vm_flags;
  213.     if (vma_wants_writenotify(vma))
  214.         vma->vm_page_prot =
  215.             protection_map[vm_flags & (VM_READ|VM_WRITE|VM_EXEC)];
  216.     if (!file || !vma_merge(mm, prev, addr, vma->vm_end,
  217.             vma->vm_flags, NULL, file, pgoff, vma_policy(vma))) {
  218.         file = vma->vm_file;
  219.         vma_link(mm, vma, prev, rb_link, rb_parent);
  220.         if (correct_wcount)
  221.             atomic_inc(&inode->i_writecount);
  222.     } else {
  223.         if (file) {
  224.             if (correct_wcount)
  225.                 atomic_inc(&inode->i_writecount);
  226.             fput(file);
  227.         }
  228.         mpol_free(vma_policy(vma));
  229.         kmem_cache_free(vm_area_cachep, vma);
  230.     }
  231. out:    
  232.     mm->total_vm += len >> PAGE_SHIFT;
  233.     vm_stat_account(mm, vm_flags, file, len >> PAGE_SHIFT);
  234.     if (vm_flags & VM_LOCKED) {
  235.         mm->locked_vm += len >> PAGE_SHIFT;
  236.         make_pages_present(addr, addr + len);
  237.     }
  238.     if (flags & MAP_POPULATE) {
  239.         up_write(&mm->mmap_sem);
  240.         sys_remap_file_pages(addr, len, 0,
  241.                     pgoff, flags & MAP_NONBLOCK);
  242.         down_write(&mm->mmap_sem);
  243.     }
  244.     return addr;
  245. unmap_and_free_vma:
  246.     if (correct_wcount)
  247.         atomic_inc(&inode->i_writecount);
  248.     vma->vm_file = NULL;
  249.     fput(file);
  250.     /* Undo any partial mapping done by a device driver. */
  251.     unmap_region(mm, vma, prev, vma->vm_start, vma->vm_end);
  252.     charged = 0;
  253. free_vma:
  254.     kmem_cache_free(vm_area_cachep, vma);
  255. unacct_error:
  256.     if (charged)
  257.         vm_unacct_memory(charged);
  258.     return error;
  259. }

mmap()系统调用

  在用户空间可以通过mmap()系统调用获取内核函数do_mmap()的功能。mmap()系统调用定义如下:

  1. void *mmap2(void *start,size_t length,int prot,int flags,int fd,off_t pgoff)

  该系统调用是mmap()的变种所以起名为mmap2()。最原始的mmap()调用中最后一个参数是字节偏移量,而且目前这个变种使用页面偏移量最最后一个参数。使用页面偏移量可以映射更大的文件和更大的偏移位置。原始的mmap()调用由POSIX定义,仍然在C库中作为mmap()方法被使用。虽然C库仍然可以使用原始版本的映射方法,但是它其实还是基于函数mmap2()进行的,对原始mmap()方法的调用是通过将字节偏移转化为页面偏移,从而转化为对mmap2()函数的调用。

  • munmap()和do_munmap():删除地址空间

   do_munmap()函数从特定的进程地址空间中删除指定地址区间。


  1. /* Munmap is split into 2 main parts -- this part which finds
  2.  * what needs doing, and the areas themselves, which do the
  3.  * work.  This now handles partial unmappings.
  4.  * Jeremy Fitzhardinge <jeremy@goop.org>
  5.  */
  6. int do_munmap(struct mm_struct *mm, unsigned long start, size_t len)
  7. {
  8.     unsigned long end;
  9.     struct vm_area_struct *vma, *prev, *last;

  10.     if ((start & ~PAGE_MASK) || start > TASK_SIZE || len > TASK_SIZE-start)
  11.         return -EINVAL;

  12.     if ((len = PAGE_ALIGN(len)) == 0)
  13.         return -EINVAL;

  14.     /* Find the first overlapping VMA */
  15.     vma = find_vma_prev(mm, start, &prev);
  16.     if (!vma)
  17.         return 0;
  18.     /* we have  start < vma->vm_end  */

  19.     /* if it doesn't overlap, we have nothing.. */
  20.     end = start + len;
  21.     if (vma->vm_start >= end)
  22.         return 0;

  23.     /*
  24.      * If we need to split any vma, do it now to save pain later.
  25.      *
  26.      * Note: mremap's move_vma VM_ACCOUNT handling assumes a partially
  27.      * unmapped vm_area_struct will remain in use: so lower split_vma
  28.      * places tmp vma above, and higher split_vma places tmp vma below.
  29.      */
  30.     if (start > vma->vm_start) {
  31.         int error = split_vma(mm, vma, start, 0);
  32.         if (error)
  33.             return error;
  34.         prev = vma;
  35.     }

  36.     /* Does it split the last one? */
  37.     last = find_vma(mm, end);
  38.     if (last && end > last->vm_start) {
  39.         int error = split_vma(mm, last, end, 1);
  40.         if (error)
  41.             return error;
  42.     }
  43.     vma = prev? prev->vm_next: mm->mmap;

  44.     /*
  45.      * Remove the vma's, and unmap the actual pages
  46.      */
  47.     detach_vmas_to_be_unmapped(mm, vma, prev, end);
  48.     unmap_region(mm, vma, prev, start, end);

  49.     /* Fix up all other VM information */
  50.     remove_vma_list(mm, vma);

  51.     return 0;
  52. }

munmap()系统调用

   系统调用munmap()给用户空间程序提供了一种从自身地址空间中删除指定地址区间的方法,它的定义如下:

  1. int munmap(void *start,size_t length)

   该系统调用定义在文件mm/mmap.c中,它是对do_munmap()函数的一个简单的封装:

  1. asmlinkage long sys_munmap(unsigned long addr, size_t len)
  2. {
  3.     int ret;
  4.     struct mm_struct *mm = current->mm;
  5.     profile_munmap(addr);
  6.     down_write(&mm->mmap_sem);
  7.     ret = do_munmap(mm, addr, len);
  8.     up_write(&mm->mmap_sem);
  9.     return ret;
  10. }


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值