1. 程式人生 > >linux高階記憶體管理之非連續記憶體區(分配和釋放)

linux高階記憶體管理之非連續記憶體區(分配和釋放)

前面總結了非連續記憶體區域的核心描述,接著看看他的分配和釋放。

一、非連續記憶體區的分配

不管是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()建立頁表對映機制的實現就是依次對pgdpudpmdpte的設定。

二、非連續記憶體區的釋放

呼叫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機制等部分需要再做分析和總結。