Linux内核源码分析-kmalloc与vmalloc

1.kamlloc

static __always_inline void *kmalloc(size_t size, gfp_t flags)
{
	struct kmem_cache *cachep;
	void *ret;

	if (__builtin_constant_p(size)) {  //__builtin_constant_p 是编译器gcc内置函数,用于判断一个值是否为编译时常量,如果是常数,返回1,否则返回0
		int i = 0;

		if (!size)
			return ZERO_SIZE_PTR;

#define CACHE(x) \
		if (size <= x) \
			goto found; \
		else \
			i++;
#include      //这里查询申请的size在哪个范围  从32乘2递增
#undef CACHE
		return NULL;
found:
#ifdef CONFIG_ZONE_DMA
		if (flags & GFP_DMA)       //如果定义了dma,并且设置了dma标志则优先从dma_cache了申请。malloc_sizes的初始化在slab.c里
			cachep = malloc_sizes[i].cs_dmacachep;
		else
#endif
			cachep = malloc_sizes[i].cs_cachep;    //从指定的cache链表分配内存,不浪费空间。

		ret = kmem_cache_alloc_trace(size, cachep, flags);

		return ret;
	}
	return __kmalloc(size, flags);
}
kmem_cache_alloc_trace(size_t size, struct kmem_cache *cachep, gfp_t flags)
{
	return kmem_cache_alloc(cachep, flags);   //调用slab_alloc
}
void *kmem_cache_alloc(struct kmem_cache *s, gfp_t gfpflags)
{
    void *ret = slab_alloc(s, gfpflags, NUMA_NO_NODE, _RET_IP_);

    trace_kmem_cache_alloc(_RET_IP_, ret, s->objsize, s->size, gfpflags);   //跟踪调试会用到

    return ret;
}

kmalloc最后主要调用了slab,下面看看 中的内容:

// 
#if (PAGE_SIZE == 4096)
	CACHE(32)
#endif
	CACHE(64)
#if L1_CACHE_BYTES < 64
	CACHE(96)
#endif
	CACHE(128)
#if L1_CACHE_BYTES < 128
	CACHE(192)
#endif
	CACHE(256)
	CACHE(512)
	CACHE(1024)
	CACHE(2048)
	CACHE(4096)
	CACHE(8192)
	CACHE(16384)
	CACHE(32768)
	CACHE(65536)
	CACHE(131072)
#if KMALLOC_MAX_SIZE >= 262144
	CACHE(262144)
#endif
#if KMALLOC_MAX_SIZE >= 524288
	CACHE(524288)
#endif
#if KMALLOC_MAX_SIZE >= 1048576
	CACHE(1048576)
#endif
#if KMALLOC_MAX_SIZE >= 2097152
	CACHE(2097152)
#endif
#if KMALLOC_MAX_SIZE >= 4194304
	CACHE(4194304)
#endif
#if KMALLOC_MAX_SIZE >= 8388608
	CACHE(8388608)
#endif
#if KMALLOC_MAX_SIZE >= 16777216
	CACHE(16777216)
#endif
#if KMALLOC_MAX_SIZE >= 33554432
	CACHE(33554432)
#endif

2.vmalloc

