面试问题之:kmalloc,vmalloc,malloc区别分析

本文深入探讨了Linux内核中的kmalloc、vmalloc以及它们的封装kvmalloc,与用户空间的malloc的区别。kmalloc适用于小内存分配,依赖slab系统提供高效服务;vmalloc适用于大内存分配,虽然速度较慢但能确保虚拟地址连续;kvmalloc则是前两者结合,当kmalloc失败时使用vmalloc。malloc作为用户空间的内存分配函数,会在堆上分配内存,必要时通过系统调用扩展。
摘要由CSDN通过智能技术生成

先放结论

使用范围地址类型关键点场景
kmalloc内核空间物理地址连续基于slab缓存内核中常见结构体,及小块内存申请
vmalloc内核空间虚拟地址连续遍历页表申请内存大小不限
malloc用户空间虚拟地址连续内存不足时通过brk向系统申请空间大小不限

kvmalloc 则相当于对 kmalloc 和 vmalloc的封装,在kmalloc申请失败的情况下,尝试使用vmalloc申请。

kmalloc

kmalloc的接口

void * kmalloc(size_t size, gfp_t flags)
void kfree(const void *objp)

kmalloc的实现

kmalloc的完整调用链如下:

kmalloc
	__kmem_cache_alloc
		kmem_cache_alloc_one
			kmem_cache_grow
				kmem_getpages
					__get_free_pages
						alloc_pages

其中__kmem_cache_alloc 用于从slab申请内存,slab通过alloc_pages从伙伴系统获取内存。slab 相当于对象的高速缓存,一般会缓存固定大小的内存块或者内核常用的对象,这样做的好处既可以避免频繁申请小块内存带来的内存碎片,也省去了申请小块内存时的系统耗时,可以提高系统效率。

部分代码片段如下:

static cache_sizes_t cache_sizes[] = {
#if PAGE_SIZE == 4096
	{    32,	NULL, NULL},
#endif
	{    64,	NULL, NULL},
	{   128,	NULL, NULL},
	{   256,	NULL, NULL},
	{   512,	NULL, NULL},
	{  1024,	NULL, NULL},
	{  2048,	NULL, NULL},
	{  4096,	NULL, NULL},
	{  8192,	NULL, NULL},
	{ 16384,	NULL, NULL},
	{ 32768,	NULL, NULL},
	{ 65536,	NULL, NULL},
	{131072,	NULL, NULL},
	{     0,	NULL, NULL}
};

void * kmalloc (size_t size, int flags)
{
	cache_sizes_t *csizep = cache_sizes;

	for (; csizep->cs_size; csizep++) {
		if (size > csizep->cs_size)
			continue;
		return __kmem_cache_alloc(flags & GFP_DMA ?
			 csizep->cs_dmacachep : csizep->cs_cachep, flags);
	}
	return NULL;
}

kmalloc的总结

slab 主要用于缓存固定大小的页面,以及常见结构体,因此 kmalloc 主要用于申请 size 小于一个页面的内存空间,以及内核中各种常见结构体,比如vm_struct, task_struct等。

因为slab通过alloc_pages直接从伙伴内存分配器申请页面,所以能保证其物理空间是连续的。因为不经过页表转换,因此不存在虚拟地址空间的概念。

vmalloc

vmalloc的接口

void * vmalloc(unsigned long size)
void vfree(const void *addr)

vmalloc的实现

vmalloc的调用链如下:

vmalloc
	__vmalloc
		__vmalloc_area_pages
			alloc_area_pmd
				alloc_area_pte
					alloc_page
static inline void * vmalloc (unsigned long size)
{
	return __vmalloc(size, GFP_KERNEL | __GFP_HIGHMEM, PAGE_KERNEL);
}

void * __vmalloc (unsigned long size, int gfp_mask, pgprot_t prot)
{
	void * addr;
	struct vm_struct *area;

	size = PAGE_ALIGN(size);
	if (!size || (size >> PAGE_SHIFT) > num_physpages)
		return NULL;
	area = get_vm_area(size, VM_ALLOC);
	if (!area)
		return NULL;
	addr = area->addr;
	if (__vmalloc_area_pages(VMALLOC_VMADDR(addr), size, gfp_mask,
				 prot, NULL)) {
		vfree(addr);
		return NULL;
	}
	return addr;
}

static inline int __vmalloc_area_pages (unsigned long address,
					unsigned long size,
					int gfp_mask,
					pgprot_t prot,
					struct page ***pages)
{
	pgd_t * dir;
	unsigned long end = address + size;
	int ret;

