Linux内核学习:kmalloc

12 篇文章 0 订阅
2 篇文章 0 订阅

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;
看注释就行了,不解释。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值