前面总结了非连续内存区域的内核描述,接着看看他的分配和释放。
一、非连续内存区的分配
不管是vmalloc()还是vmalloc_32()等系列的分配函数最后都会调用__vmalloc_node()函数实现,直接看这个函数的实现。
* __vmalloc_node - allocate virtually contiguous memory
* @size: allocation size
* @align: desired alignment
* @gfp_mask: flags for the page level allocator
* @prot: protection mask for the allocated pages
* @node: node to use for allocation or -1
* @caller: caller's return address
*
* Allocate enough pages to cover @size from the page level
* allocator with @gfp_mask flags. Map them into contiguous
* kernel virtual space, using a pagetable protection of @prot.
*/
static void *__vmalloc_node(unsigned long size, unsigned long align,
gfp_t gfp_mask, pgprot_t prot,
int node, void *caller)
{
struct vm_struct *area;
void *addr;
unsigned long real_size = size;
size = PAGE_ALIGN(size);
if (!size || (size >> PAGE_SHIFT) > totalram_pages)
return NULL;
/*分配相关的结构并对其初始化,在前面介绍过了*/
area = __get_vm_area_node(size, align, VM_ALLOC, VMALLOC_START,
VMALLOC_END, node, gfp_mask, caller);
if (!area)
return NULL;
/*分配物理空间,建立页表映射*/
addr = __vmalloc_area_node(area, gfp_mask, prot, node, caller);
/*
* 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;
}
struct page **pages;
unsigned int nr_pages, array_size, i;
/*需要减去一个页面,因为在分配结构的时候指定了多一个页面*/
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) {/*如果页面指针空间大于一个页面时,这个空间用非连续内存分配*/
pages = __vmalloc_node(array_size, 1, gfp_mask | __GFP_ZERO,
PAGE_KERNEL, node, caller);
area->flags |= VM_VPAGES;
} else {/*如果页面指针空间所占大小小于一个页面时,用slab机制分配这个空间*/
pages = kmalloc_node(array_size,
(gfp_mask & GFP_RECLAIM_MASK) | __GFP_ZERO,
node);
}
/*初始化area结构*/
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;
if (node < 0)/*分配物理页面空间*/
page = alloc_page(gfp_mask);
else
page = alloc_pages_node(node, gfp_mask, 0);
if (unlikely(!page)) {
/* Successfully allocated i pages, free them in __vunmap() */
area->nr_pages = i;
goto fail;
}
area->pages[i] = page;/*初始化area中page数组*/
}
/*因为非连续区间没有建立页表机制,在这里需要建立他*/
if (map_vm_area(area, prot, &pages))
goto fail;
return area->addr;/*返回线性地址*/
fail:
vfree(area->addr);
return NULL;
}
其中map_vm_area()建立页表映射机制的实现就是依次对pgd、pud、pmd、pte的设置。
二、非连续内存区的释放
调用vfree()函数实现
/**
* vfree - release memory allocated by vmalloc()
* @addr: memory base address
*
* Free the virtually continuous memory area starting at @addr, as
* obtained from vmalloc(), vmalloc_32() or __vmalloc(). If @addr is
* NULL, no operation is performed.
*
* Must not be called in interrupt context.
*/
void vfree(const void *addr)
{
BUG_ON(in_interrupt());
/*调试用*/
kmemleak_free(addr);
/*释放工作*/
__vunmap(addr, 1);
}
static void __vunmap(const void *addr, int deallocate_pages)
{
struct vm_struct *area;
if (!addr)
return;
if ((PAGE_SIZE-1) & (unsigned long)addr) {
WARN(1, KERN_ERR "Trying to vfree() bad address (%p)\n", addr);
return;
}
/*从vlist链表和红黑树中移除指定地址的线性区间*/
area = remove_vm_area(addr);
if (unlikely(!area)) {
WARN(1, KERN_ERR "Trying to vfree() nonexistent vm area (%p)\n",
addr);
return;
}
debug_check_no_locks_freed(addr, area->size);
debug_check_no_obj_freed(addr, area->size);
if (deallocate_pages) {
int i;
for (i = 0; i < area->nr_pages; i++) {/*每次释放一个页面*/
struct page *page = area->pages[i];
BUG_ON(!page);
__free_page(page);
}
if (area->flags & VM_VPAGES)/*在创建非连续区间时,如果页面
指针所占的空间大于一个页面时,从非连续内存区间
中分配。所以这里也就从相应的释放*/
vfree(area->pages);
else
kfree(area->pages);/*从slab中释放*/
}
kfree(area);/*释放area*/
return;
}
/**
* remove_vm_area - find and remove a continuous kernel virtual area
* @addr: base address
*
* Search for the kernel VM area starting at @addr, and remove it.
* This function returns the found VM area, but using it is NOT safe
* on SMP machines, except for its size or flags.
*/
struct vm_struct *remove_vm_area(const void *addr)
{
struct vmap_area *va;
/*从红黑树种查找而不是链表,为了效率起见*/
va = find_vmap_area((unsigned long)addr);
if (va && va->flags & VM_VM_AREA) {
struct vm_struct *vm = va->private;
struct vm_struct *tmp, **p;
/*
* remove from list and disallow access to this vm_struct
* before unmap. (address range confliction is maintained by
* vmap.)
*/
write_lock(&vmlist_lock);
/*从链表中找到,然后删除*/
for (p = &vmlist; (tmp = *p) != vm; p = &tmp->next)
;
*p = tmp->next;
write_unlock(&vmlist_lock);
/*调试用*/
vmap_debug_free_range(va->va_start, va->va_end);
/*从红黑树中删除*/
free_unmap_vmap_area(va);
vm->size -= PAGE_SIZE;
return vm;
}
return NULL;
}
总结:linux高端内存非连续区的整体描述以及其分配和释放基本就总结完了。总结的只是一个大概的原理框架,不过根据这个框架对细节的了解应该不难。另外,其中涉及到伙伴系统、slab机制等部分需要再做分析和总结。