linux高階記憶體管理之非連續記憶體區(分配和釋放)
阿新 • • 發佈:2019-01-25
前面總結了非連續記憶體區域的核心描述,接著看看他的分配和釋放。
一、非連續記憶體區的分配
不管是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機制等部分需要再做分析和總結。