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数据结构
-
/*
-
* vmalloc区域中的子内存区,ioremap也使用了该区域
-
* 所有的vm_struct组成一个链表,管理着vmalloc区域
-
* 中已经建立的各个子区域,该链表头保存于
-
* 全局变量vmlist中。
-
*/
-
struct vm_struct {
-
struct vm_struct *next;
-
//该内存区在虚拟地址空间中的起始地址
-
void *addr;
-
// 该内存区的大小
-
unsigned long size;
-
/*
-
* 与该内存区关联的标志集合,用于指定内存
-
* 区域类型,可选值有3:
-
* VM_ALLOC指定由vmalloc产生的子区域
-
* VM_MAP表示将现存pages集合映射到连续的虚拟
-
地址空间中。
-
* VM_IOREMAP表示将IO内存映射到vmalloc区域中。
-
*/
-
unsigned long flags;
-
/*
-
* 指向page指针的数组,每个数组成员表示一个
-
* 映射到虚拟地址空间中的物理内存页的实例。
-
*/
-
struct page **pages;
-
// pages数组项的数目,即该内存区对应的物理内存页数
-
unsigned int nr_pages;
-
/*
-
* ioremap时使用,用来保存该区域映射的物理
-
* 内存地址,在通常的vmalloc流程中不使用该
-
* 字段,因为vmalloc流程中会分配物理内存,并
-
* 通过修改内核页表来实现虚拟地址到物理
-
* 地址见的映射。
-
*/
-
phys_addr_t phys_addr;
-
const void *caller;
-
};
vmalloc基本流程如下:
vmalloc()
-->__vmalloc()
-->__vmalloc_node()
-->__vmalloc_node_range()
-->get_vm_area_node() //从vmalloc区中获取空闲的子区域
-->__vmalloc_area_node() //使用alloc_page分配物理内存,并修改页表,进行映射。
3、代码分析
__vmalloc_node_range:
-
// vmalloc主处理函数
-
void *__vmalloc_node_range(unsigned long size, unsigned long align,
-
unsigned long start, unsigned long end, gfp_t gfp_mask,
-
pgprot_t prot, int node, const void *caller)
-
{
-
//vmalloc区域中的子内存区
-
struct vm_struct *area;
-
void *addr;
-
unsigned long real_size = size;
-
-
// 分配内存区大小进行页对齐
-
size = PAGE_ALIGN(size);
-
if (!size || (size >> PAGE_SHIFT) > totalram_pages)
-
goto fail;
-
-
/*
-
* 从vmalloc区中的子区域链表vmlist(全局变量)中
-
* 获取空闲的子区域vm_struct。
-
*/
-
-
area = __get_vm_area_node(size, align, VM_ALLOC | VM_UNLIST,
-
start, end, node, gfp_mask, caller);
-
if (!area)
-
goto fail;
-
-
/*
-
* 分配物理内存,并通过修改内核页表建立物理地址和虚拟
-
* 地址间的映射,返回值为vmalloc区的虚拟地址
-
*/
-
addr = __vmalloc_area_node(area, gfp_mask, prot, node, caller);
-
if (!addr)
-
return NULL;
-
-
/*
-
* In this function, newly allocated vm_struct has VM_UNLIST flag.
-
* It means that vm_struct is not fully initialized.
-
* Now, it is fully initialized, so remove this flag here.
-
*/
-
clear_vm_unlist(area);
-
-
/*
-
* A ref_count = 3 is needed because the vm_struct and vmap_area
-
* structures allocated in the __get_vm_area_node() function contain
-
* references to the virtual address of the vmalloc'ed block.
-
*/
-
kmemleak_alloc(addr, real_size, 3, gfp_mask);
-
-
return addr;
-
-
fail:
-
warn_alloc_failed(gfp_mask, 0,
-
"vmalloc: allocation failure: %lu bytes\n",
-
real_size);
-
return NULL;
-
}
__vmalloc_area_node:
-
static void *__vmalloc_area_node(struct vm_struct *area, gfp_t gfp_mask,
-
pgprot_t prot, int node, const void *caller)
-
{
-
const int order = 0;
-
struct page **pages;
-
unsigned int nr_pages, array_size, i;
-
gfp_t nested_gfp = (gfp_mask & GFP_RECLAIM_MASK) | __GFP_ZERO;
-
-
nr_pages = (area->size - PAGE_SIZE) >> PAGE_SHIFT;
-
array_size = (nr_pages * sizeof(struct page *));
-
-
// 此区域需要分配的物理内存页数
-
area->nr_pages = nr_pages;
-
/* Please note that the recursion is strictly bounded. */
-
if (array_size > PAGE_SIZE) {
-
/*
-
* 如果区域大小大于1页,则进行迭代,注意
-
* 此处传入的内存分配标记中有__GFP_HIGHMEM,
-
* 表示优先从高端内存中分配,这也是vmalloc
-
* 的用处所在。
-
*/
-
pages = __vmalloc_node(array_size, 1, nested_gfp|__GFP_HIGHMEM,
-
PAGE_KERNEL, node, caller);
-
area->flags |= VM_VPAGES;
-
// 迭代直到分配的区域大小小于1页,则使用kmalloc分配
-
} else {
-
pages = kmalloc_node(array_size, nested_gfp, node);
-
}
-
area->pages = pages;
-
area->caller = caller;
-
if (!area->pages) {
-
remove_vm_area(area->addr);
-
kfree(area);
-
return NULL;
-
}
-
-
// 对于整页,逐页进行分配
-
for (i = 0; i < area->nr_pages; i++) {
-
struct page *page;
-
gfp_t tmp_mask = gfp_mask | __GFP_NOWARN;
-
-
if (node < 0)
-
// 分配实际的物理页
-
page = alloc_page(tmp_mask);
-
else
-
page = alloc_pages_node(node, tmp_mask, order);
-
-
if (unlikely(!page)) {
-
/* Successfully allocated i pages, free them in __vunmap() */
-
area->nr_pages = i;
-
goto fail;
-
}
-
// 将分配的page填入vm_struct结构中
-
area->pages[i] = page;
-
}
-
-
/*
-
* 将新分配的区域进行映射,即修改内核页表
-
* (进程页表在page fault中更新),建立虚拟地址到
-
* 物理地址间的映射。
-
*/
-
if (map_vm_area(area, prot, &pages))
-
goto fail;
-
return area->addr;
-
-
fail:
-
warn_alloc_failed(gfp_mask, order,
-
"vmalloc: allocation failure, allocated %ld of %ld bytes\n",
-
(area->nr_pages*PAGE_SIZE), area->size);
-
vfree(area->addr);
-
return NULL;
-
}
阅读(404) | 评论(0) | 转发(0) |