void *vmalloc(unsigned long size)
{
	return __vmalloc_node_flags(size, -1, GFP_KERNEL | __GFP_HIGHMEM);
}
static inline void *__vmalloc_node_flags(unsigned long size,
					int node, gfp_t flags)
{
	return __vmalloc_node(size, 1, flags, PAGE_KERNEL,
					node, __builtin_return_address(0));
}
static void *__vmalloc_node(unsigned long size, unsigned long align,
			    gfp_t gfp_mask, pgprot_t prot,
			    int node, void *caller)
{
	return __vmalloc_node_range(size, align, VMALLOC_START, VMALLOC_END,
				gfp_mask, prot, node, caller);
}
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, void *caller)
{
	struct vm_struct *area;
	void *addr;
	unsigned long real_size = size;

	size = PAGE_ALIGN(size);   //对size进行页面对齐设置
	if (!size || (size >> PAGE_SHIFT) > totalram_pages)   //检测size合法性
		goto fail;

	area = __get_vm_area_node(size, align, VM_ALLOC | VM_UNLIST,   //在高端内存区分配一个vm_struct并初始化
				  start, end, node, gfp_mask, caller);
	if (!area)
		goto fail;

	addr = __vmalloc_area_node(area, gfp_mask, prot, node, caller);   //为area分配管理page的数组,并通过伙伴系统分配物理页面
	if (!addr)   //上面可以看出来,高端内存在分配时就分配物理内存,用户空间等待访问时才分配物理内存
		return NULL;

	/*
	 * In this function, newly allocated vm_struct is not added
	 * to vmlist at __get_vm_area_node(). so, it is added here.
	 */
	insert_vmalloc_vmlist(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;
}

    上述函数主要调用了__get_vm_area_node,与__vmalloc_area_node函数,实现分配vm_struct以及分配物理页面,下面分析这两个函数:

static struct vm_struct *__get_vm_area_node(unsigned long size,
		unsigned long align, unsigned long flags, unsigned long start,
		unsigned long end, int node, gfp_t gfp_mask, void *caller)
{
	struct vmap_area *va;
	struct vm_struct *area;

	BUG_ON(in_interrupt());    //不能用于中断上下文
	if (flags & VM_IOREMAP) {   //物理页面的获取并不能保证可以立刻得到,如果页面不足会导致休眠
		int bit = fls(size);         //所以不能用于中断上下文

		if (bit > IOREMAP_MAX_ORDER)
			bit = IOREMAP_MAX_ORDER;
		else if (bit < PAGE_SHIFT)
			bit = PAGE_SHIFT;

		align = 1ul << bit;
	}

	size = PAGE_ALIGN(size);  //对size 进行对齐
	if (unlikely(!size))
		return NULL;

	area = kzalloc_node(sizeof(*area), gfp_mask & GFP_RECLAIM_MASK, node);   //分配一个vm_alloc结构
	if (unlikely(!area))
		return NULL;

	/*
	 * We always allocate a guard page.
	 */
	size += PAGE_SIZE;  //size加了一个page的大小主要做各个区间之间的隔离

	va = alloc_vmap_area(size, align, start, end, node, gfp_mask);  //分配一块虚拟内存地址空间
	if (IS_ERR(va)) {                            //上面与用户空间中的malloc相似,仅仅分配地址空间
		kfree(area);
		return NULL;
	}

	/*
	 * When this function is called from __vmalloc_node_range,
	 * we do not add vm_struct to vmlist here to avoid
	 * accessing uninitialized members of vm_struct such as
	 * pages and nr_pages fields. They will be set later.
	 * To distinguish it from others, we use a VM_UNLIST flag.
	 */
	if (flags & VM_UNLIST)
		setup_vmalloc_vm(area, va, flags, caller);   //初始化area
	else
		insert_vmalloc_vm(area, va, flags, caller);

	return area;
}
static void *__vmalloc_area_node(struct vm_struct *area, gfp_t gfp_mask,
				 pgprot_t prot, int node, 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 *));      //需要一个数组管理涉及到的物理页面地址即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, nested_gfp|__GFP_HIGHMEM,
				PAGE_KERNEL, node, caller);
		area->flags |= VM_VPAGES;
	} else {
		pages = kmalloc_node(array_size, nested_gfp, node);  //page指针数组的大小小于一个页面的大小,则直接在线性映射区直接分配,更快
	}
	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;
		}
		area->pages[i] = page;
	}

	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;
}

   从上面可以看出,高端内存的分配,每一个虚拟页分别分配一个物理页,虚拟内存分配时是连续的,但是在物理内存上并不是连续的。虚拟内存分配时调用alloc_vmap_area分配的是一块内存,物理内存分配时是分别按页分配的,不是整块分配的。

   alloc_page就是分配页,上一节中有讲到。

总结:

通过上面源码分析可以看出:
   kmalloc是建立在slab分配器的基础上的,分配在线性映射区中。虚拟空间与物理内存空间都连续。

   vmalloc最终是调用alloc_page实现的,分配在高端内存中,虚拟内存连续,但是物理内存空间不连续。

你可能感兴趣的:(嵌入式)