文章目录
先放结论
使用范围 | 地址类型 | 关键点 | 场景 | |
---|---|---|---|---|
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的总结
主要用于在用户空间申请一块指定大小的内存。当现有空间不足时,会通过系统调用向内核申请空间。