kmalloc的内核源码分析
kmalloc在内核中的定义如下:(include/linux/slab_def.h)
static __always_inline void *kmalloc(size_t size, gfp_t flags)
{
struct kmem_cache *cachep;
void *ret;
if (__builtin_constant_p(size)) {
int i = 0;
if (!size)
return ZERO_SIZE_PTR;
#define CACHE(x) \
if (size <= x) \
goto found; \
else \
i++;
#include <linux/kmalloc_sizes.h>
#undef CACHE
return NULL;
found:
#ifdef CONFIG_ZONE_DMA
if (flags & GFP_DMA)
cachep = malloc_sizes[i].cs_dmacachep;
else
#endif
cachep = malloc_sizes[i].cs_cachep;
ret = kmem_cache_alloc_trace(size, cachep, flags);
return ret;
}
return __kmalloc(size, flags);
}
首先看到的是kmalloc是基于kmem_cache的,这也就是说它是基于slab分配器的。好处是分配粒度可以小于一个页面,例如几十个字节;另外基于cache的分配,效率会很高。
接着看,__builtin_constant_p(exp)是gcc的内建函数,用于判断一个值是否为编译时常数,如果参数exp的值是常数,函数返回 1,否则返回 0。就是说,如果size不是常数,就调用__kmalloc(size, flags);去处理,这里不深入。
再往下,如果size == 0,那就是逗着玩呢,当特殊情况处理,返回ZERO_SIZE_PTR。ZERO_SIZE_PTR的定义如下:
/*
* ZERO_SIZE_PTR will be returned for zero sized kmalloc requests.
*
* Dereferencing ZERO_SIZE_PTR will lead to a distinct access fault.
*
* ZERO_SIZE_PTR can be passed to kfree though in the same way that NULL can.
* Both make kfree a no-op.
*/
#define ZERO_SIZE_PTR ((void *)16)
这让我想起来NULL在Linux中的定义(/usr/include/linux/stddef.h):
#undef NULL
#if defined(__cplusplus)
#define NULL 0
#else
#define NULL ((void *)0)
#endif
上面的定义意思是C++中NULL为0,而Linux C中,NULL为地址0所指向的内容。
而ZERO_SIZE_PTR就是地址16所指向的内容了,其具体会导致什么结果上面的注释都有,咱就不翻译了。
再往下,定义了宏CACHE(x),包含了头文件,又加了分支处理。这里算是见识了Linux内核的高手风范,反正我是头一次见到这种玩法,感觉挺新颖。
把中间的那段代码展开,变成下面这个样子:
#define CACHE(x) \
if (size <= x) \
goto found; \
else \
i++;
#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
#undef CACHE
return NULL;
found:
光看估计不好理解,试着给个值,比如申请size为500字节的内存空间,看看流程怎么走。
那么,对于一般的ARM架构,PAGE_SIZE就是4096,于是先执行CACHE(32)。在CACHE(32)内部执行时,判断500>32,于是i++,i变成1。
接着执行CACHE(64),500>64,于是i++,i变成2。
L1_CACHE_BYTES一般没那么小(大于128字节对于多数情况成立),所以,下面的跳转更常见:
接着执行CACHE(128),500>128,于是i++,i变成3。
接着执行CACHE(256),500>256,于是i++,i变成4。
接着执行CACHE(512),500<=512,于是goto found,i定格在4。
到了found:这里之后,我们看到它区分了DMA和非DMA的情况。我记得DMA物理内存是在固定的低地址空间(低16M物理地址空间),而一般的物理内存(ZONE_NORMAL)在16M~896M之间。
这里又冒出了一个malloc_sizes数组,看看它的定义:
/*
* These are the default caches for kmalloc. Custom caches can have other sizes.
*/
struct cache_sizes malloc_sizes[] = {
#define CACHE(x) { .cs_size = (x) },
#include <linux/kmalloc_sizes.h>
CACHE(ULONG_MAX)
#undef CACHE
};
好吧,还得了解一下cache_sizes是怎么回事:
/* Size description struct for general caches. */
struct cache_sizes {
size_t cs_size;
struct kmem_cache *cs_cachep;
#ifdef CONFIG_ZONE_DMA
struct kmem_cache *cs_dmacachep;
#endif
};
extern struct cache_sizes malloc_sizes[];
这下都能对上了,对于我们前面设定的500字节,到了这里,就变成了
cachep = malloc_sizes[4].cs_cachep;
而这个malloc_sizes[]数组展开就是这样的:
struct cache_sizes malloc_sizes[] = {
#define CACHE(x) { .cs_size = (x) },
{ .cs_size = (32) },
{ .cs_size = (64) },
{ .cs_size = (128) },
{ .cs_size = (256) },
{ .cs_size = (512) },
...
{ .cs_size = (ULONG_MAX) },
#undef CACHE
};
所以呢,index为4的情况就是.cs_size = (512)。
这样,搞了半天,还只是根据 “申请的size” 找对应的 “应申请size”。
实际的申请还是由最后这句来完成,而它的展开分析貌似更复杂。
ret = kmem_cache_alloc_trace(size, cachep, flags);
kmalloc的使用
说真的,以前还真没仔细想过什么情况下用kmalloc?
后来注意到kmalloc在内核源码中是怎么使用的,才有所感悟。
随便找个例子看看:
int cris_io_interface_register_watcher(void (*notify)(const unsigned int gpio_in_available,
const unsigned int gpio_out_available,
const unsigned char pa_available,
const unsigned char pb_available))
{
struct watcher *w;
(void)cris_io_interface_init();
if (NULL == notify) {
return -EINVAL;
}
w = kmalloc(sizeof(*w), GFP_KERNEL);
if (!w) {
return -ENOMEM;
w是一个指向结构体的指针,为其分配内存空间时使用了kmalloc。为什么不直接这样写?
struct watcher w;
这样就是局部变量,分配在内核栈上。可是内核栈空间有限,需要省着用啊。而使用了kmalloc,应该是分配在堆上了,堆的空间大的很,随便用。
再来看一个例子:
static int ahash_op_unaligned(struct ahash_request *req,
int (*op)(struct ahash_request *))
{
struct crypto_ahash *tfm = crypto_ahash_reqtfm(req);
unsigned long alignmask = crypto_ahash_alignmask(tfm);
unsigned int ds = crypto_ahash_digestsize(tfm);
struct ahash_request_priv *priv;
int err;
priv = kmalloc(sizeof(*priv) + ahash_align_buffer_size(ds, alignmask),
(req->base.flags & CRYPTO_TFM_REQ_MAY_SLEEP) ?
GFP_KERNEL : GFP_ATOMIC);
可见,简单的变量alignmask, ds, err等都是栈上直接分配,而结构体变量就定义个指针再用kmalloc堆上申请。看来基本原则就是为了少占用内核栈。
那么为什么不都在堆上申请?既然堆的空间大。我的理解是栈的效率高啊,“这体现在,有专门的寄存器指向栈所在的地址,有专门的机器指令完成数据入栈出栈的操作。这种机制的特点是效率高,支持的数据有限,一般是整数,指针,浮点数等系统直接支持的数据类型,并不直接支持其他的数据结构”(这段话是网上找的)。“在过去,堆内存管理器是实际的规范,但是其性能会受到内存碎片和内存回收需求的影响”(这段话也是网上找的)。所以呢,简单的需要高速存取的数据分配给栈,别超过了内核栈的限制就行;复杂的数据结构,比如结构体,就用堆分配,即kmalloc;特别大的数据结构,又不需要物理地址连续的(比如链表),用vmalloc分配。
题外话:
记得有面试题问malloc, calloc和realloc什么关系,在/usr/include/malloc.h中发现如下申明:
/* Allocate SIZE bytes of memory. */
extern void *malloc (size_t __size) __THROW __attribute_malloc__ __wur;
/* Allocate NMEMB elements of SIZE bytes each, all initialized to 0. */
extern void *calloc (size_t __nmemb, size_t __size)
__THROW __attribute_malloc__ __wur;
/* Re-allocate the previously allocated block in __ptr, making the new
block SIZE bytes long. */
/* __attribute_malloc__ is not used, because if realloc returns
the same pointer that was passed to it, aliasing needs to be allowed
between objects pointed by the old and new pointers. */
extern void *realloc (void *__ptr, size_t __size)
__THROW __attribute_warn_unused_result__;
/* Free a block allocated by `malloc', `realloc' or `calloc'. */
extern void free (void *__ptr) __THROW;
/* Free a block allocated by `calloc'. */
extern void cfree (void *__ptr) __THROW;
看注释就行了,不解释。