slab下kmalloc内核函数实现

文章目录

  • kmalloc的整体实现
  • 获取高速缓存
    • 高速缓存
    • 获取index
  • 总结

https://blog.csdn.net/qq_41683305/article/details/124554490,在这篇文章中,我们介绍了伙伴算法、slab机制和常见的内存管理函数,接下来,我们看看kmalloc内核函数的具体实现。

kmalloc() 分配连续的物理地址,用于小内存分配。get_free_page() 分配连续的物理地址,用于整页分配。在slab机制下(还有slob、slub),kmalloc() 函数是基于 slab 实现的。slab 是为分配小内存提供的一种高效机制。但 slab 这种分配机制又不是独立的,它本身也是在页分配器的基础上来划分更细粒度的内存供调用者使用。也就是说系统先用页分配器分配以页为最小单位的连续物理地址,然后 kmalloc() 再在这上面根据调用者的需要进行切分。

我们可以调用kmalloc获取slab中未分配的对象,比如kmalloc(30,GFP_KERNAL),系统会从slab中分配一个32字节的对象。

kmalloc的整体实现

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);
}
  • __builtin_constant_p表示传入的是否为一个实数,gcc编译器会做这个判断,如果是一个确定的实数而非变量,那么它返回true,主要用于编译优化的处理。
  • 如果是实数,那么会判断size是否大于KMALLOC_MAX_CACHE_SIZE,此值表示的是系统创建的slab cache(slab创建的对象)的最大值,系统为kmalloc预先创建了很多大小不同的kmem cache,用于内存分配。这里的含义就是如果内存申请超过此值,那么直接使用 kmalloc_large进行大内存分配,实际上最终会调用页分配器去分配内存,而不是使用slab分配器
  • 如果没有大于KMALLOC_MAX_CACHE_SIZE,会调用__kmalloc来进行分配内存。

我们看的是slab下kmalloc的实现,所以。我们看mm/slab.c的__kmalloc,slob.c,slub.c也有该函数的实现。
slab下kmalloc内核函数实现_第1张图片

void *__kmalloc(size_t size, gfp_t flags)
{
	return __do_kmalloc(size, flags, _RET_IP_);
}

/**
 * __do_kmalloc - allocate memory
 * @size: how many bytes of memory are required.
 * @flags: the type of memory to allocate (see kmalloc).
 * @caller: function caller for debug tracking of the caller
 */
static __always_inline void *__do_kmalloc(size_t size, gfp_t flags,
					  unsigned long caller)
{
	struct kmem_cache *cachep;
	void *ret;

	if (unlikely(size > KMALLOC_MAX_CACHE_SIZE))
		return NULL;
	cachep = kmalloc_slab(size, flags);
	if (unlikely(ZERO_OR_NULL_PTR(cachep)))
		return cachep;
	ret = slab_alloc(cachep, flags, caller);

	kasan_kmalloc(cachep, ret, size, flags);
	trace_kmalloc(caller, ret,
		      size, cachep->size, flags);

	return ret;
}

在__do_kmalloc会先调用kmalloc_slab找到一个高速缓存cachep,然后调用slab_alloc从高速缓存cachep分配一个对象,最后返回。kasan_kmalloc和trace_kmalloc是内存调试用的,我们可以忽略。

获取高速缓存

接下来我们分析怎么得到一个高速缓存的,kmalloc_slab的实现在mm/slab_common.c中。

/*
 * Find the kmem_cache structure that serves a given size of
 * allocation
 */
struct kmem_cache *kmalloc_slab(size_t size, gfp_t flags)
{
	int index;

	if (size <= 192) {
		if (!size)
			return ZERO_SIZE_PTR;

		index = size_index[size_index_elem(size)];
	} else {
		if (unlikely(size > KMALLOC_MAX_CACHE_SIZE)) {
			WARN_ON(1);
			return NULL;
		}
		index = fls(size - 1);
	}

#ifdef CONFIG_ZONE_DMA
	if (unlikely((flags & GFP_DMA)))
		return kmalloc_dma_caches[index];

#endif
	return kmalloc_caches[index];
}

该函数会首先获得一个索引index,然后返回kmalloc_caches[index],说明kmalloc_caches[index]就是我们需要的高速缓存,kmalloc_caches是系统已经分配好的高速缓存,和slab机制中的高速缓存对应。

高速缓存

struct kmem_cache *kmalloc_caches[KMALLOC_SHIFT_HIGH + 1];

KMALLOC_SHIFT_HIGH 的值在slab、slob、slub下不同,我们看slab下所定义的值:

#define KMALLOC_SHIFT_HIGH	((MAX_ORDER + PAGE_SHIFT - 1) <= 25 ? \
				(MAX_ORDER + PAGE_SHIFT - 1) : 25)

/* Free memory management - zoned buddy allocator.  */
#ifndef CONFIG_FORCE_MAX_ZONEORDER
#define MAX_ORDER 11
#else
#define MAX_ORDER CONFIG_FORCE_MAX_ZONEORDER
#endif

/* PAGE_SHIFT determines the page size */
#define PAGE_SHIFT		12

如果没有定义CONFIG_FORCE_MAX_ZONEORDER,MAX_ORDER 是11,代表的是伙伴算法的11个块链表,MAX_ORDER 决定伙伴算法中的块链表数,PAGE_SHIFT与具体架构有关,我们看的是arm下的大小,PAGE_SHIFT 代表的是页大小,12位,共4KB,所以在arm下一个页大小为4KB。
在这种情况下,KMALLOC_SHIFT_HIGH 的大小是22。

kmalloc_caches是内核自己生成的,通过调用create_kmalloc_caches函数


