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

2008年(27)

我的朋友

分类: LINUX

2008-06-07 12:47:18

通过上面的分析可以看到,内核是如何通过线性地址从pgd找到pte再释放相关页面的。到这一步,注意到,只是释放了pte所映射的页框,所以,可能会造成有很多pte项没有映射的状态,这部份pte所占的空间其实是可以回收的。

它是在free_pgtables()函数中完成的。代码如下:

static void free_pgtables(struct mmu_gather *tlb, struct vm_area_struct *prev,

     unsigned long start, unsigned long end)

{

     //PGD_SIZE大小对齐

     unsigned long first = start & PGDIR_MASK;

     //向上增加一个PGD_SIZE,last参数还会在接下来进行调整的

     unsigned long last = end + PGDIR_SIZE - 1;

     unsigned long start_index, end_index;

     struct mm_struct *mm = tlb->mm;

 

     //调整firstlast参数的值

     if (!prev) {

         prev = mm->mmap;

         if (!prev)

              goto no_mmaps;

         if (prev->vm_end > start) {

              if (last > prev->vm_start)

                   last = prev->vm_start;

              goto no_mmaps;

         }

     }

     for (;;) {

         struct vm_area_struct *next = prev->vm_next;

 

         if (next) {

              if (next->vm_start < start) {

                   prev = next;

                   continue;

              }

              if (last > next->vm_start)

                   last = next->vm_start;

         }

         if (prev->vm_end > first)

              first = prev->vm_end + PGDIR_SIZE - 1;

         break;

     }

no_mmaps:

     //非法退出

     if (last < first) 

         return;

     //first所在的页目录

     start_index = pgd_index(first);

     if (start_index < FIRST_USER_PGD_NR)

         start_index = FIRST_USER_PGD_NR;

     //last所在页目录

     end_index = pgd_index(last);

     if (end_index > start_index) {

         //将页目录start_indexend_index中所映射的pte所占空间释放掉

         clear_page_tables(tlb, start_index, end_index - start_index);

         flush_tlb_pgtables(mm, first & PGDIR_MASK, last & PGDIR_MASK);

     }

}

在研究代码之前,我们不妨先来思考几个问题:

1:一次要释放的多长的地址区间才合适呢?

i32二级映射关系为例来说明一下:

虽然pte在线性地址中只占有10位,但是实际上为pte分配内存的时候,却分配了一个页。也就是说,pgd中每一项所指向的pte占一个页面.2^10pte项占一个页面。而pte本身映射2^12大小的线性地址。所以,要释放一个pte内框所需的地址长度为2^10*2^12 = 2^22 = PGD_SIZE

I32的三级映射也类似

2prev指向的是什么?

调用这个函数的时候,prev指向的是什么区域的vma呢?

刚开始的时候:

 

detach_vmas_to_be_unmapped后:

看上面可以看出: clear_page_tables中,要操作的线性地址即为prev,prev->next之间的空洞线性地址。理解了这点之后,上面的代码就变得很简单了^_^

三:用户空间的伸展

先回顾一下sys_brk的代码:

asmlinkage unsigned long sys_brk(unsigned long brk)

{

         ……

         ……

         //前一部份是用户空间的收缩

         /* Check against rlimit.. */

         //不能超过数据段上限

         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;

}

在这有一个值得注意的地方:

find_vma_intersection()的实现如下:

//判断进程的地址空间是否与给定的地址区间相交叉

static inline struct vm_area_struct * find_vma_intersection(struct mm_struct * mm, unsigned long start_addr, unsigned long end_addr)

{

         //找到第一个结束地址大于addrvma

         struct vm_area_struct * vma = find_vma(mm,start_addr);

         //判断vma是否是给定地址区间有交叉

         if (vma && end_addr <= vma->vm_start)

                   vma = NULL;

         return vma;

}

那为什么sys_brk

find_vma_intersection(mm, oldbrk, newbrk+PAGE_SIZE)调用中,newbrk为什么要加上PAGE_SIZE呢?

这是因为newbrk oldbrk已经是经过页框对齐后的地址:如下

         newbrk = PAGE_ALIGN(brk);

         oldbrk = PAGE_ALIGN(mm->brk);

