kmalloc 系列函数是驱动者常用来向内核大管家申请内存的API,今天抽空扒一扒它是怎么工作的;首先看看它的原型
1. kmalloc () 函数
static __always_inline void *kmalloc(size_t size, gfp_t flags)
{
if (__builtin_constant_p(size)) {
if (size > KMALLOC_MAX_CACHE_SIZE)
return kmalloc_large(size, flags);
#ifndef CONFIG_SLOB
if (!(flags & GFP_DMA)) {
int index = kmalloc_index(size);
if (!index)
return ZERO_SIZE_PTR;
return kmem_cache_alloc_trace(kmalloc_caches[index],
flags, size);
}
#endif
}
return __kmalloc(size, flags);
}
代码路径:
include/linux/slab.h
函数的返回值和参数
返回值: void*,即为分配到的内存的首地址;
参数有两个,size ---》 申请内存的大小,字节(byte)为单位, flags ---》 所需要内存的标志,其中标志定义在 include\linux\gfp.h 中,详细的介绍,其中也有注释说明,内核中常用的是 “GFP_KERNEL”,表示 该片内存是在内核内部使用的。
2. kmalloc 函数通读
(1)__builtin_constant_p(x)
===》 编译时判断x是否为常数,如果为常数,则返回1, 否则返回0
(2)宏 KMALLOC_MAX_CACHE_SIZE
===》
#define KMALLOC_MAX_CACHE_SIZE (1UL << KMALLOC_SHIFT_HIGH)
关于 KMALLOC_SHIFT_HIGH
#ifdef CONFIG_SLAB
.........
#define KMALLOC_SHIFT_HIGH ((MAX_ORDER + PAGE_SHIFT - 1) <= 25 ? \
(MAX_ORDER + PAGE_SHIFT - 1) : 25)
.........
#endif
#endif
#ifdef CONFIG_SLUB
.........
#define KMALLOC_SHIFT_HIGH (PAGE_SHIFT + 1)
........
#endif
#endif
#ifdef CONFIG_SLOB
........
#define KMALLOC_SHIFT_HIGH PAGE_SHIFT
........
#endif
#endif
定义了 CONFIG_SLUB,SLAB 和 SLOB 均未定义; 因此,使用的是宏:
#define KMALLOC_SHIFT_HIGH (PAGE_SHIFT + 1)
PAGE_SHIFT 定义如下:
arch/arm/include/asm/page.h:14:#define PAGE_SHIFT 12
arch/arm64/include/asm/page.h:24:#define PAGE_SHIFT 16
arch/arm64/include/asm/page.h:26:#define PAGE_SHIFT 1
此处是arm,因此 PAGE_SHIFT = 12, KMALLOC_SHIFT_HIGH =PAGE_SHIFT + 1 = 12 + 1 = 13, KMALLOC_MAX_CACHE_SIZE = (1 << 13 ) = 8192 bits = 8K bytes。
打印的数据和这里吻合:
按照程序逻辑,当所需的内存不大于8129 bytes(8M)的时候,调用 __kmalloc(size, flags) 进行内存分配;当所需内存大于8129 bytes(8M)的时候,调用 kmalloc_large(size, flags) 进行内存分配。
3. __kmalloc(size, flags)函数
根据之前的记录,这里应该是调用的mm/slub.c 中__kmalloc(),因为配置了 CONFIG_SLUB 配置项,而没有配置 CONFIG_SLAB 和 CONFIG_SLOB,因此 slob.c 和 slab.c 两个文件不会被编译。
源码:
void *__kmalloc(size_t size, gfp_t flags)
{
struct kmem_cache *s;
void *ret;
if (unlikely(size > KMALLOC_MAX_CACHE_SIZE))
return kmalloc_large(size, flags);
s = kmalloc_slab(size, flags);
if (unlikely(ZERO_OR_NULL_PTR(s)))
return s;
ret = slab_alloc(s, flags, _RET_IP_);
trace_kmalloc(_RET_IP_, ret, size, s->size, flags);
ret = kasan_kmalloc(s, ret, size, flags);
return ret;
}
EXPORT_SYMBOL(__kmalloc);
这里也会判断大小,超过 8192 bits,就会调用其 kmalloc_large() 函数,未超过,则调用 kmalloc_slab() 来进行内存大小的分配;如果分配失败,则调用 slab_alloc()。
(1)kmalloc_slab()
路径:mm\slab_common.c
struct kmem_cache *kmalloc_slab(size_t size, gfp_t flags)
{
int index;
if (unlikely(size > KMALLOC_MAX_SIZE)) {
WARN_ON_ONCE(!(flags & __GFP_NOWARN));
return NULL;
}
if (size <= 192) {
if (!size)
return ZERO_SIZE_PTR;
index = size_index[size_index_elem(size)];
} else
index = fls(size - 1);
#ifdef CONFIG_ZONE_DMA
if (unlikely((flags & GFP_DMA)))
return kmalloc_dma_caches[index];
#endif
return kmalloc_caches[index];
}
A. 若所需要的内存大于 KMALLOC_MAX_SIZE 则 返回NULL
KMALLOC_MAX_SIZE = ?
#define KMALLOC_MAX_SIZE (1UL << KMALLOC_SHIFT_MAX)
#define KMALLOC_SHIFT_MAX (MAX_ORDER + PAGE_SHIFT)
如上2.(2)所述,page shift 为12, 那max order呢?
/* Free memory management - zoned buddy allocator. */
#ifndef CONFIG_FORCE_MAX_ZONEORDER
#define MAX_ORDER 11
#else
#define MAX_ORDER CONFIG_FORCE_MAX_ZONEORDER
#endif
从上面的代码可看出,这取决于 CONFIG_FORCE_MAX_ZONEORDER 是否配置了,查看.config文件,
有该配置的定义:
CONFIG_FORCE_MAX_ZONEORDER=11,
因此 max order 定义为11,
KMALLOC_MAX_SIZE = 1<<23 = 8M,kmalloc_slab() 函数中,最大能分配的内存大小,就是不超过8M。
B. index 大小 :如果所需的内存大小不大于192,从数组size_index[] 中获取;否则,通过 fls()进行计算。
C. 分配方法 kmalloc_caches[] 数组或者 kmalloc_dma_caches[] 数组。
从 kmalloc_dma_caches[] 数组 分配内存有两个条件:配置了可以从DMA 分配内存 且 需要内存的flags 是 GFP_DMA(内存需要者,需要的就是DMA 上的内存)。
数组 kmalloc_caches[] 的来源
struct kmem_cache *kmalloc_caches[KMALLOC_SHIFT_HIGH + 1];
EXPORT_SYMBOL(kmalloc_caches);
KMALLOC_SHIFT_HIGH 大小上述已确定为13,因此 kmalloc_caches[] 指针大小为 14;
(2)若 kmalloc_slab() 函数分配内存失败,则调用slab_alloc()进行内存分配,具体的梳理,后续呈上。
(3)对于大于8M大内存的分配——kmalloc_large()函数
路径:include\linux\slab.h
源码:
static __always_inline void *kmalloc_large(size_t size, gfp_t flags)
{
unsigned int order = get_order(size);
return kmalloc_order_trace(size, flags, order);
}
- get_order()
tatic inline __attribute_const__ int get_order(unsigned long size)
{
int lz;
asm ("nsau %0, %1" : "=r" (lz) : "r" ((size - 1) >> PAGE_SHIFT));
return 32 - lz;
}
此处应用汇编对值进行了计算,这个汇编没太看懂。
- kmalloc_order_trace(size_t size, gfp_t flags, unsigned int order)
static __always_inline void *
kmalloc_order_trace(size_t size, gfp_t flags, unsigned int order)
{
return kmalloc_order(size, flags, order);
}
void *kmalloc_order(size_t size, gfp_t flags, unsigned int order)
{
void *ret;
struct page *page;
flags |= __GFP_COMP;
page = alloc_kmem_pages(flags, order);
ret = page ? page_address(page) : NULL;
kmemleak_alloc(ret, size, 1, flags);
kasan_kmalloc_large(ret, size);
return ret;
}
EXPORT_SYMBOL(kmalloc_order);
从以上的流程大概可以看出,超过8M的时候,是从页(page)上获取内存的,具体的细节,后续梳理梳理。
4. 遗留的问题
(1) kmalloc_caches 结构体数组的初始化
(2) slab_alloc() 内存分配的过程梳理
(3)kmalloc_order() 未入口的page内存分配过程。