免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1108 | 回复: 0
打印 上一主题 下一主题

linux内存管理之sys_brk实现分析 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2008-06-07 12:46 |只看该作者 |倒序浏览

分析完linux内存管理的基本概念与实现之后,就可以接着分析用户空间与内核空间的交互操作了。Brk系统调用属于那种常用但是“可见度”不高的操作,常用于用户空间堆的管理(请参阅本站的中的malloc机制分析>>一文)。
Brk在用户空间的接口为int brk(void *end_data_segment)。它通过系统调用进入内核空间。在内核的相应接口为sys_brk().
闲言少叙,言归正传。转入相应的代码。同以往一样,linux内核代码版本为2.6.21
//sys_brk:用来扩大或者缩小进程的数据段边界,brk为新的数据段边界).
asmlinkage unsigned long sys_brk(unsigned long brk)
{
     unsigned long rlim, retval;
     unsigned long newbrk, oldbrk;
     struct mm_struct *mm = current->mm;

     down_write(&mm->mmap_sem);
     //参数有效性判断。
//代码段非法访问,
     if (brk end_code)
         goto out;
     //页框对齐
     newbrk = PAGE_ALIGN(brk);
     oldbrk = PAGE_ALIGN(mm->brk);
     //如果新边界与旧边界相等,不用进行空间的伸缩操作,直接赋值即可
     if (oldbrk == newbrk)
         goto set_brk;
//如果新边界比现在的边界要小,那说明要执行收缩操作
     //缩短堆
     if (brk brk) {
         if (!do_munmap(mm, newbrk, oldbrk-newbrk))
              goto set_brk;
         goto out;
     }
//运行到这里的话,说明要执行的是数据段的伸展操作
     //不能超过数据段上限
     rlim = current->rlim[RLIMIT_DATA].rlim_cur;
     if (rlim start_data > rlim)
         goto out;
     /* Check against existing mmap mappings. */
     //伸展空间已经有映射了
     if (find_vma_intersection(mm, oldbrk, newbrk+PAGE_SIZE))
         goto out;

     /* Ok, looks good - let it rip. */
     //执行伸长操作
if (do_brk(oldbrk, newbrk-oldbrk) != oldbrk)
         goto out;
set_brk:
     mm->brk = brk;
out:
     retval = mm->brk;
     up_write(&mm->mmap_sem);
     return retval;
}
Brk系统调用分为两种情况,一种是收缩数据区,一种是伸长操作。我们分为两种情况来分析
二:用户空间的收缩
从上面的代码我们可以看出。用户空间的收缩操作相应的接口是:do_munmap()。代码如下:
int do_munmap(struct mm_struct *mm, unsigned long start, size_t len)
{
     unsigned long end;
     struct vm_area_struct *mpnt, *prev, *last;

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

     if ((len = PAGE_ALIGN(len)) == 0)
         return -EINVAL;
//找到第一个结束地址大于start的VMA。Prev是前一个VMA
     mpnt = find_vma_prev(mm, start, &prev);
     if (!mpnt)
         return 0;
     //在没有定义CONFIG_HUGETLB_PAGE条件下,is_vm_hugetlb_page()为0
     //略过这段代码
     if (is_vm_hugetlb_page(mpnt)) {
         int ret = is_aligned_hugepage_range(start, len);

         if (ret)
              return ret;
     }

     //现在的堆尾点不可能落在空洞里
     //start:新的边界地址。Len:收缩的长度。Start+len即为旧的边界地址。
     //所以 start+len肯定是属于进程的线性地址
     end = start + len;
     if (mpnt->vm_start >= end)
         return 0;

     
     //如果start大于mpnt的起始地址,就会把mpnt一分为二
if (start > mpnt->vm_start) {
         if (split_vma(mm, mpnt, start, 0))
              return -ENOMEM;
         prev = mpnt;
     }

//找到最后的一个vma
     last = find_vma(mm, end);
     //把最后一个线性区一分为二的情况
     if (last && end > last->vm_start) {
         if (split_vma(mm, last, end, 1))
              return -ENOMEM;
     }
     mpnt = prev? prev->vm_next: mm->mmap;

     //将mpnt对的区间vma从进程描述符组中删除
     detach_vmas_to_be_unmapped(mm, mpnt, prev, end);
     spin_lock(&mm->page_table_lock);
     //更新页表项,释放页框
     unmap_region(mm, mpnt, prev, start, end);
     spin_unlock(&mm->page_table_lock);
     //到现在为止,所有要释放的vma都挂在mpnt上。Unmap_vma_list为对要删除的vma链的处理
     unmap_vma_list(mm, mpnt);

     return 0;
}
为了弄清楚收缩的整个过程,有必要详细的分析一下函数所调用的各个子函数。
Split_vma:将一个vma劈为成两个:
//参数含义:
//mm:进程的内存描述符 vma:要劈分的vma addr:为界线地址 new_below:为0时,vma为下一半 为1时,//vma为上一半
int split_vma(struct mm_struct * mm, struct vm_area_struct * vma,
           unsigned long addr, int new_below)
{
     struct mempolicy *pol;
     struct vm_area_struct *new;

     //如果进程的vma总数超过了限制值
     if (mm->map_count >= sysctl_max_map_count)
         return -ENOMEM;
     //新申请一个vma
     new = kmem_cache_alloc(vm_area_cachep, SLAB_KERNEL);
     if (!new)
         return -ENOMEM;
     //将新的vma赋值为旧的vma,使其两者相等
     *new = *vma;
//new_below为1的时候,vma为上一半,对应的new为下一半
     if (new_below)
         new->vm_end = addr;
     else {
              //new_below为0时,vma为下一半,new为上一半
         new->vm_start = addr;
         new->vm_pgoff += ((addr - vma->vm_start) >> PAGE_SHIFT);
     }

     pol = mpol_copy(vma_policy(vma));
     if (IS_ERR(pol)) {
         kmem_cache_free(vm_area_cachep, new);
         return PTR_ERR(pol);
     }
     vma_set_policy(new, pol);

     if (new->vm_file)
         get_file(new->vm_file);
     //如果定义了open操作
     if (new->vm_ops && new->vm_ops->open)
         new->vm_ops->open(new);

     //经过前面的初始化之后,再由vma_adjust调整vma的边界
     if (new_below) {
         unsigned long old_end = vma->vm_end;

         vma_adjust(vma, addr, vma->vm_end, vma->vm_pgoff +
              ((addr - new->vm_start) >> PAGE_SHIFT), new);
         if (vma->vm_flags & VM_EXEC)
              arch_remove_exec_range(mm, old_end);
     } else
         vma_adjust(vma, vma->vm_start, addr, vma->vm_pgoff, new);

     return 0;
}
转入vma_adjust():
void vma_adjust(struct vm_area_struct *vma, unsigned long start,
     unsigned long end, pgoff_t pgoff, struct vm_area_struct *insert)
{
     ……
     //调整vma的起始边界和结束边界
     vma->vm_start = start;
     vma->vm_end = end;
     vma->vm_pgoff = pgoff;
     ……
     //将新的vma,插入到进程的vma链
     __insert_vm_struct(mm, insert);
……
}
第二个要为析的函数是:detach_vmas_to_be_unmapped()
它主要是将要删除的vma链到一起,同时将要删除的vma从mm中脱链
//参数说明:
/*
     Mm:  进程的内存描述符
     Vma:要删除的起始vma
     Prev:vma的前一个vma区
     End:结束地址
*/  
static void
detach_vmas_to_be_unmapped(struct mm_struct *mm, struct vm_area_struct *vma,
     struct vm_area_struct *prev, unsigned long end)
{
     struct vm_area_struct **insertion_point;
     struct vm_area_struct *tail_vma = NULL;

