对unmap_vm_area的疑问

我看ulk349页里,unmap_area_xxx依次在循环中执行map_area_xxx的反操作,但是在map_area_xxx中都有xxx_alloc,为什么在unmap_area_xxx中没有把相关的free了呢?

在350页写了一段话:....内核修改主内核页全局目录和它的子页表中的响应项,但是映射第4个GB的进程页表的项保持不变,这是在情理之中的,因为内核永远也不会收回扎根于主内核页全局目录中的页上级目录、页中级目录和页表。
这段话是什么意思?


以下代码摘自2.6.10,供参考
  1. int map_vm_area(struct vm_struct *area, pgprot_t prot, struct page ***pages)
  2. {
  3.         unsigned long address = (unsigned long) area->addr;
  4.         unsigned long end = address + (area->size-PAGE_SIZE);
  5.         pgd_t *dir;
  6.         int err = 0;

  7.         dir = pgd_offset_k(address);
  8.         spin_lock(&init_mm.page_table_lock);
  9.         do {
  10.                 pmd_t *pmd = pmd_alloc(&init_mm, dir, address);
  11.                 if (!pmd) {
  12.                         err = -ENOMEM;
  13.                         break;
  14.                 }
  15.                 if (map_area_pmd(pmd, address, end - address, prot, pages)) {
  16.                         err = -ENOMEM;
  17.                         break;
  18.                 }

  19.                 address = (address + PGDIR_SIZE) & PGDIR_MASK;
  20.                 dir++;
  21.         } while (address && (address < end));

  22.         spin_unlock(&init_mm.page_table_lock);
  23.         flush_cache_vmap((unsigned long) area->addr, end);
  24.         return err;
  25. }
复制代码
  1. void unmap_vm_area(struct vm_struct *area)
  2. {
  3.         unsigned long address = (unsigned long) area->addr;
  4.         unsigned long end = (address + area->size);
  5.         pgd_t *dir;

  6.         dir = pgd_offset_k(address);
  7.         flush_cache_vunmap(address, end);
  8.         do {
  9.                 unmap_area_pmd(dir, address, end - address);
  10.                 address = (address + PGDIR_SIZE) & PGDIR_MASK;
  11.                 dir++;
  12.         } while (address && (address < end));
  13.         flush_tlb_kernel_range((unsigned long) area->addr, end);
  14. }
复制代码

作者: amarant   发布时间: 2011-01-19

pXd_alloc,并不一定会分配内存,只是在对应address上的这一级页目录不存在的情况下,才会分配内存。如:
  1. #define pmd_alloc(mm, pud, address) \
  2.         ((unlikely(pgd_none(*(pud))) && __pmd_alloc(mm, pud, address))? \
  3.                 NULL: pmd_offset(pud, address))
复制代码
这个函数实际上是确保对应位置上的这一级页目录存在。

关于这个地方的逻辑,我的理解是:

1、初始情况下,VM区域没有大于等于二级的页目录,VM区域一级页目录里面的值都是empty;
2、vmalloc的时候,需要确保各级的页目录存在,所以要调用pXd_alloc,并把每一级页目录都建立起来。这个时候一级页目录的相应位置从empty变成有值,然后这时建立起来的各级页目录都关联到init_mm里面去;
这里有个问题,内核没有自己的页表,它是借用了进程的页表,并且要求每个进程的页表在内核空间的范围内都是一样的。
而vmalloc的内存是在内核里面使用的,但是vmalloc这一下,只修改了当前生效的那个页表,其他进程的页表并没有一一去修改(lazy)。也就是说,你vmalloc之后,过了一段时间,发生了调度,页表被换了,然后你再去使用之前vmalloc出来的内存,可能对应的页表项是不存在的(当前的第一级页目录在对应区域的值还是empty)。
为了解决这个问题,缺页异常里面有个逻辑,就是从init_mm里面取值,在当前生效的页表中把这些缺少的这些页目录信息都补上。(其实就是把一级页目录里面的值跟init_mm做一个同步。大于等于二级的页目录都是直接引用init_mm里面的,并没有自己新建一份。)

然后,vfree的时候会发生什么呢?映射被取消,最后一级页目录里面的pte被修改。这个修改同样是在当前生效的页表里面去做的,那么其他进程的页表如何去同步呢?vmalloc的时候,页表项从无到有,可以由缺页异常来通知你该同步了;而现在页表项本身是存在的,缺页异常并不会发生。
呵呵,不过这时候根本就不需要同步,因为在每个进程的页表里面,VM区域的二级页目录都是直接引用的init_mm里面的,都是同一份。并且vfree的时候并不会释放任何页目录,只会修改最底层的pte。这样的修改是直接在所有进程的页表中同时生效的。
那么,如果vfree会释放页目录,会发生什么问题呢?页目录释放了,一级页目录里面对应的值被重置为empty。但是现在只重置了当前生效的页表,其他进程的页表没重置。什么时候同步呢?没法同步,因为其他进程的一级页目录里面对应的entry是有值的,不会触发缺页异常。并且这些entry指向的二级页目录已经被释放了,这不就BUG了么?
所以,vmalloc建立的页目录,一旦建立,就不会释放了。

作者: kouu   发布时间: 2011-01-19

回复 kouu


    非常感谢!明白了~

作者: amarant   发布时间: 2011-01-19