	dir = pgd_offset_k(address);
	spin_lock(&init_mm.page_table_lock);
	do {
		pmd_t *pmd;
		
		pmd = pmd_alloc(&init_mm, dir, address);
		ret = -ENOMEM;
		if (!pmd)
			break;

		ret = -ENOMEM;
		if (alloc_area_pmd(pmd, address, end - address, gfp_mask, prot, pages))
			break;

		address = (address + PGDIR_SIZE) & PGDIR_MASK;
		dir++;

		ret = 0;
	} while (address && (address < end));	# 通过遍历页表,申请到足够的内存页
	spin_unlock(&init_mm.page_table_lock);
	flush_cache_all();
	return ret;
}

static inline int alloc_area_pte (pte_t * pte, unsigned long address,
			unsigned long size, int gfp_mask,
			pgprot_t prot, struct page ***pages)
{
	unsigned long end;

	address &= ~PMD_MASK;
	end = address + size;
	if (end > PMD_SIZE)
		end = PMD_SIZE;
	do {
		struct page * page;

		if (!pages) {
			spin_unlock(&init_mm.page_table_lock);
			page = alloc_page(gfp_mask);	// 申请内存
			spin_lock(&init_mm.page_table_lock);
		} else {
			page = (**pages);
			(*pages)++;

			/* Add a reference to the page so we can free later */
			if (page)
				atomic_inc(&page->count);

		}
		if (!pte_none(*pte))
			printk(KERN_ERR "alloc_area_pte: page already exists\n");
		if (!page)
			return -ENOMEM;
		set_pte(pte, mk_pte(page, prot));
		address += PAGE_SIZE;
		pte++;
	} while (address < end);
	return 0;
}

vmalloc的总结

从以上可以看出,vmalloc 实现原理为遍历页表,申请到足够的内存。

  • 因此,这里可以保证申请到的虚拟内存的地址是连续的,但无法保证申请到的物理地址是连续的。因此可以用于分配大内存。
  • 因为需要遍历页表,因此速度要比具有缓存的kmalloc慢。

kvmalloc

kvmalloc的接口

void * kvmalloc(unsigned long size)
void kvfree(const void *addr)

kvmalloc的实现

static inline __alloc_size(1) void *kvmalloc(size_t size, gfp_t flags)
{
	return kvmalloc_node(size, flags, NUMA_NO_NODE);
}

void *kvmalloc_node(size_t size, gfp_t flags, int node)
{
	gfp_t kmalloc_flags = flags;
	void *ret;
	
	if (size > PAGE_SIZE) {
		kmalloc_flags |= __GFP_NOWARN;

		if (!(kmalloc_flags & __GFP_RETRY_MAYFAIL))
			kmalloc_flags |= __GFP_NORETRY;

		/* nofail semantic is implemented by the vmalloc fallback */
		kmalloc_flags &= ~__GFP_NOFAIL;
	}
	
	// 使用 kmalloc申请内存
	ret = kmalloc_node(size, kmalloc_flags, node);
	
	if (ret || size <= PAGE_SIZE)	// 如果申请成功,或者小于一页,则直接返回
		return ret;
	// 使用 vmalloc 申请内存
	return __vmalloc_node_range(size, 1, VMALLOC_START, VMALLOC_END,
			flags, PAGE_KERNEL, VM_ALLOW_HUGE_VMAP,
			node, __builtin_return_address(0));
}

void kvfree(const void *addr)
{
	if (is_vmalloc_addr(addr))
		vfree(addr);
	else
		kfree(addr);
}

kvmalloc的总结

使用kvmalloc申请内存,会优先使用kmalloc申请内存,如果申请失败,则会尝试使用 vmalloc申请。

kvfree() 也可以代替 kfree() 和 vfree() 使用。

malloc

malloc的接口

void * malloc(size_t size)

malloc的实现

malloc 为glibc实现的用户空间的接口函数。用于从堆空间申请内存,其调用链为:

malloc
  __libc_malloc
      _int_malloc
        sysmalloc
          sbrk
            __brk
                sys_brk
                	do_brk
                		extend vm_area_struct

其中 malloc 在内存充足时直接返回,在内存不足时会调用系统调用sys_brk扩展可用的内存空间。此时就会陷入内核态。

malloc的总结

主要用于在用户空间申请一块指定大小的内存。当现有空间不足时,会通过系统调用向内核申请空间。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lylhw13_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值