Chinaunix首页 | 论坛 | 博客
  • 博客访问: 141983
  • 博文数量: 27
  • 博客积分: 2010
  • 博客等级: 大尉
  • 技术积分: 295
  • 用 户 组: 普通用户
  • 注册时间: 2008-06-02 22:51
文章分类
文章存档

2008年(27)

我的朋友

分类: LINUX

2008-06-07 12:47:59

 

分析完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 < mm->end_code)

         goto out;

     //页框对齐

     newbrk = PAGE_ALIGN(brk);

     oldbrk = PAGE_ALIGN(mm->brk);

     //如果新边界与旧边界相等,不用进行空间的伸缩操作,直接赋值即可

     if (oldbrk == newbrk)

         goto set_brk;

//如果新边界比现在的边界要小,那说明要执行收缩操作

     //缩短堆

     if (brk <= mm->brk) {

         if (!do_munmap(mm, newbrk, oldbrk-newbrk))

              goto set_brk;

         goto out;

     }

//运行到这里的话,说明要执行的是数据段的伸展操作

     //不能超过数据段上限

     rlim = current->rlim[RLIMIT_DATA].rlim_cur;

     if (rlim < RLIM_INFINITY && brk - mm->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;

//找到第一个结束地址大于startVMAPrev是前一个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_below1的时候,vma为上一半,对应的new为下一半

     if (new_below)

         new->vm_end = addr;

     else {

              //new_below0时,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链到一起,同时将要删除的vmamm中脱链

//参数说明:

/*

     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 < end);

     //将要删除的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;

     //更新mmtotal_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);

}

阅读(2795) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~