而且,每个vma的起始地址跟长度都是与页框对齐的(参考ULK3.注意到find_vma_intersection()判断是否交替的时候带有一个’=’.也就是判断newbrk的下一个页框是否在进程的线性区中

接着往下看,经过判断之后,就会进入到do_brk()

unsigned long do_brk(unsigned long addr, unsigned long len)

{

         struct mm_struct * mm = current->mm;

         struct vm_area_struct * vma, * prev;

         unsigned long flags;

         struct rb_node ** rb_link, * rb_parent;

         pgoff_t pgoff = addr >> PAGE_SHIFT;

         //长度按页框对齐,不过在我们这个流程来说,这个步骤是没必要的

         //因为start end都与页框对齐,end – start肯定也是与页框对齐的

         len = PAGE_ALIGN(len);

         if (!len)

                   return addr;

         //有效性判断

         if ((addr + len) > TASK_SIZE || (addr + len) < addr)

                   return -EINVAL;

 

         //VM_LOCKED: 页被锁住不能被交换出去

         if (mm->def_flags & VM_LOCKED) {

                   unsigned long locked, lock_limit;

                   locked = mm->locked_vm << PAGE_SHIFT;

                   lock_limit = current->rlim[RLIMIT_MEMLOCK].rlim_cur;

                   locked += len;

                   if (locked > lock_limit && !capable(CAP_IPC_LOCK))

                            return -EAGAIN;

         }

 

         /*

          * Clear old maps.  this also does some error checking for us

          */

 munmap_back:

         //sys_brk的流程会进入到这个if吗???

         vma = find_vma_prepare(mm, addr, &prev, &rb_link, &rb_parent);

         if (vma && vma->vm_start < addr + len) {

                   if (do_munmap(mm, addr, len))

                            return -ENOMEM;

                   goto munmap_back;

         }

 

         //判断是否超过了限制

         if ((mm->total_vm << PAGE_SHIFT) + len

             > current->rlim[RLIMIT_AS].rlim_cur)

                   return -ENOMEM;

 

         if (mm->map_count > sysctl_max_map_count)

                   return -ENOMEM;

         //判断系统是否有足够的内存

         if (security_vm_enough_memory(len >> PAGE_SHIFT))

                   return -ENOMEM;

 

         flags = VM_DATA_DEFAULT_FLAGS | VM_ACCOUNT | mm->def_flags;

 

         //判断是否可以合并

         //如果可以合并,就将基合并为一个VMA

         if (vma_merge(mm, prev, addr, addr + len, flags,

                                               NULL, NULL, pgoff, NULL))

                   goto out;

 

         //不可以合并,新建一个VMA

         vma = kmem_cache_alloc(vm_area_cachep, SLAB_KERNEL);

         if (!vma) {

                   vm_unacct_memory(len >> PAGE_SHIFT);

                   return -ENOMEM;

         }

         memset(vma, 0, sizeof(*vma));

 

         //设值VMA的值

         vma->vm_mm = mm;

         vma->vm_start = addr;

         vma->vm_end = addr + len;

         vma->vm_pgoff = pgoff;

         vma->vm_flags = flags;

         vma->vm_page_prot = protection_map[flags & 0x0f];

         //将新分配的VMA插入到进程的VMA链表

         vma_link(mm, vma, prev, rb_link, rb_parent);

out:

         mm->total_vm += len >> PAGE_SHIFT;

         if (flags & VM_LOCKED) {

                   mm->locked_vm += len >> PAGE_SHIFT;

                   //如果定义了LOCKED。就为其分配内存

                   make_pages_present(addr, addr + len);

         }

         return addr;

}

make_pages_present()其实就是为每一个线性区模拟了一个缺页异常,然后再由缺页异常程序为之分配内存。

vm flag没有带VM_LOCKED的时候,它只是为进程分配了一个可以使用的线性地址,以后要访问这个地址的时候,就会产生缺页异常,具体关于缺页异常的处理,我们在下一节接着分析

四:总结

我们在前面分析过了vfree()的实现。还记得vfree()只是释放了内存页表项所映射的物理内存,而在进程管理的时候,sys_brk收缩线性区的时候,它不仅释放了内表所映射的物理内存还把空间页表项。PMD所占的内存释放掉了。内核这样处理是为了效率考虑的。

另外,sys_brk在扩展线性区的时候,仅分配了一个允许进程使用的合法的线性地址,等到真正要使用的时候再给其映射具体的内存,这在操作系统设计里也叫请求调页。等到下节分析缺页异常的时候,再来详细讨论

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