Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1856320
  • 博文数量: 317
  • 博客积分: 1557
  • 博客等级: 上尉
  • 技术积分: 1208
  • 用 户 组: 普通用户
  • 注册时间: 2008-02-26 23:38
个人简介

如果想出发,就不要等到明天!

文章分类

全部博文(317)

文章存档

2016年(1)

2015年(41)

2014年(152)

2013年(114)

2012年(4)

2011年(1)

2009年(4)

分类: LINUX

2014-08-27 10:43:39

1、为什么需要vmalloc
在分配内存时,总是希望能分配到连续的物理内存页,愿望是美好的,但系统中可能没有太多的连续内存可用(比如内存碎片严重时),此时就需要一种非连续内存的分配方式。于是乎,就产生了vmalloc,vmalloc用于分配不连续的物理内存页,但将其映射到内核虚拟地址空间中后,其虚拟地址是连续的,使用vmalloc分配内存有三个特点:
1)物理内存不一定连续
2)返回的虚拟内存是连续的。
3)优先分配高端内存,所以vmalloc也是在内核态使用高端内存的最主要的方式。
在用户态,应用程序直接看到的是虚拟地址空间,物理内存和虚拟内存间通过页表映射,用户太分配的内存在虚拟地址空间中总是连续的,而物理内存是否连续不必关心,所以这对用户态来说其实没啥影响(除了会稍影响性能和占用TLB)。

2、基本原理(针对IA32)
内核虚拟地址空间中,有一段专门的区间用于vmalloc,称之vmalloc区,位于线性映射区之后,准确的说是从892M+8M(VMALLOC_START)到VMALLOC_END之间,其中892M是线性映射区,用于线性映射低端内存,之后的8M是安全间隙,用于区间隔离,防止越界。
vmalloc区中包含一个个独立的子区域,每个子区域用于一次独立映射,各个子区域间通过一个内存页进行隔离,防止不正确的内存访问操作。内核中管理vmalloc区中的子区域,使用了vm_struct数据结构

点击(此处)折叠或打开

  1. /*
  2.   * vmalloc区域中的子内存区,ioremap也使用了该区域
  3.   * 所有的vm_struct组成一个链表,管理着vmalloc区域
  4.   * 中已经建立的各个子区域,该链表头保存于
  5.   * 全局变量vmlist中。
  6.   */
  7. struct vm_struct {
  8.     struct vm_struct    *next;
  9.     //该内存区在虚拟地址空间中的起始地址
  10.     void            *addr;
  11.     // 该内存区的大小
  12.     unsigned long        size;
  13.     /*
  14.      * 与该内存区关联的标志集合,用于指定内存
  15.      * 区域类型,可选值有3:
  16.      * VM_ALLOC指定由vmalloc产生的子区域
  17.      * VM_MAP表示将现存pages集合映射到连续的虚拟
  18.      地址空间中。
  19.      * VM_IOREMAP表示将IO内存映射到vmalloc区域中。
  20.      */
  21.     unsigned long        flags;
  22.     /*
  23.       * 指向page指针的数组,每个数组成员表示一个
  24.       * 映射到虚拟地址空间中的物理内存页的实例。
  25.      */
  26.     struct page        **pages;
  27.     // pages数组项的数目,即该内存区对应的物理内存页数
  28.     unsigned int        nr_pages;
  29.     /*
  30.      * ioremap时使用,用来保存该区域映射的物理
  31.      * 内存地址,在通常的vmalloc流程中不使用该
  32.      * 字段,因为vmalloc流程中会分配物理内存,并
  33.      * 通过修改内核页表来实现虚拟地址到物理
  34.      * 地址见的映射。
  35.     */
  36.     phys_addr_t        phys_addr;
  37.     const void        *caller;
  38. };
vmalloc基本流程如下:
vmalloc()
-->__vmalloc()
     -->__vmalloc_node()
          -->__vmalloc_node_range()
               -->get_vm_area_node()     //从vmalloc区中获取空闲的子区域
               -->__vmalloc_area_node() //使用alloc_page分配物理内存,并修改页表,进行映射。

3、代码分析
__vmalloc_node_range:

