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机制等部分需要再做分析和总结。

你可能感兴趣的:(linux,struct,null,alignment,recursion,Allocation)