Chinaunix首页 | 论坛 | 博客
  • 博客访问: 771127
  • 博文数量: 370
  • 博客积分: 2334
  • 博客等级: 大尉
  • 技术积分: 3222
  • 用 户 组: 普通用户
  • 注册时间: 2012-05-06 16:56
文章分类

全部博文(370)

文章存档

2013年(2)

2012年(368)

分类:

2012-05-18 11:39:23

:vmalloc的实现:

void *vmalloc(unsigned long size)

{

       return __vmalloc(size, GFP_KERNEL | __GFP_HIGHMEM, PAGE_KERNEL);

}

实际上调用__vmalloc:

void *__vmalloc(unsigned long size, int gfp_mask, pgprot_t prot)

{

     struct vm_struct *area;

     struct page **pages;

     unsigned int nr_pages, array_size, i;

 

     //使请求的大小与页框对齐

     size = PAGE_ALIGN(size);

     //有效性检查

     if (!size || (size >> PAGE_SHIFT) > num_physpages)

         return NULL;

 

     //取得一个有效的VM,这个函数我们在前面已经详细的分析过了

     area = get_vm_area(size, VM_ALLOC);

     if (!area)

         return NULL;

 

     //所要映射的页面总数

     nr_pages = size >> PAGE_SHIFT;

     //页面描述符所占的空间

     array_size = (nr_pages * sizeof(struct page *));

 

     area->nr_pages = nr_pages;

     area->pages = pages = kmalloc(array_size, (gfp_mask & ~__GFP_HIGHMEM));

 

     //如果空间分配失败

     if (!area->pages) {

         remove_vm_area(area->addr);

         kfree(area);

         return NULL;

     }

     memset(area->pages, 0, array_size);

 

     //为每一个页面分配空间

     for (i = 0; i < area->nr_pages; i++) {

         area->pages[i] = alloc_page(gfp_mask);

         if (unlikely(!area->pages[i])) {

              /* Successfully allocated i pages, free them in __vunmap() */

              area->nr_pages = i;

              goto fail;

         }

     }

 

     //为所分配的页面建立映射关系

     if (map_vm_area(area, prot, &pages))

         goto fail;

     return area->addr;

 

fail:

     vfree(area->addr);

     return NULL;

}

map_vm_area为所分配的内存建立映射关系,它的程序流程与unmap_vm_area差不多,都是从pgd找到pte,如果同样的映射关系不存在,则新建之.(:pgd对应的pmd不存在,则新建pmd,使pgd指向建好的pmd.同理,如果pmd所映射的pte项不存在,则新建pte,然后建立映射),然后将pte映射到相应的页表.代码如下:

int map_vm_area(struct vm_struct *area, pgprot_t prot, struct page ***pages)

{

     unsigned long address = (unsigned long) area->addr;

     unsigned long end = address + (area->size-PAGE_SIZE);

     pgd_t *dir;

     int err = 0;

 

     //vm 起始地址所在的页目录

     dir = pgd_offset_k(address);

     spin_lock(&init_mm.page_table_lock);

     do {

         pmd_t *pmd = pmd_alloc(&init_mm, dir, address);

         if (!pmd) {

              err = -ENOMEM;

              break;

         }

         //轮到pmd ^_^

         if (map_area_pmd(pmd, address, end - address, prot, pages)) {

              err = -ENOMEM;

              break;

         }

 

         address = (address + PGDIR_SIZE) & PGDIR_MASK;

         dir++;

     } while (address && (address < end));

 

     spin_unlock(&init_mm.page_table_lock);

     flush_cache_vmap((unsigned long) area->addr, end);

     return err;

}

static int map_area_pmd(pmd_t *pmd, unsigned long address,

                     unsigned long size, pgprot_t prot,

                     struct page ***pages)

{

     unsigned long base, end;

 

     base = address & PGDIR_MASK;

     address &= ~PGDIR_MASK;

     end = address + size;

     if (end > PGDIR_SIZE)

         end = PGDIR_SIZE;

 

     do {

         pte_t * pte = pte_alloc_kernel(&init_mm, pmd, base + address);

         if (!pte)

              return -ENOMEM;

         //轮到pte ^_^

         if (map_area_pte(pte, address, end - address, prot, pages))

              return -ENOMEM;

         address = (address + PMD_SIZE) & PMD_MASK;

         pmd++;

     } while (address < end);

 

     return 0;

}

 

//为页表页建立映射关系

static int map_area_pte(pte_t *pte, unsigned long address,

                     unsigned long size, pgprot_t prot,

                     struct page ***pages)

{

     unsigned long end;

 

     address &= ~PMD_MASK;

     end = address + size;

     if (end > PMD_SIZE)

         end = PMD_SIZE;

 

     do {

         struct page *page = **pages;

 

         WARN_ON(!pte_none(*pte));

         if (!page)

              return -ENOMEM;

        

         //具体的映射在这里了 ^_^

         set_pte(pte, mk_pte(page, prot));

         address += PAGE_SIZE;

         pte++;

         (*pages)++;

     } while (address < end);

     return 0;

}

只要理解了断开映射的过程,这段代码是很好理解的.

总而言之:linux在建立映射的时候,pgd pte相应的建立映射关系,最后将pte映射到分配得到的物理内存.而在断开映射的时候,linux内核从pgd找到pte,然后将pte置为none,表示pte末建立映射关系.

:vfree的实现:

代码如下:

void vfree(void *addr)

{

     BUG_ON(in_interrupt());

     __vunmap(addr, 1);

}

跟踪至__vunmap:

void __vunmap(void *addr, int deallocate_pages)

{

     struct vm_struct *area;

     //参数有效性检查

     if (!addr)

         return;

//判断addr是否是按页框对齐的

     if ((PAGE_SIZE-1) & (unsigned long)addr) {

         printk(KERN_ERR "Trying to vfree() bad address (%p)\n", addr);

         WARN_ON(1);

         return;

     }

//remove_vm_area:这个函数我们在之前已经分析过了 ^_^

     area = remove_vm_area(addr);

     if (unlikely(!area)) {

         //没有找到起始地址为addrvm.则无效,退出

         printk(KERN_ERR "Trying to vfree() nonexistent vm area (%p)\n",

                   addr);

         WARN_ON(1);

         return;

     }

    

     if (deallocate_pages) {

         int i;

 

         for (i = 0; i < area->nr_pages; i++) {

              if (unlikely(!area->pages[i]))

                   BUG();

              //释放请求获得的页面

              __free_page(area->pages[i]);

         }

 

         //释放分配的page 描述符

         kfree(area->pages);

     }

 

     //释放内核的vm 描述符

     kfree(area);

     return;

}

:总结

经过上面的分析,我们可以看到,vmalloc分配内存的过程是十分低效的,不仅要从伙伴系统中取内存而且要建立映射关系,显然,用vmalloc分配较小的内存是不合算的。此外。有个问题值得思考一下:为什么用__get_free_page不需要建立映射关系,而vmalloc就需要呢?

其实,不管使用何种方式。线性地址到物理地址的转换最终都要经过硬件的页式管理去完成。所不同的是__get_free_page返回的线性地址是属于(PAGE_OFFSET,HIGH_MEMORY)之间的,这段线性地址在内核初始化的时候就完成了映射。而vmalloc使用的线性地址是属于(VMALLOC_START VMALLOC_END)之间的,也就是说属于一个临时映射区,所以必须为其建立映射关系。

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