     insertion_point = (prev ? &prev->vm_next : &mm->mmap);
     do {
         //从红黑对中释放掉vma
         rb_erase(&vma->vm_rb, &mm->mm_rb);
         //更新vma计数
         mm->map_count--;
         tail_vma = vma;
         vma = vma->vm_next;
     } while (vma && vma->vm_start
     //将要删除的vma从链表中脱落
     *insertion_point = vma;
     //最后无素后向指针置NULL
     tail_vma->vm_next = NULL;
     //由于进行了删除操作。Mmap_cache失效了,置NULL
     mm->mmap_cache = NULL;      /* Kill the cache. */
}
接下来要分析的调用函数是unmap_vma_list()
它主要对删除的vma链进行处理。具体代码如下示:
//参数说明:
//mm:进程的内存描述符
//mpnt:要删除的链表的头节点
static void unmap_vma_list(struct mm_struct *mm,
     struct vm_area_struct *mpnt)
{
     //遍历链表的每个元素,然后对每一个vma,进行unmap_vma处理
do {
         struct vm_area_struct *next = mpnt->vm_next;
         unmap_vma(mm, mpnt);
         mpnt = next;
     } while (mpnt != NULL);
     //debug 用,忽略
     validate_mm(mm);
}
转向unmap_vma():
static void unmap_vma(struct mm_struct *mm, struct vm_area_struct *area)
{
     size_t len = area->vm_end - area->vm_start;
     //更新mm的total_vm
     area->vm_mm->total_vm -= len >> PAGE_SHIFT;
     if (area->vm_flags & VM_LOCKED)
         area->vm_mm->locked_vm -= len >> PAGE_SHIFT;
     vm_stat_unaccount(area);
     area->vm_mm->unmap_area(area);
     remove_vm_struct(area);
}
在remove_vm_struct中:
static void remove_vm_struct(struct vm_area_struct *vma)
{
     ……
     //将vma描述符释放
     kmem_cache_free(vm_area_cachep, vma);
}


unmap_region是整个收缩过程中的核心,它主要完成相应项表项的修改,具体映射页框的释放
代码如下:
static void unmap_region(struct mm_struct *mm,
     struct vm_area_struct *vma,
     struct vm_area_struct *prev,
     unsigned long start,
     unsigned long end)
{
     struct mmu_gather *tlb;
     unsigned long nr_accounted = 0;

     lru_add_drain();
     tlb = tlb_gather_mmu(mm, 0);
     //断开具体的vma映射
     unmap_vmas(&tlb, mm, vma, start, end, &nr_accounted, NULL);
     vm_unacct_memory(nr_accounted);

     //在x86平台上,is_hugepage_only_range()恒为零
     if (is_hugepage_only_range(start, end - start))
         hugetlb_free_pgtables(tlb, prev, start, end);
     else
         //因为删除了一些映射,会造成一个页表空闲的情况,回收页表项所占的空间
         free_pgtables(tlb, prev, start, end);
     tlb_finish_mmu(tlb, start, end);
}
unmap_vmas用来释放pte所映射的页面。代码如下:
//参数说明:
//mm:进程描述符 vma:要删除的起始vma start_addr:要删除的线性区的起始地址
// end_addr:要删除的线性区的结束地址 details:在调用的时候置为了NULL ^_^
int unmap_vmas(struct mmu_gather **tlbp, struct mm_struct *mm,
         struct vm_area_struct *vma, unsigned long start_addr,
         unsigned long end_addr, unsigned long *nr_accounted,
         struct zap_details *details)
{
     unsigned long zap_bytes = ZAP_BLOCK_SIZE;
     unsigned long tlb_start = 0;     /* For tlb_finish_mmu */
     int tlb_start_valid = 0;
     int ret = 0;
     int atomic = details && details->atomic;

