Linux内核学习:kmalloc

来源:互联网 发布:免费淘宝客推广软件 编辑:程序博客网 时间:2024/06/05 20:11

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 CACHEreturn NULL;found:#ifdef CONFIG_ZONE_DMAif (flags & GFP_DMA)cachep = malloc_sizes[i].cs_dmacachep;else#endifcachep = 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)#endifCACHE(64)#if L1_CACHE_BYTES < 64CACHE(96)#endifCACHE(128)#if L1_CACHE_BYTES < 128CACHE(192)#endifCACHE(256)CACHE(512)CACHE(1024)CACHE(2048)CACHE(4096)CACHE(8192)CACHE(16384)CACHE(32768)CACHE(65536)CACHE(131072)#if KMALLOC_MAX_SIZE >= 262144CACHE(262144)#endif#if KMALLOC_MAX_SIZE >= 524288CACHE(524288)#endif#if KMALLOC_MAX_SIZE >= 1048576CACHE(1048576)#endif#if KMALLOC_MAX_SIZE >= 2097152CACHE(2097152)#endif#if KMALLOC_MAX_SIZE >= 4194304CACHE(4194304)#endif#if KMALLOC_MAX_SIZE >= 8388608CACHE(8388608)#endif#if KMALLOC_MAX_SIZE >= 16777216CACHE(16777216)#endif#if KMALLOC_MAX_SIZE >= 33554432CACHE(33554432)#endif#undef CACHEreturn 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_DMAstruct 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;
看注释就行了,不解释。

原创粉丝点击