/*
 * Create the kmalloc array. Some of the regular kmalloc arrays
 * may already have been created because they were needed to
 * enable allocations for slab creation.
 */
void __init create_kmalloc_caches(unsigned long flags)
{
	int i;

	for (i = KMALLOC_SHIFT_LOW; i <= KMALLOC_SHIFT_HIGH; i++) {
		if (!kmalloc_caches[i])
			new_kmalloc_cache(i, flags);

		/*
		 * Caches that are not of the two-to-the-power-of size.
		 * These have to be created immediately after the
		 * earlier power of two caches
		 */
		if (KMALLOC_MIN_SIZE <= 32 && !kmalloc_caches[1] && i == 6)
			new_kmalloc_cache(1, flags);
		if (KMALLOC_MIN_SIZE <= 64 && !kmalloc_caches[2] && i == 7)
			new_kmalloc_cache(2, flags);
	}

	/* Kmalloc array is now usable */
	slab_state = UP;

#ifdef CONFIG_ZONE_DMA
	for (i = 0; i <= KMALLOC_SHIFT_HIGH; i++) {
		struct kmem_cache *s = kmalloc_caches[i];

		if (s) {
			int size = kmalloc_size(i);
			char *n = kasprintf(GFP_NOWAIT,
				 "dma-kmalloc-%d", size);

			BUG_ON(!n);
			kmalloc_dma_caches[i] = create_kmalloc_cache(n,
				size, SLAB_CACHE_DMA | flags);
		}
	}
#endif
}

KMALLOC_SHIFT_LOW在slab中定义的是5

create_kmalloc_caches调用new_kmalloc_cache,new_kmalloc_cache调用create_kmalloc_cache最终创建高速缓存

static void __init new_kmalloc_cache(int idx, unsigned long flags)
{
	kmalloc_caches[idx] = create_kmalloc_cache(kmalloc_info[idx].name,
					kmalloc_info[idx].size, flags);
}

struct kmem_cache *__init create_kmalloc_cache(const char *name, size_t size,
				unsigned long flags)
{
	struct kmem_cache *s = kmem_cache_zalloc(kmem_cache, GFP_NOWAIT);

	if (!s)
		panic("Out of memory when creating slab %s\n", name);

	create_boot_cache(s, name, size, flags);
	list_add(&s->list, &slab_caches);
	s->refcount = 1;
	return s;
}

kmalloc_info有25个,定义了25个高速缓存的名字和大小

/*
 * kmalloc_info[] is to make slub_debug=,kmalloc-xx option work at boot time.
 * kmalloc_index() supports up to 2^26=64MB, so the final entry of the table is
 * kmalloc-67108864.
 */
static struct {
	const char *name;
	unsigned long size;
} const kmalloc_info[] __initconst = {
	{NULL,                      0},		{"kmalloc-96",             96},
	{"kmalloc-192",           192},		{"kmalloc-8",               8},
	{"kmalloc-16",             16},		{"kmalloc-32",             32},
	{"kmalloc-64",             64},		{"kmalloc-128",           128},
	{"kmalloc-256",           256},		{"kmalloc-512",           512},
	{"kmalloc-1024",         1024},		{"kmalloc-2048",         2048},
	{"kmalloc-4096",         4096},		{"kmalloc-8192",         8192},
	{"kmalloc-16384",       16384},		{"kmalloc-32768",       32768},
	{"kmalloc-65536",       65536},		{"kmalloc-131072",     131072},
	{"kmalloc-262144",     262144},		{"kmalloc-524288",     524288},
	{"kmalloc-1048576",   1048576},		{"kmalloc-2097152",   2097152},
	{"kmalloc-4194304",   4194304},		{"kmalloc-8388608",   8388608},
	{"kmalloc-16777216", 16777216},		{"kmalloc-33554432", 33554432},
	{"kmalloc-67108864", 67108864}
};

获取index

如果size小于等于192,index = size_index[size_index_elem(size)];

/* 8字节对齐 */
static inline int size_index_elem(size_t bytes)
{
	return (bytes - 1) / 8;
}

/*
 * Conversion table for small slabs sizes / 8 to the index in the
 * kmalloc array. This is necessary for slabs < 192 since we have non power
 * of two cache sizes there. The size of larger slabs can be determined using
 * fls.
 */
static s8 size_index[24] = {
	3,	/* 8 */
	4,	/* 16 */
	5,	/* 24 */
	5,	/* 32 */
	6,	/* 40 */
	6,	/* 48 */
	6,	/* 56 */
	6,	/* 64 */
	1,	/* 72 */
	1,	/* 80 */
	1,	/* 88 */
	1,	/* 96 */
	7,	/* 104 */
	7,	/* 112 */
	7,	/* 120 */
	7,	/* 128 */
	2,	/* 136 */
	2,	/* 144 */
	2,	/* 152 */
	2,	/* 160 */
	2,	/* 168 */
	2,	/* 176 */
	2,	/* 184 */
	2	/* 192 */
};

如果size大于192,index = fls(size - 1);fls函数返回的是size-1的高位0的个数,比如0x800000,返回的是0,如果是0x00000000,返回的就是32。

总结

kmalloc获取内存步骤如下:

  1. 判断分配的内存是不是大于slab所能分配对象的最大值,如果大于则调用kmalloc_large,使用页分配器分配,小于等于使用slab分配器分配内存,调用__kmalloc函数。
  2. __kmalloc调用__do_kmalloc来实现
  3. 在__do_kmalloc会先调用kmalloc_slab分配一个高速缓存,然后调用slab_alloc从高速缓存中分配一个对象,最后将该对象的首地址返回就是kmalloc获取的内存

你可能感兴趣的:(Linux内核设计与实现,kmalloc,linux内核)