点击(此处)折叠或打开

  1. // vmalloc主处理函数
  2. void *__vmalloc_node_range(unsigned long size, unsigned long align,
  3.             unsigned long start, unsigned long end, gfp_t gfp_mask,
  4.             pgprot_t prot, int node, const void *caller)
  5. {
  6.     //vmalloc区域中的子内存区
  7.     struct vm_struct *area;
  8.     void *addr;
  9.     unsigned long real_size = size;

  10.     // 分配内存区大小进行页对齐
  11.     size = PAGE_ALIGN(size);
  12.     if (!size || (size >> PAGE_SHIFT) > totalram_pages)
  13.         goto fail;

  14.     /*
  15.      * 从vmalloc区中的子区域链表vmlist(全局变量)
  16.      * 获取空闲的子区域vm_struct。
  17.      */
  18.     
  19.     area = __get_vm_area_node(size, align, VM_ALLOC | VM_UNLIST,
  20.                  start, end, node, gfp_mask, caller);
  21.     if (!area)
  22.         goto fail;

  23.     /*
  24.      * 分配物理内存,并通过修改内核页表建立物理地址和虚拟
  25.      * 地址间的映射,返回值为vmalloc区的虚拟地址
  26.      */
  27.     addr = __vmalloc_area_node(area, gfp_mask, prot, node, caller);
  28.     if (!addr)
  29.         return NULL;

  30.     /*
  31.      * In this function, newly allocated vm_struct has VM_UNLIST flag.
  32.      * It means that vm_struct is not fully initialized.
  33.      * Now, it is fully initialized, so remove this flag here.
  34.      */
  35.     clear_vm_unlist(area);

  36.     /*
  37.      * A ref_count = 3 is needed because the vm_struct and vmap_area
  38.      * structures allocated in the __get_vm_area_node() function contain
  39.      * references to the virtual address of the vmalloc'ed block.
  40.      */
  41.     kmemleak_alloc(addr, real_size, 3, gfp_mask);

  42.     return addr;

  43. fail:
  44.     warn_alloc_failed(gfp_mask, 0,
  45.              "vmalloc: allocation failure: %lu bytes\n",
  46.              real_size);
  47.     return NULL;
  48. }
__vmalloc_area_node:

点击(此处)折叠或打开

  1. static void *__vmalloc_area_node(struct vm_struct *area, gfp_t gfp_mask,
  2.                  pgprot_t prot, int node, const void *caller)
  3. {
  4.     const int order = 0;
  5.     struct page **pages;
  6.     unsigned int nr_pages, array_size, i;
  7.     gfp_t nested_gfp = (gfp_mask & GFP_RECLAIM_MASK) | __GFP_ZERO;

  8.     nr_pages = (area->size - PAGE_SIZE) >> PAGE_SHIFT;
  9.     array_size = (nr_pages * sizeof(struct page *));

  10.     // 此区域需要分配的物理内存页数
  11.     area->nr_pages = nr_pages;
  12.     /* Please note that the recursion is strictly bounded. */
  13.     if (array_size > PAGE_SIZE) {
  14.         /*
  15.          * 如果区域大小大于1页,则进行迭代,注意
  16.          * 此处传入的内存分配标记中有__GFP_HIGHMEM,
  17.          * 表示优先从高端内存中分配,这也是vmalloc
  18.          * 的用处所在。
  19.          */
  20.         pages = __vmalloc_node(array_size, 1, nested_gfp|__GFP_HIGHMEM,
  21.                 PAGE_KERNEL, node, caller);
  22.         area->flags |= VM_VPAGES;
  23.     // 迭代直到分配的区域大小小于1页,则使用kmalloc分配
  24.     } else {
  25.         pages = kmalloc_node(array_size, nested_gfp, node);
  26.     }
  27.     area->pages = pages;
  28.     area->caller = caller;
  29.     if (!area->pages) {
  30.         remove_vm_area(area->addr);
  31.         kfree(area);
  32.         return NULL;
  33.     }

  34.     // 对于整页,逐页进行分配
  35.     for (i = 0; i < area->nr_pages; i++) {
  36.         struct page *page;
  37.         gfp_t tmp_mask = gfp_mask | __GFP_NOWARN;

  38.         if (node < 0)
  39.             // 分配实际的物理页
  40.             page = alloc_page(tmp_mask);
  41.         else
  42.             page = alloc_pages_node(node, tmp_mask, order);

  43.         if (unlikely(!page)) {
  44.             /* Successfully allocated i pages, free them in __vunmap() */
  45.             area->nr_pages = i;
  46.             goto fail;
  47.         }
  48.         // 将分配的page填入vm_struct结构中
  49.         area->pages[i] = page;
  50.     }

  51.     /*
  52.      * 将新分配的区域进行映射,即修改内核页表
  53.      * (进程页表在page fault中更新),建立虚拟地址到
  54.      * 物理地址间的映射。
  55.      */
  56.     if (map_vm_area(area, prot, &pages))
  57.         goto fail;
  58.     return area->addr;

  59. fail:
  60.     warn_alloc_failed(gfp_mask, order,
  61.              "vmalloc: allocation failure, allocated %ld of %ld bytes\n",
  62.              (area->nr_pages*PAGE_SIZE), area->size);
  63.     vfree(area->addr);
  64.     return NULL;
  65. }
阅读(1495) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~