上次说了 slab 的主要数据结构,这次从初始化开始进行源码剖析。
slab 的初始化,当然是从内核启动就开始了。内核启动的 start_kernel() 函数:
//内核的启动程序啊:)
asmlinkage void __init start_kernel(void)
{
...
mem_init(); //内存相关初始化
kmem_cache_init(); //slab 初始化
...
}
其中调用 kmem_cache_init() 函数,这就是要进行 slab 的初始化。
/*
* Initialisation. Called after the page allocator have been initialised and
* before smp_init().
*/
//这个函数用来建立通用缓存器,否则我们无法使用kmalloc
void __init kmem_cache_init(void)
{
size_t left_over;
struct cache_sizes *sizes;
struct cache_names *names;
int i;
int order;
int node;
//如果节点数目只有1个,那么就不能使用其他节点的shared cache
if (num_possible_nodes() == 1) //貌似当前这个版本还不支持这个,参见#define num_possible_nodes() 1
use_alien_caches = 0; //后续linux版本会支持
//在slab初始化好之前,无法通过kmalloc分配初始化过程中的一些必要对象,只能使用静态的全局变量
//待slab初始化后期,再使用kmalloc动态分配对象替换全局变量!!
//如前所述,先借用 initkem_list3 代替要用到的三链,每个节点对应一组三链
//initkmem_list3 是个三链数组,这里循环初始化每个节点的三链
for (i = 0; i < NUM_INIT_LISTS; i++) { //FIXME: NUM_INIT_LIST 是怎么求的,为什么等于2 * MAX_NODES+1 ? 有的版本是 3 * MAX_NODES
kmem_list3_init(&initkmem_list3[i]);
if (i < MAX_NUMNODES)
//全局变量cache_cache指向的slab cache包含所有的kmem_cache对象,不包含cache_cache本身
//这里初始化所有内存节点kmem_cache的slab三链为空
cache_cache.nodelists[i] = NULL;
}
/*
* Fragmentation resistance on low memory - only use bigger
* page orders on machines with more than 32MB of memory.
*/
//全部变量slab_break_gfp_order为每个slab最多占用几个页面,用来减少碎片。
//总结起来意思就是是: (1)如果物理可用内存大于32MB,也就是可用内存充裕的时候,BREAK_GFP_ORDER_HI这个宏的值是1,
//这个时候每个slab最多占用两个页面,不过此时不能横跨3个页面,除非对象大小大于8192K时才可以(一个页面大小是4K,也就是4096);
//(2)如果可用内存不大于32MB,那么BREAK_GFP_ORDER_HI值为0,最多也就是允许一个页面,除非对象超了,否则不能横跨
if (num_physpages > (32 << 20) >> PAGE_SHIFT)
slab_break_gfp_order = BREAK_GFP_ORDER_HI; //用来确定slab的最大大小
/* Bootstrap is tricky, because several objects are allocated
* from caches that do not exist yet:
* 1) initialize the cache_cache cache: it contains the struct
* kmem_cache structures of all caches, except cache_cache itself:
* cache_cache is statically allocated.
* Initially an __init data area is used for the head array and the
* kmem_list3 structures, it's replaced with a kmalloc allocated
* array at the end of the bootstrap.
* 2) Create the first kmalloc cache.
* The struct kmem_cache for the new cache is allocated normally.
* An __init data area is used for the head array.
* 3) Create the remaining kmalloc caches, with minimally sized
* head arrays.
* 4) Replace the __init data head arrays for cache_cache and the first
* kmalloc cache with kmalloc allocated arrays.
* 5) Replace the __init data for kmem_list3 for cache_cache and
* the other cache's with kmalloc allocated memory.
* 6) Resize the head arrays of the kmalloc caches to their final sizes.
*/
node = numa_node_id(); //获取节点id,取得的值为0,因为初始化程序单CPU执行,且是0号
/* 1) create the cache_cache */
//初始化cache_chain 为 kmem_cache 链表头部
INIT_LIST_HEAD(&cache_chain);
list_add(&cache_cache.next, &cache_chain);
//设置cache着色的偏移量基本值,也就是L1缓存行的大小
cache_cache.colour_off = cache_line_size(); //宏定义L1缓存行的大小 #define cache_line_size() L1_CACHE_BYTES
//初始化cache_cache的per-CPU cache,同样这里也不能使用kmalloc,需要使用静态分配的全局变量initarray_cache
cache_cache.array[smp_processor_id()] = &initarray_cache.cache;
//初始化slab链表,用全局变量,这里CACHE_CACHE值为0,是因为cache_cache就是系统第一个的缓存器
cache_cache.nodelists[node] = &initkmem_list3[CACHE_CACHE];
/*
* struct kmem_cache size depends on nr_node_ids, which
* can be less than MAX_NUMNODES.
*/
//buffer_size原指用来分配的对象大小,由于cache_cache是用来做 kmem_cache 的分配器的,所以 buffer_size 的大小就是 kmem_cache 的大小
//注意柔性数组的计算方法,nodelists不占据 kmem_cache的大小,所以要分开计算,并且注意nodelists数组在UMA架构只有一个节点,所以只有1个kmem_list3的指针
cache_cache.buffer_size = offsetof(struct kmem_cache, nodelists) +
nr_node_ids * sizeof(struct kmem_list3 *);
#if 0
#if DEBUG
cache_cache.obj_size = cache_cache.buffer_size;
#endif
#endif
//注意这里又一次计算了 buffer_size 的大小,通过这次计算将buffer_size以 缓存行 为单位进行上边界对齐
//计算分配的对象与cache line的大小对齐后的大小
cache_cache.buffer_size = ALIGN(cache_cache.buffer_size,
cache_line_size());
//计算对象大小的倒数,用于计算对象在slab中的索引
cache_cache.reciprocal_buffer_size =
reciprocal_value(cache_cache.buffer_size);
//计算cache_cache的剩余空间以及slab中对象的数目,order决定了slab的大小(PAGE_SIZEE<
for (order = 0; order < MAX_ORDER; order++) { //#define MAX_ORDER 11
cache_estimate(order, cache_cache.buffer_size, //buffer_size已经和cache line对齐过
cache_line_size(), 0, &left_over, &cache_cache.num); //计算cache_cache的对象数目
if (cache_cache.num) //num不为0意味着struct kmem_cache对象创建成功,退出,所以在此处就会确定order的大小,以赋值给gfporder
break;
}
BUG_ON(!cache_cache.num); //断言
cache_cache.gfporder = order; //gfporder表示本slab包含2^gfproder个页面,注意这里就从上面的order赋值给 gfporder
//colour_off 就是上面初始化的 L1_CACHE_BYTES,既然已经知道 L1 缓存行的大小,我们上步又计算出了浪费空间的大小
//那么用浪费空间的大小 / L1 缓存行的大小,就得出当前可用 colour 的数目,这个数目是累加且循环的,可以为0
cache_cache.colour = left_over / cache_cache.colour_off; //确定可用 colour 的数目,单位是 colour_off
//确定slab描述符以及kmem_bufctl_t数组 针对缓存行对齐后的大小
cache_cache.slab_size = ALIGN(cache_cache.num * sizeof(kmem_bufctl_t) +
sizeof(struct slab), cache_line_size());
/* 2+3) create the kmalloc caches */
sizes = malloc_sizes; //malloc_sizes数组保存着要分配的大小
names = cache_names; //cache_name保存cache名
/*
* Initialize the caches that provide memory for the array cache and the
* kmem_list3 structures first. Without this, further allocations will
* bug.
*/
//首先创建struct array_cache 和 struct kmem_list3 所用的通用缓存器general cache,它们是后续初始化动作的基础
//INDEX_AC是计算local cache所用的struct arraycache_init对象在kmalloc size中的索引,即属于哪一级别大小的general cache,创建此大小级别的cache为local cache所用
sizes[INDEX_AC].cs_cachep = kmem_cache_create(names[INDEX_AC].name,
sizes[INDEX_AC].cs_size,
ARCH_KMALLOC_MINALIGN,
ARCH_KMALLOC_FLAGS|SLAB_PANIC, //#define ARCH_KMALLOC_FLAGS SLAB_HWCACHE_ALIGN,已经对齐过的标记
NULL, NULL);
if (INDEX_AC != INDEX_L3) {
//如果struct kmem_list3 和 struct arraycache_init对应的kmalloc size索引不同,即大小属于不同的级别,
//则创建struct kmem_list3所用的cache,否则共用一个cache
sizes[INDEX_L3].cs_cachep =
kmem_cache_create(names[INDEX_L3].name,
sizes[INDEX_L3].cs_size,
ARCH_KMALLOC_MINALIGN,
ARCH_KMALLOC_FLAGS|SLAB_PANIC,
NULL, NULL);
}
slab_early_init = 0;//创建完上述两个通用缓存器后,slab early init阶段结束,在此之前,不允许创建外置式slab
//sizes->cs_size 初值为是malloc_sizes[0],值应该是从32开始
while (sizes->cs_size != ULONG_MAX) { //循环创建kmalloc各级别的通用缓存器,ULONG_MAX 是最大索引值,
/*
* For performance, all the general caches are L1 aligned.
* This should be particularly beneficial on SMP boxes, as it
* eliminates(消除) "false sharing".
* Note for systems short on memory removing the alignment will
* allow tighter(紧的) packing of the smaller caches.
*/
if (!sizes->cs_cachep) {
sizes->cs_cachep = kmem_cache_create(names->name,
sizes->cs_size,
ARCH_KMALLOC_MINALIGN,
ARCH_KMALLOC_FLAGS|SLAB_PANIC,
NULL, NULL);
}
#ifdef CONFIG_ZONE_DMA //如果配置DMA,那么为每个kmem_cache 分配两个,一个DMA,一个常规
sizes->cs_dmacachep = kmem_cache_create(
names->name_dma,
sizes->cs_size,
ARCH_KMALLOC_MINALIGN,
ARCH_KMALLOC_FLAGS|SLAB_CACHE_DMA|
SLAB_PANIC,
NULL, NULL);
#endif
sizes++; //都是数组名,直接++,进行循环迭代,由小到大分配各个大小的general caches,最大为ULONG_MAX
names++;
}
/* 4) Replace the bootstrap head arrays */
{
struct array_cache *ptr;
//现在要申请arraycache替换之前的initarray_cache
ptr = kmalloc(sizeof(struct arraycache_init), GFP_KERNEL); //GFP_KERNEL 可睡眠申请
//关中断
local_irq_disable();
BUG_ON(cpu_cache_get(&cache_cache) != &initarray_cache.cache);
memcpy(ptr, cpu_cache_get(&cache_cache),
sizeof(struct arraycache_init)); //将cache_cache中per-cpu对应的array_cache拷贝到ptr
/*
* Do not assume that spinlocks can be initialized via memcpy:
*/
spin_lock_init(&ptr->lock);
cache_cache.array[smp_processor_id()] = ptr; //再让它指向ptr?
local_irq_enable();
ptr = kmalloc(sizeof(struct arraycache_init), GFP_KERNEL);
local_irq_disable();
BUG_ON(cpu_cache_get(malloc_sizes[INDEX_AC].cs_cachep)
!= &initarray_generic.cache);
memcpy(ptr, cpu_cache_get(malloc_sizes[INDEX_AC].cs_cachep),
sizeof(struct arraycache_init));
/*
* Do not assume that spinlocks can be initialized via memcpy:
*/
spin_lock_init(&ptr->lock);
malloc_sizes[INDEX_AC].cs_cachep->array[smp_processor_id()] =
ptr;
local_irq_enable();
}
/* 5) Replace the bootstrap kmem_list3's */
{
int nid;
/* Replace the static kmem_list3 structures for the boot cpu */
init_list(&cache_cache, &initkmem_list3[CACHE_CACHE], node);
for_each_online_node(nid) {
init_list(malloc_sizes[INDEX_AC].cs_cachep,
&initkmem_list3[SIZE_AC + nid], nid);
if (INDEX_AC != INDEX_L3) {
init_list(malloc_sizes[INDEX_L3].cs_cachep,
&initkmem_list3[SIZE_L3 + nid], nid);
}
}
}
/* 6) resize the head arrays to their final sizes */
{
struct kmem_cache *cachep;
mutex_lock(&cache_chain_mutex);
list_for_each_entry(cachep, &cache_chain, next)
if (enable_cpucache(cachep)) //这个函数先暂时不剖析,是对本地缓存的处理
BUG();
mutex_unlock(&cache_chain_mutex);
}
/* Annotate slab for lockdep -- annotate the malloc caches */
init_lock_keys();
/* Done! */
g_cpucache_up = FULL;
/*
* Register a cpu startup notifier callback that initializes
* cpu_cache_get for all new cpus
*/
register_cpu_notifier(&cpucache_notifier);
/*
* The reap timers are started later, with a module init call: That part
* of the kernel is not yet operational.
*/
}
这个函数就是 slab 初始化的主干。
执行流程:
静态初始化的结构比如:
/* internal cache of cache description objs */
//这就是静态定义了第一个即通用缓存器
static struct kmem_cache cache_cache = {
.batchcount = 1,
.limit = BOOT_CPUCACHE_ENTRIES,
.shared = 1,
.buffer_size = sizeof(struct kmem_cache),
.name = "kmem_cache", //卧槽,名字就叫 kmem_cache
};
或者
static struct arraycache_init initarray_cache /*__initdata*/ =
{ {0, BOOT_CPUCACHE_ENTRIES, 1, 0} };
static struct arraycache_init initarray_generic =
{ {0, BOOT_CPUCACHE_ENTRIES, 1, 0} };
该函数其他辅助函数有:
1. 三链初始化函数
static void kmem_list3_init(struct kmem_list3 *parent)
{
INIT_LIST_HEAD(&parent->slabs_full);
INIT_LIST_HEAD(&parent->slabs_partial);
INIT_LIST_HEAD(&parent->slabs_free);
parent->shared = NULL;
parent->alien = NULL;
parent->colour_next = 0;
spin_lock_init(&parent->list_lock);
parent->free_objects = 0;
parent->free_touched = 0;
}
这个没什么卵用,就只是初始化。
2. 对齐宏
#define ALIGN(x,a) __ALIGN_MASK(x,(typeof(x))(a)-1)
#define __ALIGN_MASK(x,mask) (((x)+(mask))&~(mask))
这个宏用来控制内存以 a 的大小为基本单位对齐,0~7为一组,以此类推。
关于这个宏详细解释可参照:内核宏ALIGN的含义
3. cache_estimate()函数
这个函数用来计算每个 slab 中对象的数目以及浪费空间的大小,注释中的管理对象就是前面所说的管理者。
/*
* Calculate the number of objects and left-over bytes for a given buffer size.
*/
static void cache_estimate(unsigned long gfporder, size_t buffer_size,
size_t align, int flags, size_t *left_over,
unsigned int *num)
{
int nr_objs;
size_t mgmt_size;
//slab 大小为 2^gfporder 个页面
size_t slab_size = PAGE_SIZE << gfporder; //#define PAGE_SIZE 0x400 (即1024),FIXME: 难道一个页面是 1K ,怎么可能?
/*
* The slab management structure can be either off the slab or //有off-slab和on-slab两种方式
* on it. For the latter case, the memory allocated for a
* slab is used for: //这段内存被用来存储:
*
* - The struct slab //slab结构体
* - One kmem_bufctl_t for each object //每个对象的kmem_bufctl_t
* - Padding to respect alignment of @align //对齐的大小
* - @buffer_size bytes for each object //每个对象的大小
*
* If the slab management structure is off the slab, then the
* alignment will already be calculated into the size. Because //如果是off-slab,align早已被计算出来
* the slabs are all pages aligned, the objects will be at the //因为所有的页面对齐过了,对象申请时会处在正确的位置
* correct alignment when allocated.
*/
//对于外置slab,没有slab管理对象问题,直接用申请空间除以对象大小就是对象个数
if (flags & CFLGS_OFF_SLAB) {
//外置slab不存在管理对象,全部用于存储slab对象
mgmt_size = 0;
//所以对象个数 = slab大小 / 对象大小
nr_objs = slab_size / buffer_size; //注意buffer_size已经和cache line对齐过了
//对象个数不许超限
if (nr_objs > SLAB_LIMIT)
nr_objs = SLAB_LIMIT;
} else {
/*
* Ignore padding for the initial guess. The padding
* is at most @align-1 bytes, and @buffer_size is at
* least @align. In the worst case, this result will
* be one greater than the number of objects that fit
* into the memory allocation when taking the padding
* into account.
*/
//内置式slab,slab管理对象与slab对象都在一片内存中,此时slab页面包含:
//一个struct slab 对象,一个kmem_bufctl_t 类型数组(kmem_bufctl_t 数组的项数和slab对象数目相同)
//slab大小需要减去管理对象大小,所以对象个数为 剩余大小 / (每个对象大小 + sizeof(kmem_bufctl_t)), 它们是一一匹配的关系
nr_objs = (slab_size - sizeof(struct slab)) /
(buffer_size + sizeof(kmem_bufctl_t));
/*
* This calculated number will be either the right
* amount, or one greater than what we want.
*/
//如果对齐后超过slab 总大小 ,需要减去一个对象
if (slab_mgmt_size(nr_objs, align) + nr_objs*buffer_size
> slab_size)
nr_objs--;
//对象个数不许超限
if (nr_objs > SLAB_LIMIT)
nr_objs = SLAB_LIMIT;
//计算 管理对象以缓存行 对齐后的总大小
mgmt_size = slab_mgmt_size(nr_objs, align);
}
//得出slab最终对象个数
*num = nr_objs;
//前面已经得到了slab管理对象大小(外置为0,内置也已计算),这样就可以最终的出slab最终浪费空间大小
*left_over = slab_size - nr_objs*buffer_size - mgmt_size;
}
5. malloc_sizes[]表
我们的需要通过 kmem_cache_create() 函数创建通用缓存器,最终实际会向伙伴系统申请。那创建多大呢?这个有一个很有意思的地方:
首先看 INDEX_AC:
#define INDEX_AC index_of(sizeof(struct arraycache_init))
而 index_of 是这样的:
/*
* This function must be completely optimized away if a constant is passed to
* it. Mostly the same as what is in linux/slab.h except it returns an index.
*/
static __always_inline int index_of(const size_t size) //这个函数用来选择大小的,可作为mallloc_size的参数
{
extern void __bad_size(void);
if (__builtin_constant_p(size)) {
int i = 0;
#define CACHE(x) \
if (size <=x) \ //适配一个刚足够容纳size的大小
return i; \
else \
i++; //不成功,增大继续适配
#include "linux/kmalloc_sizes.h"
#undef CACHE
__bad_size();
} else
__bad_size();
return 0;
}
为什么说 index_of 能确定大小呢,这真是有点玄幻了。注意这一句
#include "linux/kmalloc_sizes.h"
而 linux/kmalloc_sizes.h 中的内容是这样的:
#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
在 index_of 函数中局部定义了 CACHE(x) 宏,并把这个头文件引入,你可以理解为把头文件中所有的内容都加入到了 index_of 函数的那一句所在的位置。那么,程序接下来会不停地执行者个局部宏 CACHE(X),而这个宏又是有函数意义的,当匹配到正确的正确的size,会直接返回 i 值(真是神一样的技巧,相当于用宏来循环,我算是服了)。通过这种方式就确定了要申请的大小对应在 malloc_sizes 表中下标 i 的值。
那么只知道下标 i 的值可不行,malloc_sizes[]表是什么时候初始化的呢? 答案是它是静态初始化的。
/*
* These are the default caches for kmalloc. Custom caches can have other sizes.
*/
struct cache_sizes malloc_sizes[] = { //通用缓存器的大小由malloc_size表决定
#define CACHE(x) { .cs_size = (x) },
#include //终于明白这是什么用法了
CACHE(ULONG_MAX)
#undef CACHE
};
在上面的代码中,同样定义了 CACHE(X),不过这个可和之前的那个作用不一样,它们俩都是局部宏,使用完即 undef,不会产生影响。
在这里注意,这个宏被定义为 “{ .cs_size = (x) },”,注意到这个逗号了没有?下面会同样引入 kmalloc_size 头文件中的一大堆 CACHE(x),每个都是这种形式,这不正是数组的初始化方式吗?只不过每个数据类型都是 cache_sizes 类型罢了。相当于int array[] = { {1}, {2}, …},大概就是这样。cache_names[] 表同理。
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
};
于是,通过上面这种初始化方式,就把 malloc_sizes[] 表中按照由小到大顺序将全部 cache_sizes的 cs_size 成员初始化了。所以 malloc_size[i] 对应的就是描述大小为 i 的缓存器的 cache_sizes 结构,通过 .cs_cachep 就可以得到对应大小的通用缓存器!
如果是 DMA,那么会配置两种通用缓存器。
6. kmem_cache_create()函数
这个函数是核心,对该函数的详细分析会在下一篇博客出现,很长的:)
7. kmalloc()函数
也是重点,以后会单独剖析。
8. init_list()函数
/*
* swap the static kmem_list3 with kmalloced memory
*/
static void init_list(struct kmem_cache *cachep, struct kmem_list3 *list,
int nodeid)
{
struct kmem_list3 *ptr;
ptr = kmalloc_node(sizeof(struct kmem_list3), GFP_KERNEL, nodeid);
BUG_ON(!ptr);
local_irq_disable();
memcpy(ptr, list, sizeof(struct kmem_list3));
/*
* Do not assume that spinlocks can be initialized via memcpy:
*/
spin_lock_init(&ptr->list_lock);
MAKE_ALL_LISTS(cachep, ptr, nodeid);
cachep->nodelists[nodeid] = ptr;
local_irq_enable();
}
这是利用 kmalloc 申请三链和静态三链交换的函数,为什么说是利用 kmalloc 呢,且看它调用的 kmalloc_node() 函数:
static inline void *kmalloc_node(size_t size, gfp_t flags, int node)
{
return kmalloc(size, flags);
}
就是这样,我们为了解决“鸡与蛋”的问题,首先设定几个静态变量,比如三链 initkmem_list3,先用它合成我们的 cache_cache,然后有了 cache_cache,这是为缓存器制定规则的缓存器,通过它我们就可以随意合成大小其他缓存器了。有了各种大小的缓存器,在 kmem_cache_init() 函数后半部分就可以用缓存器来 kmalloc 分配对象,替换之前所有用来辅助 cache_cache 的静态数据结构(如 initarray_cache, initkmem_list3),以后这些静态数据结构就不再使用了。
cache_cache 为缓存器大小制定了一种规则,这说明所有缓存器自身大小也是一样的,只不过它们的 buffer_size 字段描述的用来分配的对象大小是不一样的,以后不同大小的对象就靠这个找不同的缓存器分配就行了。
参考:
PS: 最近是怎么了,有点慌,觉得学了几天才学了这么一点东西,感觉好没有成就感。嗯,没慌,搞技术的要静的下心来。