1. kmalloc
kmalloc函数解析:
static __always_inline void *kmalloc(size_t size, gfp_t flags)
{
if (__builtin_constant_p(size)) {
#ifndef CONFIG_SLOB
unsigned int index;
#endif
if (size > KMALLOC_MAX_CACHE_SIZE)
return kmalloc_large(size, flags);//大于8k,从伙伴系统中分配内存
#ifndef CONFIG_SLOB
index = kmalloc_index(size);//计算出需要从slab的那个层分配内存
if (!index)
return ZERO_SIZE_PTR;
return kmem_cache_alloc_trace( //小于8k,从slab管理器中分配内存
kmalloc_caches[kmalloc_type(flags)][index],
flags, size);
#endif
}
return __kmalloc(size, flags);
}
kmalloc首先判断需要申请的内存大小,如果比较大,则调用kmalloc_large从伙伴系统中分配内存;如果比较小,则从slab管理器中分配内存。分界线是KMALLOC_MAX_CACHE_SIZE,根据我这边的实际情况,我们使用的是SLUB,也就是CONFIG_SLUB=y,所以定义如下:
#define KMALLOC_MAX_CACHE_SIZE (1UL << KMALLOC_SHIFT_HIGH)
#define KMALLOC_SHIFT_HIGH (PAGE_SHIFT + 1)
也就是,分界线是8k,大于8k会调用kmalloc_large从伙伴系统中分配内存;小于8k则会调用kmem_cache_alloc_trace从slab管理器中分配内存。
1.1 kmalloc_large(size, flags)
static __always_inline void *kmalloc_large(size_t size, gfp_t flags)
{
unsigned int order = get_order(size);//计算出从伙伴系统中申请内存的order
return kmalloc_order_trace(size, flags, order);//真正的分配函数
}
我们可以看到,kmalloc从伙伴系统分配页面首先通过get_order根据size计算出需要分配的order,然后调用kmalloc_order_trace进行页面的分配。着重看kmalloc_order_trace:
void *kmalloc_order_trace(size_t size, gfp_t flags, unsigned int order)
{
void *ret = kmalloc_order(size, flags, order);//从伙伴系统分配页面内存的核心函数
trace_kmalloc(_RET_IP_, ret, size, PAGE_SIZE << order, flags);//tracer跟踪kmalloc,debug用的
return ret;
}
kmalloc_order_trace函数的作用是调用kmalloc_order进行真正的页面分配,然后在trace_kmalloc函数中使用tracer动态跟踪kmalloc。trace_kmalloc函数我们不用关心,我们看kmalloc_order:
void *kmalloc_order(size_t size, gfp_t flags, unsigned int order)
{
void *ret = NULL;
struct page *page;
if (unlikely(flags & GFP_SLAB_BUG_MASK))
flags = kmalloc_fix_flags(flags);
flags |= __GFP_COMP;//复合页,可以知道在kfree中正确释放页面的分配顺序
page = alloc_pages(flags, order);//从伙伴系统中分配页面的函数
if (likely(page)) {//分支预测,分配成功
ret = page_address(page);//获取页面的虚拟地址
mod_lruvec_page_state(page, NR_SLAB_UNRECLAIMABLE_B,
PAGE_SIZE << order);
}
ret = kasan_kmalloc_large(ret, size, flags);//KASAN工具检测内存问题
/* As ret might get tagged, call kmemleak hook after KASAN. */
kmemleak_alloc(ret, size, 1, flags);//内存检测用到,注册内存对象
return ret;
}
kmalloc_order就是真正进行页面分配的函数,首先flags加上__GFP_COMP标志,这样子就是使用了复合页,然后通过alloc_pages从伙伴系统中分配页面,如果分配成功,再调用page_address函数,把page结构体转化为虚拟地址,最后返回虚拟地址。alloc_pages函数在前面的伙伴系统中已经讲过了,这里不再多说,把page结构体转化为虚拟地址的page_address:
static __always_inline void *lowmem_page_address(const struct page *page)
{
return page_to_virt(page);
}
#define page_to_virt(x) ({ \
__typeof__(x) __page = x; \
u64 __idx = ((u64)__page - VMEMMAP_START) / sizeof(struct page);\
u64 __addr = PAGE_OFFSET + (__idx * PAGE_SIZE); \
(void *)__tag_set((const void *)__addr, page_kasan_tag(__page));\
})
static inline const void *__tag_set(const void *addr, u8 tag)
{
u64 __addr = (u64)addr & ~__tag_shifted(0xff);
return (const void *)(__addr | __tag_shifted(tag));
}
#define __tag_shifted(tag) 0UL
#define VMEMMAP_START (-VMEMMAP_SIZE - SZ_2M)
根据这些宏,我们可以把lowmem_page_address函数看做:
static __always_inline void *lowmem_page_address(const struct page *page)
{
u64 __idx = ((u64)page - VMEMMAP_START) / sizeof(struct page);
u64 __addr = PAGE_OFFSET + (__idx * PAGE_SIZE);
return __addr ;
}
看到这里我有点懵逼,需要捋一捋。
1.2 kmem_cache_alloc_trace(kmalloc_caches[kmalloc_type(flags)][index],flags, size)
static __always_inline void *kmem_cache_alloc_trace(struct kmem_cache *s,
gfp_t flags, size_t size)
{
void *ret = kmem_cache_alloc(s, flags);
ret = kasan_kmalloc(s, ret, size, flags);
return ret;
}
kmem_cache_alloc_trace主要是调用kmem_cache_alloc从slab分配内存块,这个函数我们在之前的slab管理器中讲过,这里就不在多说了。