内存管理:1. slab-初始化-爱代码爱编程
slab系统初始化
源码基于5.10
slab系统的初始化分为2部分:kmem_cache_init和kmem_cache_init_late。
slab系统初始化第1部分
// 这是静态分配的一个实例,解决‘先有鸡还是先有蛋’的问题
static struct kmem_cache kmem_cache_boot = {
// 批量操作的数据量
.batchcount = 1,
// BOOT_CPUCACHE_ENTRIES是1
.limit = BOOT_CPUCACHE_ENTRIES,
// 可以共享
.shared = 1,
// 每个对象的大小
.size = sizeof(struct kmem_cache),
// 缓存名称
.name = "kmem_cache",
};
// 这个函数由 mm_init 调用,是slab系统初始化的第一部分
void __init kmem_cache_init(void)
{
int i;
// 把静态分配的实例复制到kmem_cache
kmem_cache = &kmem_cache_boot;
// 如果不是numa或者只有一个cpu,就不用外面的缓存
// todo: 外部缓存后面再看
if (!IS_ENABLED(CONFIG_NUMA) || num_possible_nodes() == 1)
use_alien_caches = 0;
/*
NUM_INIT_LISTS是2 * MAX_NUMNODES,这个MAX_NUMNODES是可以在
配置文件里配的,由CONFIG_NODES_SHIFT配置。
init_kmem_cache_node的数组,第0~MAX_NUMNODES是给
CACHE_CACHE(slab对象自身的cache)用的,
MAX_NUMNODES ~ 2 * MAX_NUMNODES是给kmalloc-SIZE_NODE用的
*/
for (i = 0; i < NUM_INIT_LISTS; i++)
// init_kmem_cache_node是struct kmem_cache_node类型
// 初始化每个node对象,这里面只是对对象做了基本的数据初始化,
// 如:初始化链表关,初始化锁等
kmem_cache_node_init(&init_kmem_cache_node[i]);
// slab_max_order可以从命令行设置,
// !slab_max_order_set表示slab_max_order还没有设置,
// 如果没设置,当内存>32M对应的页时,设置slab_max_order,SLAB_MAX_ORDER_HI是1
if (!slab_max_order_set && totalram_pages() > (32 << 20) >> PAGE_SHIFT)
slab_max_order = SLAB_MAX_ORDER_HI;
// 创建根kmem_cache
create_boot_cache(kmem_cache, "kmem_cache",
offsetof(struct kmem_cache, node) +
// 这个大小是每个node都有一个struct kmem_cache_node
nr_node_ids * sizeof(struct kmem_cache_node *),
SLAB_HWCACHE_ALIGN, 0, 0);
// 把kmem_cache加到slab_caches链表
list_add(&kmem_cache->list, &slab_caches);
/*
// 这是slab的状态,一个4个:
DOWN // slab功能不可用
PARTIAL, //kmem_cache可用
PARTIAL_NODE, // kmalloc-sizeof(struct kmem_cache_node)的大小可用
UP, // kmalloc array可以用了
FULL // 功能全部可用
*/
slab_state = PARTIAL;
/*
kmalloc_caches的定义:
缓存的类型,每个kmalloc-size都有这3个类型
enum kmalloc_cache_type {
KMALLOC_NORMAL = 0, // 一般缓存是这个
KMALLOC_RECLAIM, // 回收
#ifdef CONFIG_ZONE_DMA
KMALLOC_DMA, // DMA
#endif
NR_KMALLOC_TYPES
};
KMALLOC_SHIFT_HIGH最大是25,也就是32M,
MAX_ORDER是buddy分配最大物理页的阶数,如果没有在config里指定,默认是11.
PAGE_SHIFT是页大小
#define KMALLOC_SHIFT_HIGH ((MAX_ORDER + PAGE_SHIFT - 1) <= 25 ? \
(MAX_ORDER + PAGE_SHIFT - 1) : 25)
extern struct kmem_cache * kmalloc_caches[NR_KMALLOC_TYPES][KMALLOC_SHIFT_HIGH + 1];
kmalloc_caches是一个缓存数组,第一个下标是缓存的类型,一般用NORMAL比较多,第2个下标是大小,最大是25,也就是 1<<25=32M.就是我们平时使用的kmalloc对应的各个slab缓存
假设页大小是4K,则PAGE_SHIFT是12,那KMALLOC_SHIFT_HIGH就是 11+12-1=22,也就是4M.当页大小为4K时,kmalloc最大支持4M的缓存
*/
/*
kmalloc_info的定义:
extern const struct kmalloc_info_struct {
const char *name[NR_KMALLOC_TYPES]; // 3种类型的名字
unsigned int size;
} kmalloc_info[];
#define INIT_KMALLOC_INFO(__size, __short_size) \
{ \
// 先初始化3个名字。原来c语言还可以这样初始化数组,长见识了。。。
.name[KMALLOC_NORMAL] = "kmalloc-" #__short_size, \
.name[KMALLOC_RECLAIM] = "kmalloc-rcl-" #__short_size, \
.name[KMALLOC_DMA] = "dma-kmalloc-" #__short_size, \
// 最后初始化大小
.size = __size, \
}
这个数组的下标是通过kmalloc_index来获取的,不是按由小到大排序的。
todo: 为什么这么排序
const struct kmalloc_info_struct kmalloc_info[] __initconst = {
INIT_KMALLOC_INFO(0, 0),
INIT_KMALLOC_INFO(96, 96),
INIT_KMALLOC_INFO(192, 192),
...
};
*/
// 创建第2个cache,第2个cache是kmalloc-SIZENODE,这里的size是struct kmem_cache_node的大小
// KMALLOC_NORMAL=0, INDEX_NODE=kmalloc_index(sizeof(struct kmem_cache_node))
// 这个就是创建kmem_cache_node大小的kmalloc,因为下面要用kmem_cache_node,
// 所以先把它创建出来,这里面分配slab是在kmem_cache上分配,因为上面已经把kmem_cache初始化
// 好了,所以这里可以从kmem_cache里分配kmalloc-SIZENODE所需的slab头
kmalloc_caches[KMALLOC_NORMAL][INDEX_NODE] = create_kmalloc_cache(
kmalloc_info[INDEX_NODE].name[KMALLOC_NORMAL],
kmalloc_info[INDEX_NODE].size,
ARCH_KMALLOC_FLAGS, 0,
// todo: 这里usersize传的是index_node对应的size
kmalloc_info[INDEX_NODE].size);
// kmalloc-SIZENODE可用了
slab_state = PARTIAL_NODE;
// 初始化size_index数组。size_index是对 <= 192字节的kmalloc-size所在kmalloc_caches序号的
// 一个快速查找表。kmalloc_caches数组并不是按内存大小对各个kmem_cache进行排序
setup_kmalloc_cache_index_table();
// 早期初始化结束?
// 在510代码里这个变量已经没有人在用,是不是可以删掉了!!
slab_early_init = 0;
// 初始化kmem_cache和kmalloc-SIZENODE对应的各个node节点
{
int nid;
// 遍历在线node
for_each_online_node(nid) {
// init_list会重新调用kmalloc分配一个kmem_cache_node对象,再把init_kmem_cache_node里对应节点的复制过去,再设置各个cachep的node对象
// 初始化每个节点的kmem_cache的node结点。
init_list(kmem_cache, &init_kmem_cache_node[CACHE_CACHE + nid], nid);
// 初始kmalloc_caches[KMALLOC_NORMAL][INDEX_NODE]对象的各node结点
init_list(kmalloc_caches[KMALLOC_NORMAL][INDEX_NODE],
&init_kmem_cache_node[SIZE_NODE + nid], nid);
}
}
// 初始化其它kmalloc size
create_kmalloc_caches(ARCH_KMALLOC_FLAGS);
}
static void kmem_cache_node_init(struct kmem_cache_node *parent)
{
// 初始化3个链表头
// slab里的对象已全部使用
INIT_LIST_HEAD(&parent->slabs_full);
// slab里的对象部分使用
INIT_LIST_HEAD(&parent->slabs_partial);
// slab里的对象全部空闲
INIT_LIST_HEAD(&parent->slabs_free);
// slab总数
parent->total_slabs = 0;
// 空闲slab数
parent->free_slabs = 0;
// todo: 共享数量?
parent->shared = NULL;
// todo: 从外部获取的数量?
parent->alien = NULL;
// 下一个着色的值
parent->colour_next = 0;
// 保护上面3个list的锁
spin_lock_init(&parent->list_lock);
// 空闲对象为0
parent->free_objects = 0;
// 从空闲列表上分配一个slab时,这个值被置1
parent->free_touched = 0;
}
static void __init init_list(struct kmem_cache *cachep, struct kmem_cache_node *list,
int nodeid)
{
struct kmem_cache_node *ptr;
// 分配一个node,kmalloc是在kmalloc-size上分配
ptr = kmalloc_node(sizeof(struct kmem_cache_node), GFP_NOWAIT, nodeid);
BUG_ON(!ptr);
// 把list数据直接复制过来
memcpy(ptr, list, sizeof(struct kmem_cache_node));
// 初始化锁
spin_lock_init(&ptr->list_lock);
// 初始化新slab的full, partial, free链表
MAKE_ALL_LISTS(cachep, ptr, nodeid);
// 设置到cachep里。
cachep->node[nodeid] = ptr;
}
void __init setup_kmalloc_cache_index_table(void)
{
unsigned int i;
// 在slab时KMALLOC_MIN_SIZE是1<<5=32
BUILD_BUG_ON(KMALLOC_MIN_SIZE > 256 ||
(KMALLOC_MIN_SIZE & (KMALLOC_MIN_SIZE - 1)));
// KMALLOC_MIN_SIZE对应在kmalloc-size的shift就是KMALLOC_SHIFT_LOW,
// 所以小于它大小的都按最小size分。也就是最小分配32字节?
for (i = 8; i < KMALLOC_MIN_SIZE; i += 8) {
// 取出i对应的index
unsigned int elem = size_index_elem(i);
// 判断index是否越界
if (elem >= ARRAY_SIZE(size_index))
break;
size_index[elem] = KMALLOC_SHIFT_LOW;
}
// todo: 下面没太看懂
if (KMALLOC_MIN_SIZE >= 64) {
// 这里最大可以对齐到64byte,所以要把96的分配到7,7是128byte
for (i = 64 + 8; i <= 96; i += 8)
size_index[size_index_elem(i)] = 7;
}
if (KMALLOC_MIN_SIZE >= 128) {
// 这里同上,如果对齐到128时,把小于192的分到8,8是256
for (i = 128 + 8; i <= 192; i += 8)
size_index[size_index_elem(i)] = 8;
}
}
static inline unsigned int size_index_elem(unsigned int bytes)
{
// 向下以8对齐
return (bytes - 1) / 8;
}
kmem_cache_init是slab系统初始化第一阶段的主函数,主要做了下面这些事:
-
初始化kmem_cache,它本身也是一个slab缓存。它的slab里保存的结构也是struct kmem_cache,主要是为了给其它slab缓存的结构提供缓存。为了解决‘鸡与蛋‘的问题,kmem_cache是一个静态结构。
-
初始化kmalloc-INODESIZE,这是kmalloc-size中的一个缓存。其slab对象的大小是sizeof(struct kmem_cache_node),因为在初始化过程中,要分配这些对象。
-
初始化其它kmalloc-size的slab对象。这个数组里保存了一些不同大小的slab缓存。
情景1:kmem_cache 初始化
// 初始化入口
create_boot_cache(kmem_cache, "kmem_cache",
// node在kmem_cache 里的偏移量
offsetof(struct kmem_cache, node) +
// 这个大小是每个node都有一个struct kmem_cache_node
nr_node_ids * sizeof(struct kmem_cache_node *),
// 硬件缓存对齐
SLAB_HWCACHE_ALIGN, 0, 0);
`offsetof(struct kmem_cache, node) + nr_node_ids * sizeof(struct kmem_cache_node *)`算出来的就是一个kmem_cache结构的总长度,因为kmem_cache缓存里放的也是kmem_cache本身,所以算出的大小就是slab缓存里对象的大小。
void __init create_boot_cache(struct kmem_cache *s, const char *name,
unsigned int size, slab_flags_t flags,
unsigned int useroffset, unsigned int usersize)
{
int err;
// ARCH_KMALLOC_MINALIGN没定义时,默认是__alignof__(unsigned long long)
// unsigned long long是8字节
unsigned int align = ARCH_KMALLOC_MINALIGN;
s->name = name;
// 把slab的大小和对象大小设成一样。
// 因为kmem_cache存的对象就是struct kmem_cache类型,
// 所以对象大小和每个slab大小是相同的
s->size = s->object_size = size;
// 如果对象大小是2的幂,并且大于最小的对齐值时,可以用obj的大小来做对齐
if (is_power_of_2(size))
align = max(align, size);
// 计算最终对齐的值
s->align = calculate_alignment(flags, align, size);
// todo: useroffset/size是什么?
s->useroffset = useroffset;
s->usersize = usersize;
// 真正的创建cache
err = __kmem_cache_create(s, flags);
if (err)
panic("Creation of kmalloc slab %s size=%u failed. Reason %d\n",
name, size, err);
// todo: 这里的refcount为什么设成-1
s->refcount = -1; /* Exempt from merging for now */
}
static unsigned int calculate_alignment(slab_flags_t flags,
unsigned int align, unsigned int size)
{
// 硬件缓存对齐
if (flags & SLAB_HWCACHE_ALIGN) {
unsigned int ralign;
// 缓存行为32K
ralign = cache_line_size();
// 算出size在缓存行内占的最少大小
while (size <= ralign / 2)
ralign /= 2;
// 取对齐值较大的值
align = max(align, ralign);
}
// 不能小于架构规定的最小对齐值,x86上ARCH_SLAB_MINALIGN 64 字节
if (align < ARCH_SLAB_MINALIGN)
align = ARCH_SLAB_MINALIGN;
// 最终还要和指针的大小对齐
return ALIGN(align, sizeof(void *));
}
// 下面函数里去除了debug相关的东西
int __kmem_cache_create(struct kmem_cache *cachep, slab_flags_t flags)
{
// BYTES_PER_WORD: sizeof(void *)
size_t ralign = BYTES_PER_WORD;
gfp_t gfp;
int err;
// slab大小
unsigned int size = cachep->size;
// 对齐size到BYTES_PER_WORD
size = ALIGN(size, BYTES_PER_WORD);
// SLAB_RED_ZONE是调试用的,这个情景里flags只有SLAB_HWCACHE_ALIGN,所以这里不会走
if (flags & SLAB_RED_ZONE) {
ralign = REDZONE_ALIGN;
/* If redzoning, ensure that the second redzone is suitably
* aligned, by adjusting the object size accordingly. */
size = ALIGN(size, REDZONE_ALIGN);
}
// 对齐值始终取较大的,在这个情景里,cache的align比ralign大
if (ralign < cachep->align) {
ralign = cachep->align;
}
// 在这个情景里,这个条件成立。todo: 这是什么意思?
if (ralign > __alignof__(unsigned long long))
flags &= ~(SLAB_RED_ZONE | SLAB_STORE_USER);
// 保存最终的对齐值
cachep->align = ralign;
// 着色默认为缓存行大小
cachep->colour_off = cache_line_size();
// 着色偏移必须大于等于对齐值。否则,连第一个对象都对不齐
if (cachep->colour_off < cachep->align)
cachep->colour_off = cachep->align;
// 这个判断是slab_state >= UP
// 这个情景里不成立,这个时候slab的状态还是DOWN
if (slab_is_available())
gfp = GFP_KERNEL;
else
// 所以走这个分支,
// GFP_NOWAIT就是__GFP_KSWAPD_RECLAIM,不能等待
gfp = GFP_NOWAIT;
// kasan没打开时这是空语句
kasan_cache_create(cachep, &size, &flags);
// 这里的size还要再跟align对齐,因为经过上面时cachep->align可能会变
size = ALIGN(size, cachep->align);
// SLAB_OBJ_MIN_SIZE是16, 如果size小于最小值,就要以最小值重新计算对齐
if (FREELIST_BYTE_INDEX && size < SLAB_OBJ_MIN_SIZE)
size = ALIGN(SLAB_OBJ_MIN_SIZE, cachep->align);
// 下面这3个函数都是计算order, num, 只要一个设置成功就算成功
// 先用CFLGS_OBJFREELIST_SLAB来计算,使用最后一个对象保存freelist
if (set_objfreelist_slab_cache(cachep, size, flags)) {
flags |= CFLGS_OBJFREELIST_SLAB;
goto done;
}
// 用off_slab来计算,把freelist保存在外部
if (set_off_slab_cache(cachep, size, flags)) {
flags |= CFLGS_OFF_SLAB;
goto done;
}
// 把freelist保存在slab内部
if (set_on_slab_cache(cachep, size, flags))
goto done;
return -E2BIG;
done:
// 空闲列表大小
cachep->freelist_size = cachep->num * sizeof(freelist_idx_t);
// slab标志
cachep->flags = flags;
// 根据flag设置分配标志,分配标志是在slab无可用时,重新分配页时,使用的标志
cachep->allocflags = __GFP_COMP;
// 根据slab的特点,确定分配新内存时的标志
if (flags & SLAB_CACHE_DMA)
cachep->allocflags |= GFP_DMA;
if (flags & SLAB_CACHE_DMA32)
cachep->allocflags |= GFP_DMA32;
if (flags & SLAB_RECLAIM_ACCOUNT)
cachep->allocflags |= __GFP_RECLAIMABLE;
// 设置slab大小
cachep->size = size;
// 算出size的倒数。todo: 算倒数干啥?
cachep->reciprocal_buffer_size = reciprocal_value(size);
// 如果slab对象是在外面保存,还得分配一个freelist_cache
// 在这个情景里不存在
if (OFF_SLAB(cachep)) {
cachep->freelist_cache =
kmalloc_slab(cachep->freelist_size, 0u);
}
// 这个主要设置percpu-cache里node的值,和对当前cpu上的node的初始化
err = setup_cpu_cache(cachep, gfp);
if (err) {
// todo: 为什么cpu缓存设置失败,就算创建失败,必须要有cpu_cache?
__kmem_cache_release(cachep);
return err;
}
return 0;
}
static bool set_objfreelist_slab_cache(struct kmem_cache *cachep,
size_t size, slab_flags_t flags)
{
size_t left;
cachep->num = 0;
// 判断是否需要上释放时重新初始化。todo: 为什么直接返回false
if (unlikely(slab_want_init_on_free(cachep)))
return false;
// 有构造函数或者是rcu,也返回false。todo: why?
if (cachep->ctor || flags & SLAB_TYPESAFE_BY_RCU)
return false;
// 走到这里一般是没有构造函数的
// 计算每个slab里的对象数,和对应的order
left = calculate_slab_order(cachep, size,
flags | CFLGS_OBJFREELIST_SLAB);
// slab的对象还是为0,说明计算出错
if (!cachep->num)
return false;
// cachep->num * sizeof(freelist_idx_t)是空闲列表的大小.空闲列表的大小,
// 如果超过的对象的大小,就需要在外面保存
if (cachep->num * sizeof(freelist_idx_t) > cachep->object_size)
return false;
// 算出着色的值,着色也就是偏移
cachep->colour = left / cachep->colour_off;
return true;
}
static size_t calculate_slab_order(struct kmem_cache *cachep,
size_t size, slab_flags_t flags)
{
size_t left_over = 0;
int gfporder;
// KMALLOC_SHIFT_MAX与配置有关,最大是25,也就是32M
for (gfporder = 0; gfporder <= KMALLOC_MAX_ORDER; gfporder++) {
unsigned int num;
size_t remainder;
// 计算当前order可以保存的对象数量
num = cache_estimate(gfporder, size, flags, &remainder);
// 一个对象都保存不了?
if (!num)
continue;
// #define SLAB_OBJ_MAX_NUM ((1 << sizeof(freelist_idx_t) * BITS_PER_BYTE) - 1)
// slab对象的最大值,因为freelist_idx_t的长度限制,
// SLAB_OBJ_MAX_NUM是freelist_idx_t可以保存最多的
if (num > SLAB_OBJ_MAX_NUM)
break;
// flags里有这个标志,表示用户强制把slab头保存到slab外部
// 这个在初始化创建kmem_cache时,不可能走到这个分支,因为kmalloc-size还没有准备好
if (flags & CFLGS_OFF_SLAB) {
struct kmem_cache *freelist_cache;
size_t freelist_size;
// freelist的总长度
freelist_size = num * sizeof(freelist_idx_t);
// freelist_size对应的kmalloc-size的缓存对象
freelist_cache = kmalloc_slab(freelist_size, 0u);
if (!freelist_cache)
continue;
/*
* Needed to avoid possible looping condition
* in cache_grow_begin()
*/
// todo: ?
if (OFF_SLAB(freelist_cache))
continue;
/* check if off slab has enough benefit */
// todo: ?
if (freelist_cache->size > cachep->size / 2)
continue;
}
// 先保存num和gfporder
cachep->num = num;
cachep->gfporder = gfporder;
left_over = remainder;
/*
* A VFS-reclaimable slab tends to have most allocations
* as GFP_NOFS and we really don't want to have to be allocating
* higher-order pages when we are unable to shrink dcache.
*/
// todo: ?
if (flags & SLAB_RECLAIM_ACCOUNT)
break;
// gfp太大了也不行
if (gfporder >= slab_max_order)
break;
// 剩余空闲小于总空间的1/8就可以了?
if (left_over * 8 <= (PAGE_SIZE << gfporder))
break;
}
return left_over;
}
static unsigned int cache_estimate(unsigned long gfporder, size_t buffer_size,
slab_flags_t flags, size_t *left_over)
{
unsigned int num;
// order对应的size
size_t slab_size = PAGE_SIZE << gfporder;
if (flags & (CFLGS_OBJFREELIST_SLAB | CFLGS_OFF_SLAB)) {
// slab保存在外面的情况
// 可以存多少个对象
num = slab_size / buffer_size;
// 还剩多少空间
*left_over = slab_size % buffer_size;
} else {
// slab头保存在slab本身
// 除了对象本身外,每个对象还需要一个freelist_idx_t
num = slab_size / (buffer_size + sizeof(freelist_idx_t));
*left_over = slab_size %
(buffer_size + sizeof(freelist_idx_t));
}
return num;
}
static inline bool slab_want_init_on_free(struct kmem_cache *c)
{
// init_on_free由CONFIG_INIT_ON_FREE_DEFAULT_ON配置决定,如果没有打开这个配置就是false,
// 或者在命令行指定init_on_free参数
if (static_branch_unlikely(&init_on_free))
// 在没有构造函数时,还需要这2个标志,才返回true
return !(c->ctor ||
(c->flags & (SLAB_TYPESAFE_BY_RCU | SLAB_POISON)));
return false;
}
static bool set_off_slab_cache(struct kmem_cache *cachep,
size_t size, slab_flags_t flags)
{
size_t left;
cachep->num = 0;
// 条件在这个情景不成立
if (flags & SLAB_NOLEAKTRACE)
return false;
// 用带CFLGS_OFF_SLAB来计算order,CFLGS_OFF_SLAB表示slab和管理数据不在slab内
left = calculate_slab_order(cachep, size, flags | CFLGS_OFF_SLAB);
if (!cachep->num)
return false;
// 剩余的量如果大小保存对象的空闲列表,说明不适合用off_slab
if (left >= cachep->num * sizeof(freelist_idx_t))
return false;
// 着色值
cachep->colour = left / cachep->colour_off;
return true;
}
static bool set_on_slab_cache(struct kmem_cache *cachep,
size_t size, slab_flags_t flags)
{
size_t left;
cachep->num = 0;
// 直接计算order
left = calculate_slab_order(cachep, size, flags);
if (!cachep->num)
return false;
// 着色
cachep->colour = left / cachep->colour_off;
return true;
}
static int __ref setup_cpu_cache(struct kmem_cache *cachep, gfp_t gfp)
{
// 这个条件表示slab已经初始化完了,则使能cpucache
if (slab_state >= FULL)
return enable_cpucache(cachep, gfp);
// 走到这里表示slab还没有初始化完成?
// 这里面主要分配了cachep->cpu_slab的percpu变量
cachep->cpu_cache = alloc_kmem_cache_cpus(cachep, 1, 1);
if (!cachep->cpu_cache)
return 1;
// DOWN表示slab功能不可用
if (slab_state == DOWN) {
// 在我们在这个情景里,走这个条件
// CACHE_CACHE=0
// set_up_node是用init_kmem_cache_node里对应的元素来初始化cachep里每个node
set_up_node(kmem_cache, CACHE_CACHE);
} else if (slab_state == PARTIAL) {
// kmem_cache_node可用
// SIZE_NODE=MAX_NUMNODES
set_up_node(cachep, SIZE_NODE);
// 在DOWN, UP这2个条件里,因为kmalloc-size还没有完成初始化,
// 所以得用init_kmem_cache_node元素,解决鸡与蛋的问题
} else {
// 这个分支表示PARTIAL_NODE,UP状态
// 在这个状态时kmalloc已经可以用了,所以可以直接使用
int node;
// 这里初始化每个node
for_each_online_node(node) {
cachep->node[node] = kmalloc_node(
sizeof(struct kmem_cache_node), gfp, node);
BUG_ON(!cachep->node[node]);
// 这里面是node缓存的基本初始化
kmem_cache_node_init(cachep->node[node]);
}
}
// 把当前numa上的回收时间再计算一下。todo: why?
cachep->node[numa_mem_id()]->next_reap =
jiffies + REAPTIMEOUT_NODE +
((unsigned long)cachep) % REAPTIMEOUT_NODE;
// 下面是设置了当前cpu的一些值
// cpu_cache_get获取的是本cpu对应的percpu cache
cpu_cache_get(cachep)->avail = 0;
// BOOT_CPUCACHE_ENTRIES是1
cpu_cache_get(cachep)->limit = BOOT_CPUCACHE_ENTRIES;
// 默认cpu-cache是1个?
cpu_cache_get(cachep)->batchcount = 1;
cpu_cache_get(cachep)->touched = 0;
// 初始化cachep的batchcount和limit
cachep->batchcount = 1;
cachep->limit = BOOT_CPUCACHE_ENTRIES;
return 0;
}
static void __init set_up_node(struct kmem_cache *cachep, int index)
{
int node;
// 初始化每个cache node
// init_kmem_cache_node保存的是初始化的对象,这个数组大小是 2 * MAX_NUMNODES
// CACHE_CACHE: 0~MAX_NUMNODES
// SIZE_NODE: MAX_NUMNODES ~ 2 * MAX_NUMNODES
for_each_online_node(node) {
// 把init_kmem_cache_node里的数据全部复制过去,这是个静态数组
cachep->node[node] = &init_kmem_cache_node[index + node];
// 计算下一次回收的时间
cachep->node[node]->next_reap = jiffies +
REAPTIMEOUT_NODE +
((unsigned long)cachep) % REAPTIMEOUT_NODE;
}
}
static struct array_cache __percpu *alloc_kmem_cache_cpus(
struct kmem_cache *cachep, int entries, int batchcount)
{
int cpu;
size_t size;
struct array_cache __percpu *cpu_cache;
size = sizeof(void *) * entries + sizeof(struct array_cache);
cpu_cache = __alloc_percpu(size, sizeof(void *));
if (!cpu_cache)
return NULL;
for_each_possible_cpu(cpu) {
init_arraycache(per_cpu_ptr(cpu_cache, cpu),
entries, batchcount);
}
return cpu_cache;
}
情景2:kmalloc-INDEX_NODE的初始化
初始化的入口
kmalloc_caches[KMALLOC_NORMAL][INDEX_NODE] = create_kmalloc_cache(
kmalloc_info[INDEX_NODE].name[KMALLOC_NORMAL],
kmalloc_info[INDEX_NODE].size,
ARCH_KMALLOC_FLAGS, 0,
// todo: 这里usersize传的是index_node对应的size
kmalloc_info[INDEX_NODE].size);
// INDEX_NODE的计数如下:
// struct kmem_cache_node是112个字节
#define INDEX_NODE kmalloc_index(sizeof(struct kmem_cache_node))
// 假设size就是112
static __always_inline unsigned int kmalloc_index(size_t size)
{
if (!size)
return 0;
// KMALLOC_MIN_SIZE是32
if (size <= KMALLOC_MIN_SIZE)
return KMALLOC_SHIFT_LOW;
if (KMALLOC_MIN_SIZE <= 32 && size > 64 && size <= 96)
return 1;
if (KMALLOC_MIN_SIZE <= 64 && size > 128 && size <= 192)
return 2;
// 上面4个条件都不符合
if (size <= 8) return 3;
if (size <= 16) return 4;
if (size <= 32) return 5;
if (size <= 64) return 6;
// 在这里会返回,所以INDEX_NODE的值是7
if (size <= 128) return 7;
if (size <= 256) return 8;
if (size <= 512) return 9;
if (size <= 1024) return 10;
if (size <= 2 * 1024) return 11;
if (size <= 4 * 1024) return 12;
if (size <= 8 * 1024) return 13;
if (size <= 16 * 1024) return 14;
if (size <= 32 * 1024) return 15;
if (size <= 64 * 1024) return 16;
if (size <= 128 * 1024) return 17;
if (size <= 256 * 1024) return 18;
if (size <= 512 * 1024) return 19;
if (size <= 1024 * 1024) return 20;
if (size <= 2 * 1024 * 1024) return 21;
if (size <= 4 * 1024 * 1024) return 22;
if (size <= 8 * 1024 * 1024) return 23;
if (size <= 16 * 1024 * 1024) return 24;
if (size <= 32 * 1024 * 1024) return 25;
if (size <= 64 * 1024 * 1024) return 26;
BUG();
/* Will never be reached. Needed because the compiler may complain */
return -1;
}
kmalloc_info[7] = INIT_KMALLOC_INFO(128, 128)
#define INIT_KMALLOC_INFO(__size, __short_size) \
{ \
// 先初始化3个名字。原来c语言还可以这样初始化数组,长见识了。。。
.name[KMALLOC_NORMAL] = "kmalloc-" #__short_size, \
.name[KMALLOC_RECLAIM] = "kmalloc-rcl-" #__short_size, \
.name[KMALLOC_DMA] = "dma-kmalloc-" #__short_size, \
// 最后初始化大小
.size = __size, \
}
kmalloc_info[7] = {
.name[KMALLOC_NORMAL] = "kmalloc-128",
.name[KMALLOC_RECLAIM] = "kmalloc-rcl-128",
.name[KMALLOC_DMA] = "dma-kmalloc-128",
.size = 128
}
// KMALLOC_NORMAL是0,最终的入口展开的如下:
kmalloc_caches[0][7] = create_kmalloc_cache(
"kmalloc-128",
128,
ARCH_KMALLOC_FLAGS, 0,
128);
struct kmem_cache *__init create_kmalloc_cache(const char *name,
unsigned int size, slab_flags_t flags,
unsigned int useroffset, unsigned int usersize)
{
// 从kmem_cache分配一个对象
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, useroffset, usersize);
// 把kmalloc slab加到链表缓存里
list_add(&s->list, &slab_caches);
// 引用计数为1
s->refcount = 1;
return s;
}
创建kmalloc_cache和创建kmem_cache的流程差不多,区别在于kmem_cache是从kmem_cache_boot对象复制而来,kmem_cache_boot是个静态对象。而kmalloc_cache对象是从kmem_cache这个slab里分配的,这就解决了“先有鸡先有蛋”的问题。所以最终都会调到create_boot_cache里,这个流程已经在kmem_cache里介绍过,所以不再赘述。
情景3:创建其它kmalloc-size
void __init create_kmalloc_caches(slab_flags_t flags)
{
int i;
enum kmalloc_cache_type type;
// 这里只初始化normal和reclaim类型的kmalloc
// 第一个循环遍历每个类型
for (type = KMALLOC_NORMAL; type <= KMALLOC_RECLAIM; type++) {
// 第二个循环遍历所有的大小
for (i = KMALLOC_SHIFT_LOW; i <= KMALLOC_SHIFT_HIGH; i++) {
// 如果还没有创建,则先创建对应的缓存
if (!kmalloc_caches[type][i])
new_kmalloc_cache(i, type, flags);
// todo: 下面没太看懂
/*
* 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 && i == 6 &&
!kmalloc_caches[type][1])
new_kmalloc_cache(1, type, flags);
if (KMALLOC_MIN_SIZE <= 64 && i == 7 &&
!kmalloc_caches[type][2])
new_kmalloc_cache(2, type, flags);
}
}
// kmalloc-size可用
slab_state = UP;
// 初始化dma使用的kmalloc-size
#ifdef CONFIG_ZONE_DMA
for (i = 0; i <= KMALLOC_SHIFT_HIGH; i++) {
struct kmem_cache *s = kmalloc_caches[KMALLOC_NORMAL][i];
// 只有normal分配了,才分配dma。todo: why ?
if (s) {
kmalloc_caches[KMALLOC_DMA][i] = create_kmalloc_cache(
kmalloc_info[i].name[KMALLOC_DMA],
kmalloc_info[i].size,
SLAB_CACHE_DMA | flags, 0,
kmalloc_info[i].size);
}
}
#endif
}
static void __init
new_kmalloc_cache(int idx, enum kmalloc_cache_type type, slab_flags_t flags)
{
if (type == KMALLOC_RECLAIM)
flags |= SLAB_RECLAIM_ACCOUNT;
// 这个和上面创建kmalloc-INDEX_NODE差不多,最终都会调到create_boot_cache里
kmalloc_caches[type][idx] = create_kmalloc_cache(
kmalloc_info[idx].name[type],
kmalloc_info[idx].size, flags, 0,
kmalloc_info[idx].size);
}
slab系统初始化第2部分
在第一部分初始化结束以后,slab_state的状态是UP,表示大部分功能已经可用了,但是还没有完全初始化完,还有cpu_cache没初始化完。之所以要分成2部分初始化,是因为在初始化内核一些核心子系统时,他们可以也要使用slab,但是那时cpucache可能还不具备初始化的条件,所以分成2部分来初始化。cpucache是属于对分配的优化,所以可以放在后面来进行。
// 这个函数在start_kernel里调用,会完成最后的slab初始化
void __init kmem_cache_init_late(void)
{
struct kmem_cache *cachep;
mutex_lock(&slab_mutex);
// 遍历slab_caches里的所有的kmem_cache对象
list_for_each_entry(cachep, &slab_caches, list)
// 使能每个kmem_cache的cpucache
if (enable_cpucache(cachep, GFP_NOWAIT))
BUG();
mutex_unlock(&slab_mutex);
// slab已经完全初始化了
slab_state = FULL;
#ifdef CONFIG_NUMA
// 注册一个numa监听器,这个是支持mem上下线的,动态创建或销毁slab
hotplug_memory_notifier(slab_memory_callback, SLAB_CALLBACK_PRI);
#endif
/*
* The reap timers are started later, with a module init call: That part
* of the kernel is not yet operational.
*/
}
static int __meminit slab_memory_callback(struct notifier_block *self,
unsigned long action, void *arg)
{
struct memory_notify *mnb = arg;
int ret = 0;
int nid;
nid = mnb->status_change_nid;
if (nid < 0)
goto out;
switch (action) {
case MEM_GOING_ONLINE:
// 内存将要上线,就创建一个cache node
mutex_lock(&slab_mutex);
ret = init_cache_node_node(nid);
mutex_unlock(&slab_mutex);
break;
case MEM_GOING_OFFLINE:
// 内存将要下线,就销毁一个cache node
mutex_lock(&slab_mutex);
ret = drain_cache_node_node(nid);
mutex_unlock(&slab_mutex);
break;
case MEM_ONLINE:
case MEM_OFFLINE:
case MEM_CANCEL_ONLINE:
case MEM_CANCEL_OFFLINE:
break;
}
out:
return notifier_from_errno(ret);
}
static int enable_cpucache(struct kmem_cache *cachep, gfp_t gfp)
{
int err;
int limit = 0;
int shared = 0;
int batchcount = 0;
// 创建random_seq随机数组
err = cache_random_seq_create(cachep, cachep->num, gfp);
if (err)
goto end;
// 走到这儿,limit=0,这个条件恒为false呀!!
if (limit && shared && batchcount)
goto skip_setup;
// 计算limit,cachep->size是slab的大小,
// 对象越小,缓存的对象越多
if (cachep->size > 131072)
limit = 1;
else if (cachep->size > PAGE_SIZE)
limit = 8;
else if (cachep->size > 1024)
limit = 24;
else if (cachep->size > 256)
limit = 54;
else
limit = 120;
// 默认禁用共享,共享用于在不同的cpu之间共享对象
shared = 0;
// 对象小于页大小,而且cpu数量大于1,则会开启共享,默认共享8个对象
if (cachep->size <= PAGE_SIZE && num_possible_cpus() > 1)
shared = 8;
// 批量数量为limit的一半,并以2对齐
batchcount = (limit + 1) / 2;
skip_setup:
// 设置cpu缓存
err = do_tune_cpucache(cachep, limit, batchcount, shared, gfp);
end:
if (err)
pr_err("enable_cpucache failed for %s, error %d\n",
cachep->name, -err);
return err;
}
static int do_tune_cpucache(struct kmem_cache *cachep, int limit,
int batchcount, int shared, gfp_t gfp)
{
struct array_cache __percpu *cpu_cache, *prev;
int cpu;
// 这里面主要分配了cachep->cpu_slab的percpu变量
cpu_cache = alloc_kmem_cache_cpus(cachep, limit, batchcount);
if (!cpu_cache)
return -ENOMEM;
// 先保存之前的
prev = cachep->cpu_cache;
// 然后设置新的cache
cachep->cpu_cache = cpu_cache;
// 如果prev有值,则激活所有cpu? todo: 什么意思?
if (prev)
kick_all_cpus_sync();
// todo: 这里为什么要检查中断是开的?
check_irq_on();
// 每次新增或销毁的批数量
cachep->batchcount = batchcount;
// 缓存限制
cachep->limit = limit;
// 本percpu缓存可以共享的数量
cachep->shared = shared;
// prev为NULL,表示是第一个分配cache,则直接调到设置
if (!prev)
goto setup_node;
// 释放每个prev里的cpucache
for_each_online_cpu(cpu) {
LIST_HEAD(list);
int node;
struct kmem_cache_node *n;
// 取出每个cpu的ac
struct array_cache *ac = per_cpu_ptr(prev, cpu);
// 把cpu号转成nodeid
node = cpu_to_mem(cpu);
// 获取cachep里对应的node
n = get_node(cachep, node);
spin_lock_irq(&n->list_lock);
// 释放对应的内存
free_block(cachep, ac->entry, ac->avail, node, &list);
spin_unlock_irq(&n->list_lock);
// 如果上面从free_block里释放掉了内存,就把page从cachep里解链,
// 然后销毁,然后把内存还给buddy系统
slabs_destroy(cachep, &list);
}
// 释放prev
free_percpu(prev);
setup_node:
// 真正的设置缓存
return setup_kmem_cache_nodes(cachep, gfp);
}
static int setup_kmem_cache_nodes(struct kmem_cache *cachep, gfp_t gfp)
{
int ret;
int node;
struct kmem_cache_node *n;
// 遍历每个节点,设置percpu
for_each_online_node(node) {
// 这个函数里主要设置了cachep里node对应的kmem_cache_node对象,并没有真正的分配内存。
// 其他还分配了shared, alien相应的内存,如果有的话。
ret = setup_kmem_cache_node(cachep, node, gfp, true);
if (ret)
goto fail;
}
return 0;
fail:
// 如果有失败的情况,就释放每个节点
if (!cachep->list.next) {
/* Cache is not active yet. Roll back what we did */
node--;
while (node >= 0) {
n = get_node(cachep, node);
if (n) {
kfree(n->shared);
free_alien_cache(n->alien);
kfree(n);
cachep->node[node] = NULL;
}
node--;
}
}
return -ENOMEM;
}
static int setup_kmem_cache_node(struct kmem_cache *cachep,
int node, gfp_t gfp, bool force_change)
{
int ret = -ENOMEM;
struct kmem_cache_node *n;
struct array_cache *old_shared = NULL;
struct array_cache *new_shared = NULL;
struct alien_cache **new_alien = NULL;
LIST_HEAD(list);
// 这个一般都是1,只有在没有打开numa或者只有1个cpu时才为0
if (use_alien_caches) {
new_alien = alloc_alien_cache(node, cachep->limit, gfp);
if (!new_alien)
goto fail;
}
// 需要共享
if (cachep->shared) {
// 分配share需要的内存
new_shared = alloc_arraycache(node,
cachep->shared * cachep->batchcount, 0xbaadf00d, gfp);
if (!new_shared)
goto fail;
}
// 初始化缓存node
ret = init_cache_node(cachep, node, gfp);
if (ret)
goto fail;
// 获得node对应的结构
n = get_node(cachep, node);
spin_lock_irq(&n->list_lock);
// 如果当前node有共享,先释放node里的内存
if (n->shared && force_change) {
free_block(cachep, n->shared->entry,
n->shared->avail, node, &list);
n->shared->avail = 0;
}
// 如果之前没有共享,或者强制改变,就设置新的共享对象
// 从上个函数里传过来的force_change是true,
if (!n->shared || force_change) {
old_shared = n->shared;
n->shared = new_shared;
new_shared = NULL;
}
// 设置新的alien
if (!n->alien) {
n->alien = new_alien;
new_alien = NULL;
}
spin_unlock_irq(&n->list_lock);
// 如果上面freelock有释放的内存,就在这里销毁
slabs_destroy(cachep, &list);
// 这是为了保护在中断关闭时,无锁访问n->shared的情况,所以需要进入宽限期
if (old_shared && force_change)
synchronize_rcu();
fail:
kfree(old_shared);
kfree(new_shared);
free_alien_cache(new_alien);
return ret;
}
static int init_cache_node(struct kmem_cache *cachep, int node, gfp_t gfp)
{
struct kmem_cache_node *n;
// 获取node对象
n = get_node(cachep, node);
if (n) {
// 如果之前已经分配过了,就只计算空闲的限制,如果slab超过了这个空闲值,就归还相应的内存
spin_lock_irq(&n->list_lock);
// todo: node序号越大,空闲限制越大?
n->free_limit = (1 + nr_cpus_node(node)) * cachep->batchcount +
cachep->num;
spin_unlock_irq(&n->list_lock);
return 0;
}
// 下面是cachep对应node的初始化,和上面所看到其他node初始化类似
// 分配一个node对象
n = kmalloc_node(sizeof(struct kmem_cache_node), gfp, node);
if (!n)
return -ENOMEM;
// 这里面是对象的一些初始化,比如初始化列表,设置常量为0,指针为NULL.
kmem_cache_node_init(n);
// todo: 下一次回收的时间?
n->next_reap = jiffies + REAPTIMEOUT_NODE +
((unsigned long)cachep) % REAPTIMEOUT_NODE;
// 再计算一下free_limit
n->free_limit =
(1 + nr_cpus_node(node)) * cachep->batchcount + cachep->num;
// 上面的get_node就是获取的cachep->node[node]
cachep->node[node] = n;
return 0;
}
int cache_random_seq_create(struct kmem_cache *cachep, unsigned int count,
gfp_t gfp)
{
struct rnd_state state;
// count是slab里要求的对象数量
// todo: 这个判断什么意思?
if (count < 2 || cachep->random_seq)
return 0;
// 分配random_seq数组
cachep->random_seq = kcalloc(count, sizeof(unsigned int), gfp);
if (!cachep->random_seq)
return -ENOMEM;
// 设置随机数种子
prandom_seed_state(&state, get_random_long());
// 把random_seq数组里的元素随机化
freelist_randomize(&state, cachep->random_seq, count);
return 0;
}
static void freelist_randomize(struct rnd_state *state, unsigned int *list,
unsigned int count)
{
unsigned int rand;
unsigned int i;
// 初始化列表里的每个值
for (i = 0; i < count; i++)
list[i] = i;
// 随机化列表里的值
for (i = count - 1; i > 0; i--) {
rand = prandom_u32_state(state);
rand %= (i + 1);
swap(list[i], list[rand]);
}
}