     //遍历要删除的vma链表
     for ( ; vma && vma->vm_start vm_next) {
         unsigned long start;
         unsigned long end;
         
         //确定要断开映射的起始地址跟结束地址
         start = max(vma->vm_start, start_addr);
         if (start >= vma->vm_end)
              continue;
         end = min(vma->vm_end, end_addr);
         if (end vm_start)
              continue;

         if (vma->vm_flags & VM_ACCOUNT)
              *nr_accounted += (end - start) >> PAGE_SHIFT;

         ret++;
         //while循环开始断开start到end的所有被映射的页框,在足够的情况下一次释放zap_bytes
         while (start != end) {
              unsigned long block;

              if (!tlb_start_valid) {
                   tlb_start = start;
                   tlb_start_valid = 1;
              }
              
              //在条件编译下is_vm_hugetlb_page()为空
              if (is_vm_hugetlb_page(vma)) {
                   block = end - start;
                   unmap_hugepage_range(vma, start, end);
              } else {
                   //block:要释放的线性区大小
                   block = min(zap_bytes, end - start);
                   //断开从start到start + block之间的映射
                   unmap_page_range(*tlbp, vma, start,
                            start + block, details);
              }
              //更新起始地址
              start += block;
              zap_bytes -= block;
              if (!atomic && need_resched()) {
                   int fullmm = tlb_is_full_mm(*tlbp);
                   tlb_finish_mmu(*tlbp, tlb_start, start);
                   cond_resched_lock(&mm->page_table_lock);
                   *tlbp = tlb_gather_mmu(mm, fullmm);
                   tlb_start_valid = 0;
              }
              if ((long)zap_bytes > 0)
                   continue;
              zap_bytes = ZAP_BLOCK_SIZE;
         }
     }
     return ret;
}
跟进unmap_page_range():
static void unmap_page_range(struct mmu_gather *tlb,
         struct vm_area_struct *vma, unsigned long address,
         unsigned long end, struct zap_details *details)
{
     pgd_t * dir;

     BUG_ON(address >= end);
     //取得页目录
     dir = pgd_offset(vma->vm_mm, address);
     tlb_start_vma(tlb, vma);
     //断开pgd项对应的pmd
     do {
         zap_pmd_range(tlb, dir, address, end - address, details);
         //加上一个pgd大小,并对应PGD_SIZE
         address = (address + PGDIR_SIZE) & PGDIR_MASK;
         dir++;
     } while (address && (address
     //x86为空函数,忽略
     tlb_end_vma(tlb, vma);
}
转入zap_pmd_range():
static void zap_pmd_range(struct mmu_gather *tlb,
         pgd_t * dir, unsigned long address,
         unsigned long size, struct zap_details *details)
{
     pmd_t * pmd;
     unsigned long end, pgd_boundary;

     //页目录没有映射
     if (pgd_none(*dir))
         return;
     //无效
     if (unlikely(pgd_bad(*dir))) {
         pgd_ERROR(*dir);
         pgd_clear(dir);
         return;
     }
     //找到起始的pmd
     pmd = pmd_offset(dir, address);
     end = address + size;
     pgd_boundary = ((address + PGDIR_SIZE) & PGDIR_MASK);
     if (pgd_boundary && (end > pgd_boundary))
         end = pgd_boundary;
     do {
         //根据pmd找到pte
          (tlb, pmd, address, end - address, details);
         address = (address + PMD_SIZE) & PMD_MASK;
         pmd++;
     } while (address && (address
}
继续跟进zap_pte_range():
static void zap_pte_range(struct mmu_gather *tlb,
         pmd_t *pmd, unsigned long address,
         unsigned long size, struct zap_details *details)
{
     unsigned long offset;
     pte_t *ptep;

     //pmd没有映射页面
     if (pmd_none(*pmd))
         return;
     //无效情况
     if (unlikely(pmd_bad(*pmd))) {
         pmd_ERROR(*pmd);
         pmd_clear(pmd);
         return;
     }
     ptep = pte_offset_map(pmd, address);
     offset = address & ~PMD_MASK;
     if (offset + size > PMD_SIZE)
         size = PMD_SIZE - offset;
     size &= PAGE_MASK;
     if (details && !details->check_mapping && !details->nonlinear_vma)
         details = NULL;
     for (offset=0; offset
         pte_t pte = *ptep;
         //pte没有映射页面
         if (pte_none(pte))
              continue;
         //相应的页在主存中
         if (pte_present(pte)) {
              struct page *page = NULL;
              //将pte映射的物理地址转换为页面号
              unsigned long pfn = pte_pfn(pte);
              //如果页面号合法,则转换为相应的page,如果页面被保留(不可以断开映射),page置``````````````//为NULL
              if (pfn_valid(pfn)) {
                   page = pfn_to_page(pfn);
                   if (PageReserved(page))
                       //Reserverd:留给内核使用或者没有使用
                       page = NULL;
              }
              //函数调用时,details为NULL。略过这部份代码 ^_^
              if (unlikely(details) && page) {
                   /*
                    * unmap_shared_mapping_pages() wants to
                    * invalidate cache without truncating:
                    * unmap shared but keep private pages.
                    */
                   if (details->check_mapping &&
                       details->check_mapping != page->mapping)
                       continue;
                   /*
                    * Each page->index must be checked when
                    * invalidating or truncating nonlinear.
                    */
                   if (details->nonlinear_vma &&
                       (page->index first_index ||
                        page->index > details->last_index))
                       continue;
              }
              //清除pte值,并返回原来的pte值
              pte = ptep_get_and_clear(ptep);
              tlb_remove_tlb_entry(tlb, ptep, address+offset);
              //如果page 为NULL,说明不需要释放page
              if (unlikely(!page))
                   continue;
              if (unlikely(details) && details->nonlinear_vma
                  && linear_page_index(details->nonlinear_vma,
                       address+offset) != page->index)
                   set_pte(ptep, pgoff_to_pte(page->index));
              //如果页面项为脏,置page为脏
              if (pte_dirty(pte))
                   set_page_dirty(page);
              if (pte_young(pte) && !PageAnon(page))
                   mark_page_accessed(page);
              tlb->freed++;
              page_remove_rmap(page);
              //在tlb_remove_page里判断page的引用计数,如果没有引用了
              //调用free_page_and_swap_cache将页面释放
              tlb_remove_page(tlb, page);
              continue;
         }
         
         if (unlikely(details))
              continue;
     //如果页表项所映射的数据被交换到了磁盘,释放相关数据
         if (!pte_file(pte))
              free_swap_and_cache(pte_to_swp_entry(pte));
         //清除pte映射
         pte_clear(ptep);
     }
     pte_unmap(ptep-1);
}


本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u2/70722/showart_728898.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP