Slab
1.前言
很久很久以前:一个叫做Mark Hemment的哥儿们写了Slab。在接下来的一些年里,其他人对Slab进行了完善。后来,SLOB问世了。SLOB的目标是针对嵌入式系统 的,主要是适用于那些内存非常有限的系统,比如32MB以下的内存,它不太注重large smp系统,虽然后来这方面有一些小的改进。在之后,SLUB闪亮登场。它基本上属于对Slab的重设计(redesign),但是代码更少,并且 能更好的适应large NUMA系统。
SLUB被很认为是Slab和Slob的取代者,内核开发人员称其为:more SMP-friendly SLUB allocator。显然,在桌面平台上的多核心处理器也能从中受益。
在研究SLUB之前,先说说SLAB。众所周知,操作系统进行内存分配的时候,是以页为单位进行的,也可以称为内存块或者堆。但是内核对象远小于页的大 小,而这些对象在操作系统的生命周期中会被频繁的申请和释放,并且实验发现,这些对象初始化的时间超过了分配内存和释放内存的总时间,所以需要一种更细粒度的针对内核对象的分配算法,于是SLAB诞生了:
SLAB缓存已经释放的内核对象,以便下次申请时不需要再次初始化和分配空间,类似对象池的概念。并且没有修改通用的内存分配算法,以保证不影响大内存块分配时的性能。
由于SLAB按照对象的大小进行了分组,在分配的时候不会产生堆分配方式的碎片,也不会产生Buddy分配算法中的空间浪费,并且支持硬件缓存对齐来提高 TLB的性能,堪称完美。
但是这个世界上没有完美的算法,一个算法要么占用更多的空间以减少运算时间,要么使用更多的运算时间减少空间的占用。优秀的算法就是根据实际应用情况在这 两者之间找一个平衡点。SLAB虽然能更快的分配内核对象,但是metadata,诸如缓存队列等复杂层次结构占用了大量的内存。
SLUB因此而诞生: SLUB 不包含SLAB这么复杂的结构。SLAB不但有队列,而且每个SLAB开头保存了该SLAB对象的metadata。SLUB只将相近大小的对象对齐填入页面,并且保存了未分配的SLAB对象的链表,访问的时候容易快速定位,省去了队列遍历和头部metadata的偏移计算。该链表虽然和SLAB一样是每 CPU节点单独维护,但使用了一个独立的线程来维护全局的SLAB对象,一个CPU不使用的对象会被放到全局的partial队列,供其他CPU使用,平衡了各节点的SLAB对象。回收页面时,SLUB的SLAB对象是全局失效的,不会引起对象共享问题。另外,SLUB采用了合并相似SLAB对象的方法, 进一步减少内存的占用。
据内核开发人员称,SLUB相对于SLAB有5%-10%的性能提升和减少50%的内存占用。所以SLUB是一个时间和空间上均有改善的算法,而且SLUB完全兼容SLAB的接口,所以内核其他模块不需要修改即 可从SLUB的高性能中受益。SLUB在2.6.22内核中理所当然的替代了SLAB。
2.slab分配机制
slab分配器是基于对象进行管理的,所谓的对象就是内核中的数据结构(例如:task_struct,file_struct 等)。相同类型的对象归为一类,每当要申请这样一个对象时,slab分配器就从一个slab列表中分配一个这样大小的单元出去,而当要释放时,将其重新保存在该列表中,而不是直接返回给伙伴系统,从而避免内部碎片。slab分配器并不丢弃已经分配的对象,而是释放并把它们保存在内存中。slab分配对象时,会使用最近释放的对象的内存块,因此其驻留在cpu高速缓存中的概率会大大提高。
slab分配器有以下三个基本目标:
- 减少伙伴算法在分配小块连续内存时所产生的内部碎片
- 将频繁使用的对象缓存起来,减少分配、初始化和释放对象的时间开销
- 通过着色技术调整对象以更好的使用硬件高速缓存
3.slab的主要数据结构
注: slub和slab的数据结构不太一致,这里只是简单介绍,具体看源码分析
简要分析下这个图:kmem_cache是一个cache_chain的链表,描述了一个高速缓存,每个高速缓存包含了一个slabs的列表,这通常是一段连续的内存块。存在3种slab:slabs_full(完全分配的slab),slabs_partial(部分分配的slab),slabs_empty(空slab,或者没有对象被分配)。slab是slab分配器的最小单位,在实现上一个slab有一个或多个连续的物理页组成(通常只有一页)。单个slab可以在slab链表之间移动,例如如果一个半满slab被分配了对象后变满了,就要从slabs_partial中被删除,同时插入到slabs_full中去。
举例说明:如果有一个名叫inode_cachep的struct kmem_cache节点,它存放了一些inode对象。当内核请求分配一个新的inode对象时,slab分配器就开始工作了
4.slab分配器的任务
slab分配器分配内存以字节为单位,基于伙伴分配器的大内存进一步细分成小内存分配。换句话说,slab 分配器仍然从 Buddy 分配器中申请内存,之后自己对申请来的内存细分管理。
除了提供小内存外,slab 分配器的第二个任务是维护常用对象的缓存。对于内核中使用的许多结构,初始化对象所需的时间可等于或超过为其分配空间的成本。当创建一个新的slab 时,许多对象将被打包到其中并使用构造函数(如果有)进行初始化。释放对象后,它会保持其初始化状态,这样可以快速分配对象。
通过命令cat /proc/slabinfo可查看系统当前 slab 使用情况。以mm_struct结构体为例,当前系统已分配了 68 个mm_struct缓存,每个大小为 960字节,其中 active 的有 68 个。
可以看到,系统中存在的 slab 有些形如 kmalloc-xxx 的 slab,我们称其为通用型 slab,用来满足分配通用内存。其他含有具体名字的 slab 我们称其为 专用 slab,用来为特定结构体分配内存,如 mm_struct 等。
为什么要分专用和通用 slab ? 最直观的一个原因就是通用 slab 会造成内存浪费:出于 slab 管理的方便,每个 slab 管理的对象大小都是一致的,当我们需要分配一个处于 64-96字节中间大小的对象时,就必须从保存 96 字节的 slab 中分配。而对于专用的 slab,其管理的都是同一个结构体实例,申请一个就给一个恰好内存大小的对象,这就可以充分利用空间
5.slab实现原理
由于slab的缓存特性,slab 分配器从 buddy 分配器中获取的物理内存称为 内存缓存,使用结构体struct kmem_cache(定义在include/linux/slab_def.h文件中)描述。
SLAB分配器由可变数量的缓存组成,这些缓存由称为“缓存链”的双向循环链表链接在一起。在slab分配器的上下文中,缓存是特定类型的多个对象的管理器,例如使用cat /proc/slabinfo命令输出的mm_struct缓存,其名字保存在kmem_cache->name中(Linux 支持单个最大的 slab 缓存大小为32MB )。kmem_cache 中所有对象的大小是相同的(object_size),并且此 kmem_cache 中所有SLAB的大小也是相同的(gfporder、num)。
每个缓存节点在内存中维护称为slab的连续页块,这些页面被切成小块,用于缓存数据结构和对象。 kmem_cache的 kmem_cache_node 成员记录了该kmem_cache 下的所有 slabs 列表(注:在slub的实现中,只剩下了partial)。
源码分析
1. 关键数据结构
/*
* Slab cache management.
*/
struct kmem_cache {
//本地高速缓存,每CPU结构,对象释放时,优先放入这个本地CPU高速缓存中
struct kmem_cache_cpu __percpu *cpu_slab;
/* Used for retrieving partial slabs, etc. */
slab_flags_t flags;
/*
min_partial 变量的作用是控制 partial list 上页面分配的最小数量。
在页面分配过程中,如果 partial list 上的数量低于 min_partial,
那么分配器会尝试从伙伴系统获取更多的页面,以达到内存管理的效率和性能。
如果 partial list 上的页面数量高于 min_partial,
那么分配器则会尽可能从 partial list 上获取页面,
以减少分配请求的延迟和对内存管理的影响。
min_partial 的值会在系统运行时进行动态调整,
以适应不同的内存使用情况和负载特征。在内存较为稀缺的情况下,
可能会适当增加 min_partial 的值,以避免频繁地进行页面回收和伙伴映射,
提高系统的响应速度。而在内存充足的情况下,可能会适当降低 min_partial 的值,
以减少系统内存的浪费和提高内存使用率。
*/
unsigned long min_partial;
unsigned int size; /* The size of an object including metadata */
unsigned int object_size;/* The size of an object without metadata */
// 变量除以常量的优化,无符号除法
struct reciprocal_value reciprocal_size;
/*
Free pointer offset 指的是在内存中分配的块(如 struct page)中,
用于记录下一个空闲块的指针值距离该块起始地址的偏移量。
这个值通常会存储在结构体中的特定成员中,例如 Linux 操作系统中,
struct page 结构体中有一个成员叫作 “freelist”,用于存储空闲块列表的头结点,
而该成员所占据的内存空间就是 free pointer offset 的大小。
这个值的主要作用是,当申请新的内存块或者释放内存块时,
可以快速地获取上一个被使用的内存块位置,以进行内存的分配和释放,从而提高操作的效率
*/
unsigned int offset; /* Free pointer offset */
#ifdef CONFIG_SLUB_CPU_PARTIAL
/* Number of per cpu partial objects to keep around */
unsigned int cpu_partial;
#endif
//该结构体会描述申请页面的order值,以及object的个数
struct kmem_cache_order_objects oo;
/* Allocation and freeing of slabs */
struct kmem_cache_order_objects max;
struct kmem_cache_order_objects min;
gfp_t allocflags; /* gfp flags to use on each alloc */
int refcount; /* Refcount for slab cache destroy,引用计数为0时销毁 */
void (*ctor)(void *);
unsigned int inuse; /* Offset to metadata */
unsigned int align; /* Alignment */
unsigned int red_left_pad; /* Left redzone padding size */
const char *name; /* Name (only for display!) */
//kmem_cache最终会链接在一个全局链表中
struct list_head list; /* List of slab caches */
#ifdef CONFIG_SYSFS
struct kobject kobj; /* For sysfs */
#endif
#ifdef CONFIG_KASAN
struct kasan_cache kasan_info;
#endif
unsigned int useroffset; /* Usercopy region offset */
unsigned int usersize; /* Usercopy region size */
//Node管理的slab页面
struct kmem_cache_node *node[MAX_NUMNODES];
};
struct kmem_cache_cpu {
void **freelist; /* Pointer to next available object */
unsigned long tid; /* Globally unique transaction id */
//slab缓存的页面
struct page *page; /* The slab from which we are allocating */
#ifdef CONFIG_SLUB_CPU_PARTIAL
struct page *partial; /* Partially allocated frozen slabs */
#endif
};
/*
* The slab lists for all objects.
*/
struct kmem_cache_node {
spinlock_t list_lock;
#ifdef CONFIG_SLAB
struct list_head slabs_partial; /* partial list first, better asm code */
struct list_head slabs_full;
struct list_head slabs_free;
unsigned long total_slabs; /* length of all slab lists */
unsigned long free_slabs; /* length of free slab list only */
unsigned long free_objects;
unsigned int free_limit;
unsigned int colour_next; /* Per-node cache coloring */
struct array_cache *shared; /* shared per node */
struct alien_cache **alien; /* on other nodes */
unsigned long next_reap; /* updated without locking */
int free_touched; /* updated without locking */
#endif
#ifdef CONFIG_SLUB
// 用于表示该节点中部分填充的slab对象的数量。
// 当一个slab对象被部分使用时,它会被添加到该节点的partial列表中,并增加nr_partial的值
unsigned long nr_partial;
struct list_head partial; //slab页面链表
#ifdef CONFIG_SLUB_DEBUG
atomic_long_t nr_slabs;
atomic_long_t total_objects;
struct list_head full;
#endif
#endif
};
struct page { //删除了和slub无关的部分
/* 删除部分 */
union {
/* 删除部分 */
struct { /* slab, slob and slub */
union {
struct list_head slab_list;
struct { /* Partial pages */
struct page *next;
#ifdef CONFIG_64BIT
int pages; /* Nr of pages left */
int pobjects; /* Approximate count */
#else
short int pages;
short int pobjects;
#endif
};
};
struct kmem_cache *slab_cache; /* not slob */
/* Double-word boundary */
void *freelist; /* first free object */
union {
void *s_mem; /* slab: first object */
unsigned long counters; /* SLUB */
struct { /* SLUB */
unsigned inuse:16;
unsigned objects:15;
unsigned frozen:1;
};
};
};
/* 删除部分 */
};
/* 删除部分 */
} _struct_page_alignment;
2. slab_allocator内存分配关系图
3 kmem_cache_node和结构体kmem_cache
3.1 struct kmem_cache这个结构体类型的作用
该结构体的作用是用于管理大小为size (size = struct kmem_cache.size)的对象在各个node(NUMA架构)中在哪些slab page中分配。
其中struct kmem_cache.node[MAX_NUMNODES].partial用于维护每个node中有哪些slab page被用于分配对该size大小对象的allocate request。
当我们发起一个object allocate request后,其实不是先从struct kmem_cache.node[req_node].partial链表中查找合适的slab page并从中分配的,而是会先从struct kmem_cache.cpu_slab (struct kmem_cache_cpu __percpu类型)中查找slab page,如果没有找到,会将struct kmem_cache.node[req_node].partial中的slab page移到cpu_slab中,然后再从cpu_slab中分配,这样cpu_slab中就维护了一个special slab page list,这样可以提高分配的效率,该结构体struct kmem_cache_cpu是从cpu_area中单独分配的。
3.2 kmem_cache_node对象的作用(struct kmem_cache类型)
该对象的作用是为了分配struct kmem_cache_node类型对象。
struct kmem_cache_node类型对象是用来管理其对应node中的一些slab pages,而这些slab pages是由 struct kmem_cache.size对应的 struct kmem_cache_node所指向的。
kmem_cache_node.node[] 数组是struct kmem_cache_node* 类型数组,其每个元素指向一个struct kmem_cache_node类型对象,每个struct kmem_cache_node类型对象记录了大小为struct kmem_cache.size的对象应该在哪些slab page中分配;而这个kmem_cache_node对象(struct kmem_cache类型)就是用来描述系统中所有struct kmem_cache_node类型对象是如何分配的,具体就是通过kmem_cache_node.node[]指向的struct kmem_cache_node对象在指定的node中分配struct kmem_cache_node对象;是不是有点绕,这就是所谓的chicken-egg dilemma问题。
Linux early bootstrap阶段是如何解决这个问题的呢?实际就是early_kmem_cache_node_alloc函数的实现
-
系统会调用new_slab()函数从每个node对应自己的buddy system中为其分配一个page(page-order0=4K)用于分配各自node中的struct kmem_cache_node对象。
-
每个node新分配的page.freelist维护一个单向free_object_addr地址链表,该链表记录了在该slab page中哪些位置是空闲的可以用来分配struct kmem_cache_node对象,默认是指向该struct page所代表的PFN的基地址,每个free_object_addr用于存储下一个free_object_addr的地址,这样就构成一个list了。
-
kmem_cache_node.node[n]数组指向的每个struct kmem_cache_node对象占用每个node在上面步骤中分配的page.freelist指向的第一个free_object_addr,然后将page.freelist指向下一个free_object_addr,然后设置kmem_cache_node.node[n].partial = &page.slab_list,将该page加入到struct kmem_cache_node.partial(listhead类型),这样该page就被这个struct kmem_cache_node类型对象管理了,用于分配struct kmem_cache_node类型对象,这样就解决了chicken-egg问题了。
从partial是个listhead双向链表类型可以看出,一旦该struct kmem_cache_node对象管理的空间不足了,就继续从buddy system中分配新的page加入partial list即可。
4 kmem_cache_init
在5.10版本的内核中,kmem_cache_init函数的源码路径在mm/slub.c中,该函数的作用是初始化kmem_cache数据结构,该数据结构用于高效地管理内核堆内存。具体实现如下:
void __init kmem_cache_init(void)
{
static __initdata struct kmem_cache boot_kmem_cache,
boot_kmem_cache_node;
if (debug_guardpage_minorder())
slub_max_order = 0;
/*
*(1) boot_kmem_cache为编译时创建的静态数据,使用时不用内存分配,
用作slab系统的第一个slab cache,为其它所有的struct kmem_cache结构分配obj.
*(2)kmem_cache是一个全局的静态变量struct kmem_cache *kmem_cache
*(3)将编译时创建的静态数据变量boot_kmem_cache的地址赋值给全局静态变量kmem_cache
*(4) kmem_cache_node 同理
*/
kmem_cache_node = &boot_kmem_cache_node;
kmem_cache = &boot_kmem_cache;
// 见4.1详细分析
create_boot_cache(kmem_cache_node, "kmem_cache_node",
sizeof(struct kmem_cache_node), SLAB_HWCACHE_ALIGN, 0, 0);
register_hotmemory_notifier(&slab_memory_callback_nb);
/* Able to allocate the per node structures */
slab_state = PARTIAL;
// 这里根据实际node(NUMA)计算结构体kmem_cache的大小
create_boot_cache(kmem_cache, "kmem_cache",
offsetof(struct kmem_cache, node) +
nr_node_ids * sizeof(struct kmem_cache_node *),
SLAB_HWCACHE_ALIGN, 0, 0);
// 见4.5详细分析
kmem_cache = bootstrap(&boot_kmem_cache);
kmem_cache_node = bootstrap(&boot_kmem_cache_node);
// 走到这里已经可以随意的申请slab page了
/* Now we can use the kmem_cache to allocate kmalloc slabs */
// 详见4.10分析
setup_kmalloc_cache_index_table();
// 详见4.11分析
create_kmalloc_caches(0);
/* Setup random freelists for each cache */
// CONFIG_SLAB_FREELIST_RANDOM没开启什么也不做
init_freelist_randomization();
// CPU热插拔通知注册,没具体分析,大致上应该是如果产生CPU热插拔
// 则把对应CPU上的PCP中的内存全部下架
cpuhp_setup_state_nocalls(CPUHP_SLUB_DEAD, "slub:dead", NULL,
slub_cpu_dead);
pr_info("SLUB: HWalign=%d, Order=%u-%u, MinObjects=%u, CPUs=%u, Nodes=%u\n",
cache_line_size(),
slub_min_order, slub_max_order, slub_min_objects,
nr_cpu_ids, nr_node_ids);
}
4.1 create_boot_cache
/* Create a cache during boot when no slab services are available yet */
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;
unsigned int align = ARCH_KMALLOC_MINALIGN;
s->name = name;
//赋值size,则该kmem_cache对应的slab obj大小就被确定了
s->size = s->object_size = size;
/*
* For power of two sizes, guarantee natural alignment for kmalloc
* caches, regardless of SL*B debugging options.
*/
if (is_power_of_2(size))
align = max(align, size);
s->align = calculate_alignment(flags, align, size); // 计算对齐
s->useroffset = useroffset;
s->usersize = usersize;
// 见4.2详细分析
err = __kmem_cache_create(s, flags);
if (err)
panic("Creation of kmalloc slab %s size=%u failed. Reason %d\n",
name, size, err);
// 在启动的初期,还没有任何引用
s->refcount = -1; /* Exempt from merging for now */
}
4.2 kmem_cache_open
/*
* State of the slab allocator.
*
* This is used to describe the states of the allocator during bootup.
* Allocators use this to gradually bootstrap themselves. Most allocators
* have the problem that the structures used for managing slab caches are
* allocated from slab caches themselves.
*/
enum slab_state {
DOWN, /* No slab functionality yet */
PARTIAL, /* SLUB: kmem_cache_node available */
PARTIAL_NODE, /* SLAB: kmalloc size for node struct available */
UP, /* Slab caches usable but not all extras yet */
FULL /* Everything is working */
};
int __kmem_cache_create(struct kmem_cache *s, slab_flags_t flags)
{
int err;
err = kmem_cache_open(s, flags);
if (err)
return err;
/* Mutex is not taken during early boot */
// 在启动初期,slab_state = DOWN,所以这里直接返回了
if (slab_state <= UP)
return 0;
err = sysfs_slab_add(s);
if (err)
__kmem_cache_release(s);
return err;
}
static int kmem_cache_open(struct kmem_cache *s, slab_flags_t flags)
{
s->flags = kmem_cache_flags(s->size, flags, s->name);
// 初始化对象数,大小,分配order值等,如果开启了CONFIG_SLUB_DEBUG
// 还会对red_left_pad进行预留大小,通过calculate_order计算order(0~3)
// 注:3这个值通过宏PAGE_ALLOC_COSTLY_ORDER定义
// 即一个slab page结构最大可能包含8个实际page
// 通过oo_make(将order和size)拼接成kmem_cache_order_objects
// 计算s->oo s->min 和 s->max 实际上是在计算一个slab page能放几个obj
// 见4.3详细分析
if (!calculate_sizes(s, -1))
goto error;
/* 删除部分 */
/*
在进行内存分配时,partial list 上的页面数量会影响系统的内存使用效率。
如果 partial list 上的页面数量太少,那么分配请求可能会经常触发页面分配器,
并且由于需要频繁访问伙伴映射表,会导致系统性能下降。
而如果 partial list 上的页面数量过多,那么在空闲页面较少时就会出现“长时间借贷”现象,
导致页面使用不合理,降低系统的内存使用率。
因此,需要根据对象大小和系统负载等因素来确定合适的 partial list 页面数量。.
*/
//设置kmem_cache中的min_partial,它表示kmem_cache_node中partial链表可挂入的slab数量
set_min_partial(s, ilog2(s->size) / 2);
//设置每个CPU上的partial slabs的数量
set_cpu_partial(s);
/* 删除部分 */
/* 见下面源码和解析 */
if (!init_kmem_cache_nodes(s))
goto error;
if (alloc_kmem_cache_cpus(s))
return 0;
error:
__kmem_cache_release(s);
return -EINVAL;
}
static int init_kmem_cache_nodes(struct kmem_cache *s)
{
int node;
for_each_node_state(node, N_NORMAL_MEMORY) {
struct kmem_cache_node *n;
if (slab_state == DOWN) {
// 在初始化早期,slab没起来前会走到这里
// 见4.3详细分析
early_kmem_cache_node_alloc(node);
continue;
}
n = kmem_cache_alloc_node(kmem_cache_node,
GFP_KERNEL, node);
if (!n) {
free_kmem_cache_nodes(s);
return 0;
}
init_kmem_cache_node(n);
s->node[node] = n;
}
return 1;
}
4.3 calculate_sizes(slub obj-layout)
这里涉及到slub的object-layout
/*
* Object layout:
*
* object address
* Bytes of the object to be managed.
* If the freepointer may overlay the object then the free
* pointer is at the middle of the object.( 如果 freepointer 可以覆盖对象,则 free 指针位于对象的中间)
*
* Poisoning uses 0x6b (POISON_FREE) and the last byte is
* 0xa5 (POISON_END)
*
* object + s->object_size
* Padding to reach word boundary. This is also used for Redzoning.
* Padding is extended by another word if Redzoning is enabled and
* object_size == inuse.
*
* We fill with 0xbb (RED_INACTIVE) for inactive objects and with
* 0xcc (RED_ACTIVE) for objects in use.
*
* object + s->inuse
* Meta data starts here.
*
* A. Free pointer (if we cannot overwrite object on free)
* B. Tracking data for SLAB_STORE_USER
* C. Padding to reach required alignment boundary or at mininum
* one word if debugging is on to be able to detect writes
* before the word boundary.
*
* Padding is done using 0x5a (POISON_INUSE)
*
* object + s->size
* Nothing is used beyond s->size.
*
* If slabcaches are merged then the object_size and inuse boundaries are mostly
* ignored. And therefore no slab options that rely on these boundaries
* may be used with merged slabcaches.
*/
Layout如下,其中fp如果可以覆盖obj的内容的话,fp位于obj的中间位置
SLUBU DEBUG关闭的情况下,free pointer是内嵌在object之中的,但是SLUB DEBUG打开之后,free pointer是在object之外,并且多了很多其他的内存,例如red zone、trace和red_left_pad等。这里之所以将FP后移就是因为为了检测use-after-free问题,当free object时会在将object填充magic num(0x6b)。如果不后移的话,岂不是破坏了object之间的单链表关系。
按照calculate_sizes()中布局object。区域划分的layout就如同你看到上图的上半部分。但实际上却不是这样的。在struct page结构中有一个freelist指针,freelist会指向第一个available object(空闲obj)。在构建object之间的单链表的时候,object首地址实际上都会加上一个red_left_pad的偏移,这样实际的layout就如同图片中转换之后的layout。为什么会这样呢?因为在有SLUB DEBUG功能的时候,并没有检测left oob功能。这种转换是后续一个补丁的修改。补丁就是为了增加left oob检测功能。做了转换之后的red_left_pad就可以检测left oob。检测的方法和Red zone区域一样,填充的magic num也一样,差别只是检测的区域不一样而已。
关于开启了slub debug的相关的内容,后面再去详细分析
/*
* calculate_sizes() determines the order and the distribution of data within a slab object.
*/
static int calculate_sizes(struct kmem_cache *s, int forced_order)
{
slab_flags_t flags = s->flags;
unsigned int size = s->object_size;
unsigned int order;
// 将size按照void * 大小对齐
size = ALIGN(size, sizeof(void *));
#ifdef CONFIG_SLUB_DEBUG
// 是否支持内存下毒
if ((flags & SLAB_POISON) && !(flags & SLAB_TYPESAFE_BY_RCU) &&
!s->ctor)
s->flags |= __OBJECT_POISON;
else
s->flags &= ~__OBJECT_POISON;
/*
在进行 Redzoning 时,除了要添加 Redzone 区域外,还需要考虑内核对象和 Redzone
之间需要保留一定大小的空间,以免 Redzone 的信息覆盖了后面的内核对象。
因此,在向内核对象申请内存时,如果这个对象已经没有可用的空间能够存储
Redzone 的信息了,这段代码会为该对象再分配额外的内存空间
*/
if ((flags & SLAB_RED_ZONE) && size == s->object_size)
size += sizeof(void *);
#endif
/*
元数据偏移 = 内存对象 + (对齐) + Redzone 区域在内存中实际占用的字节数
*/
s->inuse = size;
if ((flags & (SLAB_TYPESAFE_BY_RCU | SLAB_POISON)) ||
((flags & SLAB_RED_ZONE) && s->object_size < sizeof(void *)) ||
s->ctor) {
/*
* Relocate free pointer after the object if it is not
* permitted to overwrite the first word of the object on
* kmem_cache_free.
*
* This is the case if we do RCU, have a constructor or
* destructor, are poisoning the objects, or are
* redzoning an object smaller than sizeof(void *).
*
* The assumption that s->offset >= s->inuse means free
* pointer is outside of the object is used in the
* freeptr_outside_object() function. If that is no
* longer true, the function needs to be modified.
*/
/* 开启slub debug等相关检测的话,fp位于元数据之后 */
s->offset = size;
size += sizeof(void *);
} else {
/*
如果没有开启slub debug等相关检测的话,FP则存在obj的中间位置
将 freelist 指针存储在对象的中间位置,以避免它靠近对象的边缘,
从而避免与相邻的分配发生小型溢出或下溢。这是一种防止内存访问越界的方法,
因为如果 freelist 指针位于对象的边缘附近,而相邻的对象大小不同,
则可能会出现溢出或下溢。将 freelist 指针存储在对象的中间位置可以避免这种情况发生,
从而提高内存的安全性
*/
s->offset = ALIGN_DOWN(s->object_size / 2, sizeof(void *));
}
#ifdef CONFIG_SLUB_DEBUG
if (flags & SLAB_STORE_USER)
/*
* Need to store information about allocs and frees after
* the object.
*/
// 在加两个struct track大小的空间
size += 2 * sizeof(struct track);
#endif
kasan_cache_create(s, &size, &s->flags);
#ifdef CONFIG_SLUB_DEBUG
if (flags & SLAB_RED_ZONE) {
/*
* Add some empty padding so that we can catch
* overwrites from earlier objects rather than let
* tracking information or the free pointer be
* corrupted if a user writes before the start
* of the object.
*/
// 在加上对齐后的red_left_pad大小
size += sizeof(void *);
s->red_left_pad = sizeof(void *);
s->red_left_pad = ALIGN(s->red_left_pad, s->align);
size += s->red_left_pad;
}
#endif
/*
SLUB 存储对象时,一个对象紧随另一个对象从偏移量 0 开始。
为了对齐对象,我们只需将每个对象的大小调整为符合对齐方式。
*/
size = ALIGN(size, s->align);
s->size = size;
s->reciprocal_size = reciprocal_value(size);
if (forced_order >= 0)
order = forced_order;
else
//计算order(0~3)即该slub只可能是1,2,4,8个page组成
order = calculate_order(size);
if ((int)order < 0)
return 0;
s->allocflags = 0;
// 如果order不为0 则一定申请成复合页
// 详情看5.3 复合页
if (order)
s->allocflags |= __GFP_COMP;
if (s->flags & SLAB_CACHE_DMA)
s->allocflags |= GFP_DMA;
if (s->flags & SLAB_CACHE_DMA32)
s->allocflags |= GFP_DMA32;
if (s->flags & SLAB_RECLAIM_ACCOUNT)
s->allocflags |= __GFP_RECLAIMABLE;
/*
* Determine the number of objects per slab
*/
s->oo = oo_make(order, size);
s->min = oo_make(get_order(size), size);
if (oo_objects(s->oo) > oo_objects(s->max))
s->max = s->oo;
return !!oo_objects(s->oo);
}
正常大小如下
4.4 early_kmem_cache_node_alloc
static void early_kmem_cache_node_alloc(int node)
{
struct page *page;
struct kmem_cache_node *n;
BUG_ON(kmem_cache_node->size < sizeof(struct kmem_cache_node));
// 详见4.5分析
page = new_slab(kmem_cache_node, GFP_NOWAIT, node);
/* 删除部分 */
// 第一个kmem_cache_node就是申请到的第一个slab指向的page的
// freelist对应的地址
n = page->freelist;
BUG_ON(!n);
#ifdef CONFIG_SLUB_DEBUG
// 如果开启slub debug 则给红区填充 0xcc
init_object(kmem_cache_node, n, SLUB_RED_ACTIVE);
init_tracking(kmem_cache_node, n);
#endif
n = kasan_kmalloc(kmem_cache_node, n, sizeof(struct kmem_cache_node), GFP_KERNEL);
// 记录fp的位置
page->freelist = get_freepointer(kmem_cache_node, n);
page->inuse = 1;
page->frozen = 0;
// 全局量kmem_cache_node的第一个节点就是申请到的这个n完成第一个蛋赋值
kmem_cache_node->node[node] = n;
// 初始化struct kmem_cache_node,链表和锁初始化,nr_partial = 0
init_kmem_cache_node(n);
inc_slabs_node(kmem_cache_node, node, page->objects);
/*
* No locks need to be taken here as it has just been
* initialized and there is no concurrent access.
*/
// 将该page->slab_list加入到n->partial链表中,实现管理
__add_partial(n, page, DEACTIVATE_TO_HEAD);
}
static inline void
__add_partial(struct kmem_cache_node *n, struct page *page, int tail)
{
n->nr_partial++;
if (tail == DEACTIVATE_TO_TAIL)
list_add_tail(&page->slab_list, &n->partial);
else
list_add(&page->slab_list, &n->partial);
}
4.5 new_slab
* A. page->freelist -> List of object free in a page
* B. page->inuse -> Number of objects in use
* C. page->objects -> Number of objects in page
* D. page->frozen -> frozen state
static struct page *new_slab(struct kmem_cache *s, gfp_t flags, int node)
{
if (unlikely(flags & GFP_SLAB_BUG_MASK))
flags = kmalloc_fix_flags(flags);
return allocate_slab(s,
flags & (GFP_RECLAIM_MASK | GFP_CONSTRAINT_MASK), node);
}
static struct page *allocate_slab(struct kmem_cache *s, gfp_t flags, int node)
{
struct page *page;
struct kmem_cache_order_objects oo = s->oo;
gfp_t alloc_gfp;
void *start, *p, *next;
int idx;
bool shuffle;
flags &= gfp_allowed_mask;
if (gfpflags_allow_blocking(flags))
local_irq_enable();
flags |= s->allocflags;
/*
* Let the initial higher-order allocation fail under memory pressure
* so we fall-back to the minimum order allocation.
*/
alloc_gfp = (flags | __GFP_NOWARN | __GFP_NORETRY) & ~__GFP_NOFAIL;
if ((alloc_gfp & __GFP_DIRECT_RECLAIM) && oo_order(oo) > oo_order(s->min))
alloc_gfp = (alloc_gfp | __GFP_NOMEMALLOC) & ~(__GFP_RECLAIM|__GFP_NOFAIL);
// 这里直接调用伙伴系统去申请page
page = alloc_slab_page(s, alloc_gfp, node, oo);
if (unlikely(!page)) {
oo = s->min;
alloc_gfp = flags;
/*
* Allocation may have failed due to fragmentation.
* Try a lower order alloc if possible
*/
// 如果第一次没申请到,使用更宽松的gfp去申请(fallback)
page = alloc_slab_page(s, alloc_gfp, node, oo);
if (unlikely(!page))
goto out;
stat(s, ORDER_FALLBACK);
}
// 设置该slub页有多少个obj
page->objects = oo_objects(oo);
// kmem_cache设置
page->slab_cache = s;
/*
这个__SetPageSlab函数是搜索不到定义位置的,因为这个函数的定义使用
#define的##连接操作。
具体位置是源码include/linux/page-flags.h中
最后实现调用 __set_bit 给page->flag标记为PG_slab
*/
__SetPageSlab(page);
/*
* Return true only if the page has been allocated with
* ALLOC_NO_WATERMARKS and the low watermark was not
* met implying that the system is under some pressure.
*/
if (page_is_pfmemalloc(page))
SetPageSlabPfmemalloc(page);
kasan_poison_slab(page);
// start = 该page的虚拟地址
start = page_address(page);
// #define POISON_INUSE 0x5a
// 如果开启内存下毒,则将该page全部memset成0x5a
setup_page_debug(s, page, start);
// 未开启CONFIG_SLAB_FREELIST_RANDOM则一定为false
shuffle = shuffle_freelist(s, page);
if (!shuffle) {
// 在这里进行转化见4.3图,start += red_left_pad
start = fixup_red_left(s, start);
#define SLUB_RED_INACTIVE 0xbb
// 如果开启CONFIG_SLUB_DEBUG,则对obj进行数据填充0xbb
start = setup_object(s, page, start);
// 该page的freelist指向转换后的page的虚拟地址
// 即如果没有开启各种调试的话,就是指向该page本身
page->freelist = start;
for (idx = 0, p = start; idx < page->objects - 1; idx++) {
// 获取下一个obj的地址
next = p + s->size;
// 继续初始化obj
next = setup_object(s, page, next);
// 这里将fp设置到obj中,因为是新申请的slub页
// 所以obj一定都是空的,fp可以覆盖obj
set_freepointer(s, p, next);
p = next;
}
// 最后一个obj的fp指向空,这样一个单链表就维护好了
set_freepointer(s, p, NULL);
}
// 有objects个数量可以使用
page->inuse = page->objects;
// 此时page处于冻结状态
page->frozen = 1;
out:
if (gfpflags_allow_blocking(flags))
local_irq_disable();
if (!page)
return NULL;
// 见下面源码
inc_slabs_node(s, page_to_nid(page), page->objects);
return page;
}
void *fixup_red_left(struct kmem_cache *s, void *p)
{
if (kmem_cache_debug_flags(s, SLAB_RED_ZONE))
p += s->red_left_pad;
return p;
}
static inline void set_freepointer(struct kmem_cache *s, void *object, void *fp)
{
unsigned long freeptr_addr = (unsigned long)object + s->offset;
// freeptr_addr所指向的地址的内容为fp的值
// 即 当前fp = next,形成一个单链表
*(void **)freeptr_addr = freelist_ptr(s, fp, freeptr_addr);
}
static inline void inc_slabs_node(struct kmem_cache *s, int node, int objects)
{
struct kmem_cache_node *n = get_node(s, node);
/*
* May be called early in order to allocate a slab for the
* kmem_cache_node structure. Solve the chicken-egg
* dilemma by deferring the increment of the count during
* bootstrap (see early_kmem_cache_node_alloc).
*/
if (likely(n)) {
// 为创建好的kmem_cache_node节点中增加管理的slab数量和总obj
// 完成无中生有
atomic_long_inc(&n->nr_slabs);
atomic_long_add(objects, &n->total_objects);
}
}
4.6 bootstrap
/*
* Used for early kmem_cache structures that were allocated using
* the page allocator. Allocate them properly then fix up the pointers
* that may be pointing to the wrong kmem_cache structure.
*/
static struct kmem_cache * __init bootstrap(struct kmem_cache *static_cache)
{
int node;
// slab_alloc –> slab_alloc_node 详见4.6分析
struct kmem_cache *s = kmem_cache_zalloc(kmem_cache, GFP_NOWAIT);
struct kmem_cache_node *n;
// 把静态的kmem_cache数据赋值给新申请的kmem_cache *s中
memcpy(s, static_cache, kmem_cache->object_size);
/*
该代码块运行非常早,只有启动处理器(boot processor)处于运行状态。
即使有其他处理器在运行,中断请求 IRQs (IRQs)也未启用,因此无法触发中断处理程序(IPIs)
*/
// 后面详细分析
__flush_cpu_slab(s, smp_processor_id());
// 遍历所有node获取kmem_cache_node
for_each_kmem_cache_node(s, node, n) {
struct page *p;
// 由于前面已经将申请的page的slab_lisb加入到了对应的
// kmem_cache_node[node]->partial的管理中
// 因此通过链表kmem_cache_node[node]->partial 可以遍历所有的page
// 然后将这些page的slab_cache指向刚申请的kmem_cache *s
list_for_each_entry(p, &n->partial, slab_list)
p->slab_cache = s;
#ifdef CONFIG_SLUB_DEBUG
list_for_each_entry(p, &n->full, slab_list)
p->slab_cache = s;
#endif
}
// 所有的slab_cache都要加入slab_caches链表中
list_add(&s->list, &slab_caches);
return s;
}
4.7 slab_alloc_node
下图简单描述了一下slab的申请情况
快速路径下,以原子的方式检索per-CPU缓存的freelist列表中的第一个对象,如果freelist为空并且没有要检索的对象,则跳入慢速路径操作,最后再返回到快速路径中重试操作
/*
* Inlined fastpath so that allocation functions (kmalloc, kmem_cache_alloc)
* have the fastpath folded into their functions. So no function call
* overhead for requests that can be satisfied on the fastpath.
*
* The fastpath works by first checking if the lockless freelist can be used.
* If not then __slab_alloc is called for slow processing.
*
* Otherwise we can simply pick the next object from the lockless free list.
*/
static __always_inline void *slab_alloc_node(struct kmem_cache *s,
gfp_t gfpflags, int node, unsigned long addr)
{
void *object;
struct kmem_cache_cpu *c;
struct page *page;
unsigned long tid;
struct obj_cgroup *objcg = NULL;
s = slab_pre_alloc_hook(s, &objcg, 1, gfpflags);
if (!s)
return NULL;
redo:
// 保证内存申请在同一个CPU上执行的
do {
tid = this_cpu_read(s->cpu_slab->tid);
c = raw_cpu_ptr(s->cpu_slab);
} while (IS_ENABLED(CONFIG_PREEMPTION) &&
unlikely(tid != READ_ONCE(c->tid)));
barrier();
/*
* The transaction ids are globally unique per cpu and per operation on
* a per cpu queue. Thus they can be guarantee that the cmpxchg_double
* occurs on the right processor and that there was no operation on the
* linked list in between.
*/
// obj/page地址被赋值为cpu->freelist/page
object = c->freelist;
page = c->page;
// 如果obj或者page有一个是空的,说明PCP中某一个不够了,需要走慢速路径
if (unlikely(!object || !page || !node_match(page, node))) {
// 详见4.7 分析
object = __slab_alloc(s, gfpflags, node, addr, c);
} else {
// 快速路径
void *next_object = get_freepointer_safe(s, object);
/*
cmpxchg 操作只有在没有其他操作的情况下,并且在正确的处理器上才会匹配。
这个操作原子地完成以下操作(没有锁!):
* 将第一个指针重定位到当前的 per cpu 区域。
* 验证 tid 和 freelist 是否已更改。
* 如果它们没有更改,则替换 tid 和 freelist。
由于这是没有锁的,所以保护仅针对在此 CPU 上执行的代码,而不是来自其他 CPU 的访问。
*/
// 判断obj和tid都是当前cpu_slab的,将next赋值给cpu_slub
if (unlikely(!this_cpu_cmpxchg_double(
s->cpu_slab->freelist, s->cpu_slab->tid,
object, tid,
next_object, next_tid(tid)))) {
note_cmpxchg_failure("slab_alloc", s, tid);
goto redo;
}
// 如果命中了的话,预加载next_obj,当前的就拿走用掉
prefetch_freepointer(s, next_object);
// 标记为快速申请
stat(s, ALLOC_FASTPATH);
}
// fp指向的地址中的数据清0,毕竟已经要申请拿走用了,fp的数据不能保留
maybe_wipe_obj_freeptr(s, object);
// obj数据全部清0
if (unlikely(slab_want_init_on_alloc(gfpflags, s)) && object)
memset(object, 0, s->object_size);
slab_post_alloc_hook(s, objcg, gfpflags, 1, &object);
return object;
}
4.8 __slab_alloc
慢速申请路径图示:
slowpath-1:将per-CPU缓存中page指向的slab页中的空闲对象迁移到freelist中,如果有空闲对象,则freeze该页面,没有空闲对象则跳转到slowpath-2
slowpath-2:将per-CPU缓存中partial链表中的第一个slab页迁移到page指针中,如果partial链表为空,则跳转到slowpath-3
slowpath-3:将Node管理的partial链表中的slab页迁移到per-CPU缓存中的page中,并重复第二个slab页将其添加到per-CPU缓存中的partial链表中。如果迁移的slab中空闲对象超过了kmem_cache.cpu_partial的一半,则仅迁移slab页,并且不再重复。
如果每个Node的partial链表都为空,跳转到slowpath-4。
slowpath-4:从Buddy System中获取页面,并将其添加到per-CPU的page中。
/*
* Another one that disabled interrupt and compensates for possible
* cpu changes by refetching the per cpu area pointer.
*/
static void *__slab_alloc(struct kmem_cache *s, gfp_t gfpflags, int node, unsigned long addr, struct kmem_cache_cpu *c)
{
void *p;
unsigned long flags;
local_irq_save(flags);
#ifdef CONFIG_PREEMPTION
/*
* We may have been preempted and rescheduled on a different
* cpu before disabling interrupts. Need to reload cpu area
* pointer.
*/
c = this_cpu_ptr(s->cpu_slab);
#endif
p = ___slab_alloc(s, gfpflags, node, addr, c);
local_irq_restore(flags);
return p;
}
/*
* Slow path. The lockless freelist is empty or we need to perform
* debugging duties.
*
* Processing is still very fast if new objects have been freed to the
* regular freelist. In that case we simply take over the regular freelist
* as the lockless freelist and zap the regular freelist.
*
* If that is not working then we fall back to the partial lists. We take the
* first element of the freelist as the object to allocate now and move the
* rest of the freelist to the lockless freelist.
*
* And if we were unable to get a new slab from the partial slab lists then
* we need to allocate a new slab. This is the slowest path since it involves
* a call to the page allocator and the setup of a new slab.
*
* Version of __slab_alloc to use when we know that interrupts are
* already disabled (which is the case for bulk allocation).
*/
static void *___slab_alloc(struct kmem_cache *s, gfp_t gfpflags, int node, unsigned long addr, struct kmem_cache_cpu *c)
{
void *freelist;
struct page *page;
// 慢速申请路径
stat(s, ALLOC_SLOWPATH);
page = c->page;
if (!page) {
/*
* if the node is not online or has no normal memory, just
* ignore the node constraint
*/
if (unlikely(node != NUMA_NO_NODE &&
!node_state(node, N_NORMAL_MEMORY)))
node = NUMA_NO_NODE;
// PCP中缓存的page都没有了直接跳到new_slab
goto new_slab;
}
redo:
// 对于NUMA架构设备会可能会出现该page和node不匹配的问题
// 非NUMA架构的不会进这里
if (unlikely(!node_match(page, node))) {
/*
* same as above but node_match() being false already
* implies node != NUMA_NO_NODE
*/
if (!node_state(node, N_NORMAL_MEMORY)) {
node = NUMA_NO_NODE;
goto redo;
} else {
stat(s, ALLOC_NODE_MISMATCH);
// Remove the cpu slab
deactivate_slab(s, page, c->freelist, c);
goto new_slab;
}
}
/*
* By rights, we should be searching for a slab page that was
* PFMEMALLOC but right now, we are losing the pfmemalloc
* information when the page leaves the per-cpu allocator
*/
/*
在寻找 slab page 的时候,实际上应该寻找被标记为 PFMEMALLOC 的 slab page,
但是目前在 slab page 离开 per-cpu 分配器时,会失去 pfmemalloc 信息,
所以无法判断该 slab page 是否是 pfmemalloc (大概意思是作为slub分配出来的page,
正常都要带有___GFP_MEMALLOC/PF_MEMALLOC这种属性?目前不是很理解)
*/
if (unlikely(!pfmemalloc_match(page, gfpflags))) {
// 看程序就是分析page的flag属性和要申请的gfp是否一致
// 不一致也要重新申请
// 强制下架当前cpu_slab->page,
// 也就是强制切换cpu_slab控制的slab page
deactivate_slab(s, page, c->freelist, c);
goto new_slab;
}
/* must check again c->freelist in case of cpu migration or IRQ */
// 最后在确认下cpu->freelist(下一个obj)是不是真的是空的,
// 不是不是空的则从freelist获取
freelist = c->freelist;
if (freelist)
goto load_freelist;
// 获取page->freelist 如果非空则后续将page->freelst赋值给该cpu的PCP
freelist = get_freelist(s, page);
if (!freelist) {
// 如果这都为空,就一定要重新申请了
c->page = NULL;
stat(s, DEACTIVATE_BYPASS);
goto new_slab;
}
stat(s, ALLOC_REFILL);
load_freelist:
/*
* freelist 指向待使用的对象列表.
* page 指向用于获取对象的页面.
* 必须冻结该页面以使 per-cpu 分配生效
*/
VM_BUG_ON(!c->page->frozen);
c->freelist = get_freepointer(s, freelist);
c->tid = next_tid(c->tid);
// 以上都是slow-path-1
// 通过freelist获取的方式(相当于将slub_page交换到了cpu_slub)
return freelist;
new_slab:
// slow-path-2
// 看看PCP中 cpu->partial还有没有slub页缓存了
if (slub_percpu_partial(c)) {
// 有的话从中获取一个page,到这里还都是PCP缓存中获取的
page = c->page = slub_percpu_partial(c);
slub_set_percpu_partial(c, page);
stat(s, CPU_PARTIAL_ALLOC);
// 如果ok 重来一遍
goto redo;
}
// slow-path-3
// 如果PCP中真的没了,优先尝试从kmem_cache_node[node]->partial中获取
// 如果也获取不到,最后才会使用new_slab函数,在伙伴系统中申请一个page
// 详见4.8解析
freelist = new_slab_objects(s, gfpflags, node, &c);
if (unlikely(!freelist)) {
// 如果这都获取不了,那恭喜系统喜提slub oom
slab_out_of_memory(s, gfpflags, node);
return NULL;
}
page = c->page;
// 如果没开启slub debug 并且flag匹配的上,走load_freelist路线
if (likely(!kmem_cache_debug(s) && pfmemalloc_match(page, gfpflags)))
goto load_freelist;
/* Only entered in the debug case */
// 开了slub debug后对obj进行初始化set_track和trace的工作
if (kmem_cache_debug(s) &&
!alloc_debug_processing(s, page, freelist, addr))
goto new_slab; /* Slab failed checks. Next slab needed */
// 下架现cpu_slab
deactivate_slab(s, page, get_freepointer(s, freelist), c);
return freelist;
}
4.9 new_slab_objects
static inline void *new_slab_objects(struct kmem_cache *s, gfp_t flags,
int node, struct kmem_cache_cpu **pc)
{
void *freelist;
struct kmem_cache_cpu *c = *pc;
struct page *page;
WARN_ON_ONCE(s->ctor && (flags & __GFP_ZERO));
// slow-path-3
// 获取node 的partial slab
freelist = get_partial(s, flags, node, c);
// 如果获取到了那就直接用node里剩余的partial slab
if (freelist)
return freelist;
// slow-path-4
// 要是node里面没有了,调用new_slab分配新page
page = new_slab(s, flags, node);
if (page) {
// 获取当前cpu
c = raw_cpu_ptr(s->cpu_slab);
// 如果当前cpu有page则flush_slab刷新,这里会将page强制下架
if (c->page)
/*
如果申请好了新slab page,当前cpu_slab->page不为空,则会调用flush_slab
然后调用deactivate_slab 强制下架cpu_slab->page。
也就是我现在有新的了,必须得把老的下架放回node中,
否则新的slab page不知道放哪(总不能刚申请完就放到node中)
*/
// 详见4.9解析
flush_slab(s, c);
/*
* No other reference to the page yet so we can
* muck around with it freely without cmpxchg
*/
// 更新freelist、page
freelist = page->freelist;
page->freelist = NULL;
stat(s, ALLOC_SLAB);
// 该page被cpu_slab控制
c->page = page;
*pc = c;
}
return freelist;
}
4.10 deactivate_slab
deactivate_slab 函数完成强制下架,把cpu_slab当前控制的slab page状态更新,并根据现在的slab 分配情况(半空、满、空)放入对应的node的list中或者销毁掉。
static inline void flush_slab(struct kmem_cache *s, struct kmem_cache_cpu *c)
{
stat(s, CPUSLAB_FLUSH);
deactivate_slab(s, c->page, c->freelist, c);
c->tid = next_tid(c->tid);
}
/*
* Remove the cpu slab
*/
static void deactivate_slab(struct kmem_cache *s, struct page *page,
void *freelist, struct kmem_cache_cpu *c)
{
enum slab_modes { M_NONE, M_PARTIAL, M_FULL, M_FREE };
struct kmem_cache_node *n = get_node(s, page_to_nid(page));
int lock = 0;
enum slab_modes l = M_NONE, m = M_NONE;
void *nextfree;
// 默认情况下,移除的slab放到表头,因为是通过flush_slab调用,
// 放到表头之后可在短时间内再次使用
int tail = DEACTIVATE_TO_HEAD;
struct page new;
struct page old;
// 如果要被下架的page的freelist还非空说明链表后续还有挺多obj
// 那后面把这些obj加到node->partial链表尾中
if (page->freelist) {
stat(s, DEACTIVATE_REMOTE_FREES);
tail = DEACTIVATE_TO_TAIL;
}
/*
这个阶段的主要目的是让所有可用的 per cpu 对象回收到页空闲列表(page freelist)中,
并且保持该页面仍处于被冻结状态,只留下一个 per-cpu 对象不被回收。
在这个阶段中不需要获取页面 freelist 的锁,因为页面仍处于被冻结状态
*/
while (freelist && (nextfree = get_freepointer(s, freelist))) {
void *prior;
unsigned long counters;
/*
* 如果 nextfree 指针无效,那么不能简单地将 freelist 中的对象添加到空闲队列中。
* 相反,它们需要被隔离,以防止对它们进行进一步的修改或破坏.
*/
if (freelist_corrupted(s, page, &freelist, nextfree))
break;
// 执行obj回收操作
do {
prior = page->freelist;
counters = page->counters;
// 将freelist指向(cpu_slab)page->freelist
set_freepointer(s, freelist, prior);
// 这里new保存当前counters的值
// 注意page结构,counters和inuse, objects,frozen是一个union
new.counters = counters;
new.inuse--;
// 必须保证全程处于冻结状态
VM_BUG_ON(!new.frozen);
// 确认当前(cpu_slab)page的freelist和counters与旧值是否一致
// 如果不一致就重新执行
// 如果一致实际上(cpu_slab)page的freelist没变
// counters中的inuse - 1
} while (!__cmpxchg_double_slab(s, page,
prior, counters,
freelist, new.counters,
"drain percpu freelist"));
//
freelist = nextfree;
}
// 走到这里原freelist(obj)已经指向了page的freelist
// 当while执行完成之后,应该是除了最后一个obj之外全部都指向了
// page->freelist
/*
* Stage two: Ensure that the page is unfrozen while the
* list presence reflects the actual number of objects
* during unfreeze.
*
* We setup the list membership and then perform a cmpxchg
* with the count. If there is a mismatch then the page
* is not unfrozen but the page is on the wrong list.
*
* Then we restart the process which may have to remove
* the page from the list that we just put it on again
* because the number of objects in the slab may have
* changed.
*/
redo:
old.freelist = page->freelist;
old.counters = page->counters;
// 必须保证此时的page是冻结状态
VM_BUG_ON(!old.frozen);
/* Determine target state of the slab */
new.counters = old.counters;
// 此时freelist指向移除的slab的最后一个可用obj
if (freelist) {
new.inuse--;
set_freepointer(s, freelist, old.freelist);
new.freelist = freelist;
} else
// new.freelist = old.freelist = page->freelist
new.freelist = old.freelist;
// 解冻,该标志位代表是否被cpu_slab控制
new.frozen = 0;
// slab为空,并且节点的partial slab数量大于最小值,
// 可以释放要移除的slab,设置为M_FREE状态
if (!new.inuse && n->nr_partial >= s->min_partial)
m = M_FREE;
else if (new.freelist) {
// slab的freelist有可用对象,应该放到partial表
m = M_PARTIAL;
if (!lock) {
lock = 1;
/*
* Taking the spinlock removes the possibility
* that acquire_slab() will see a slab page that
* is frozen
*/
spin_lock(&n->list_lock);
}
} else {
// 如果page->freelist为NULL,说明该slub已经用满了(obj没了)
m = M_FULL;
#ifdef CONFIG_SLUB_DEBUG
if ((s->flags & SLAB_STORE_USER) && !lock) {
lock = 1;
/*
* This also ensures that the scanning of full
* slabs from diagnostic functions will not see
* any frozen slabs.
*/
spin_lock(&n->list_lock);
}
#endif
}
// 第一次走到这里的话 l 一定不等于 m
if (l != m) {
if (l == M_PARTIAL)
remove_partial(n, page);
else if (l == M_FULL)
remove_full(s, n, page);
if (m == M_PARTIAL)
// 将page加到node->partial尾
add_partial(n, page, tail);
else if (m == M_FULL)
// 如果开启SLUB_DEBUG 则加到node->full,否则什么也不做
add_full(s, n, page);
}
// 记录状态
l = m;
// 这里如果比较不相等(因为没带锁,也有可能是别的CPU在更新?这里确实比较迷惑)
// 如果修改page的freelist失败,回到redo重新执行。
// 这时需要根据l的状态先把之前添加到节点的slab移除,
// 然后再插入,以保证一致性
if (!__cmpxchg_double_slab(s, page,
old.freelist, old.counters,
new.freelist, new.counters,
"unfreezing slab"))
goto redo;
if (lock)
spin_unlock(&n->list_lock);
if (m == M_PARTIAL)
stat(s, tail);
else if (m == M_FULL)
stat(s, DEACTIVATE_FULL);
else if (m == M_FREE) {
stat(s, DEACTIVATE_EMPTY);
// 这里执行 free_slab 操作,后面再详细分析
discard_slab(s, page);
stat(s, FREE_SLAB);
}
// 最后彻底清空cpu_slab 的page 和freelist,完成下架
c->page = NULL;
c->freelist = NULL;
}
关于cmpxchg函数完成的功能是:将old和ptr指向的内容比较,如果相等,则将new写入到ptr中,返回old,如果不相等,则返回ptr指向的内容
/* Interrupts must be disabled (for the fallback code to work right) */
static inline bool __cmpxchg_double_slab(struct kmem_cache *s, struct page *page,
void *freelist_old, unsigned long counters_old,
void *freelist_new, unsigned long counters_new,
const char *n)
{
VM_BUG_ON(!irqs_disabled());
#if defined(CONFIG_HAVE_CMPXCHG_DOUBLE) && \
defined(CONFIG_HAVE_ALIGNED_STRUCT_PAGE)
if (s->flags & __CMPXCHG_DOUBLE) {
if (cmpxchg_double(&page->freelist, &page->counters,
freelist_old, counters_old,
freelist_new, counters_new))
return true;
} else
#endif
{
slab_lock(page);
// 实际上这里就是cmpxchg的C实现
// 将page->freelist/counters和old比较,如果相等
if (page->freelist == freelist_old &&
page->counters == counters_old) {
// 将新内容写入到指针中
page->freelist = freelist_new;
page->counters = counters_new;
slab_unlock(page);
return true;
}
slab_unlock(page);
}
cpu_relax();
stat(s, CMPXCHG_DOUBLE_FAIL);
#ifdef SLUB_DEBUG_CMPXCHG
pr_info("%s %s: cmpxchg double redo ", n, s->name);
#endif
return false;
}
4.11 setup_kmalloc_cache_index_table
这步就是初始化kmalloc_cache的索引表
/*
* 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 u8 size_index[24] __ro_after_init = {
3, /* 8 */ ---- 7
4, /* 16 */ ---- 7
5, /* 24 */ ---- 7
5, /* 32 */ ---- 7
6, /* 40 */ ---- 7
6, /* 48 */ ---- 7
6, /* 56 */ ---- 7
6, /* 64 */ ---- 7
1, /* 72 */ ---- 7
1, /* 80 */ ---- 7
1, /* 88 */ ---- 7
1, /* 96 */ ---- 7
7, /* 104 */ ---- 7
7, /* 112 */ ---- 7
7, /* 120 */ ---- 7
7, /* 128 */ ---- 7 // 这之前都使用kmalloc-128申请
2, /* 136 */ ---- 8 // 这之后都使用kmalloc-256申请
2, /* 144 */ ---- 8
2, /* 152 */ ---- 8
2, /* 160 */ ---- 8
2, /* 168 */ ---- 8
2, /* 176 */ ---- 8
2, /* 184 */ ---- 8
2 /* 192 */ ---- 8
};
static inline unsigned int size_index_elem(unsigned int bytes)
{
return (bytes - 1) / 8;
}
/*
* Patch up the size_index table if we have strange large alignment
* requirements for the kmalloc array. This is only the case for
* MIPS it seems. The standard arches will not generate any code here.
*
* Largest permitted alignment is 256 bytes due to the way we
* handle the index determination for the smaller caches.
*
* Make sure that nothing crazy happens if someone starts tinkering
* around with ARCH_KMALLOC_MINALIGN
*/
void __init setup_kmalloc_cache_index_table(void)
{
unsigned int i;
BUILD_BUG_ON(KMALLOC_MIN_SIZE > 256 ||
(KMALLOC_MIN_SIZE & (KMALLOC_MIN_SIZE - 1)));
// 其中KMALLOC_MIN_SIZE = 128,
for (i = 8; i < KMALLOC_MIN_SIZE; i += 8) {
unsigned int elem = size_index_elem(i);
// elem最大是15
if (elem >= ARRAY_SIZE(size_index))
break;
// 也就是说size_index[0-15] 都赋值为7
// 这个值后面kmalloc时候会用在分析
size_index[elem] = KMALLOC_SHIFT_LOW;
}
if (KMALLOC_MIN_SIZE >= 64) {
/*
* The 96 byte size cache is not used if the alignment
* is 64 byte.
*/
// 64以上 96以下,统统用索引7的kmalloc_caches申请
for (i = 64 + 8; i <= 96; i += 8)
size_index[size_index_elem(i)] = 7;
}
if (KMALLOC_MIN_SIZE >= 128) {
/*
* The 192 byte sized cache is not used if the alignment
* is 128 byte. Redirect kmalloc to use the 256 byte cache
* instead.
*/
// 128以上,192以下,用索引8的kmalloc_caches申请
for (i = 128 + 8; i <= 192; i += 8)
size_index[size_index_elem(i)] = 8;
}
}
4.12 create_kmalloc_caches
/*
* Whenever changing this, take care of that kmalloc_type() and
* create_kmalloc_caches() still work as intended.
*/
enum kmalloc_cache_type {
// 规定 kmalloc 内存池的内存需要在 NORMAL 直接映射区分配
KMALLOC_NORMAL = 0,
// 规定 kmalloc 内存池中的内存是可以回收的,比如文件页缓存,匿名页
KMALLOC_RECLAIM,
#ifdef CONFIG_ZONE_DMA
KMALLOC_DMA,
#endif
NR_KMALLOC_TYPES
};
extern struct kmem_cache *
kmalloc_caches[NR_KMALLOC_TYPES][KMALLOC_SHIFT_HIGH + 1];
/*
* 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.
*/
const struct kmalloc_info_struct kmalloc_info[] __initconst = {
INIT_KMALLOC_INFO(0, 0),
INIT_KMALLOC_INFO(96, 96),
INIT_KMALLOC_INFO(192, 192),
INIT_KMALLOC_INFO(8, 8),
INIT_KMALLOC_INFO(16, 16),
INIT_KMALLOC_INFO(32, 32),
INIT_KMALLOC_INFO(64, 64),
INIT_KMALLOC_INFO(128, 128), // 7
INIT_KMALLOC_INFO(256, 256), // 8
INIT_KMALLOC_INFO(512, 512), // 9
INIT_KMALLOC_INFO(1024, 1k), // 10
INIT_KMALLOC_INFO(2048, 2k), // 11
INIT_KMALLOC_INFO(4096, 4k), // 12
INIT_KMALLOC_INFO(8192, 8k), // 13
INIT_KMALLOC_INFO(16384, 16k),
INIT_KMALLOC_INFO(32768, 32k),
INIT_KMALLOC_INFO(65536, 64k),
INIT_KMALLOC_INFO(131072, 128k),
INIT_KMALLOC_INFO(262144, 256k),
INIT_KMALLOC_INFO(524288, 512k),
INIT_KMALLOC_INFO(1048576, 1M),
INIT_KMALLOC_INFO(2097152, 2M),
INIT_KMALLOC_INFO(4194304, 4M),
INIT_KMALLOC_INFO(8388608, 8M),
INIT_KMALLOC_INFO(16777216, 16M),
INIT_KMALLOC_INFO(33554432, 32M),
INIT_KMALLOC_INFO(67108864, 64M)
};
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;
// 创建kmem_cache * 的kmalloc_cache
kmalloc_caches[type][idx] = create_kmalloc_cache(
kmalloc_info[idx].name[type],
kmalloc_info[idx].size, flags, 0,
kmalloc_info[idx].size);
}
/*
* 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(slab_flags_t flags)
{
int i;
enum kmalloc_cache_type type;
#define KMALLOC_SHIFT_HIGH (PAGE_SHIFT + 1) = 13
for (type = KMALLOC_NORMAL; type <= KMALLOC_RECLAIM; type++) {
for (i = KMALLOC_SHIFT_LOW; i <= KMALLOC_SHIFT_HIGH; i++) {
// 从7~13 创建 即 kmalloc-128 ~ 8k
if (!kmalloc_caches[type][i])
new_kmalloc_cache(i, type, flags);
/* 删除部分 */
}
}
/* Kmalloc array is now usable */
// 到这里kmalloc已经可以使用了
slab_state = UP;
/* 删除部分 */
}
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_alloc -> slab_alloc -> slab_alloc_node
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);
// 所有的slab_cache都要加入slab_caches链表中
list_add(&s->list, &slab_caches);
s->refcount = 1;
return s;
}
5.kmalloc和kfree
5.1 kmalloc
相关gfp标志的内容这里就不分析了,只讲内存申请的过程
static __always_inline void *kmalloc(size_t size, gfp_t flags)
{
if (__builtin_constant_p(size)) {
// 如果是内建过的大小,则使用最快的申请方式
// 即直接查表(128/256/512…)
#ifndef CONFIG_SLOB
unsigned int index;
#endif
#define KMALLOC_SHIFT_HIGH (PAGE_SHIFT + 1) = 13
#define KMALLOC_MAX_CACHE_SIZE (1UL << KMALLOC_SHIFT_HIGH)
// 如果申请的size大小大于8k 使用 kmalloc_large
if (size > KMALLOC_MAX_CACHE_SIZE)
return kmalloc_large(size, flags);
#ifndef CONFIG_SLOB
// 根据4.10初始化过的索引表获取idx
index = kmalloc_index(size);
if (!index)
return ZERO_SIZE_PTR;
// 直接在查表获得的索引中对应的cache获取slab obj
// kmem_cache_alloc -> slab_alloc -> slab_alloc_node
// 详见4.6解析
return kmem_cache_alloc_trace(
kmalloc_caches[kmalloc_type(flags)][index],
flags, size);
#endif
}
return __kmalloc(size, flags);
}
5.2 __kmalloc
void *__kmalloc(size_t size, gfp_t flags)
{
struct kmem_cache *s;
void *ret;
if (unlikely(size > KMALLOC_MAX_CACHE_SIZE))
return kmalloc_large(size, flags);
// 和上面的唯一区别就在这里了,详见下面解析
s = kmalloc_slab(size, flags);
if (unlikely(ZERO_OR_NULL_PTR(s)))
return s;
// slab_alloc -> slab_alloc_node 详见4.6解析
ret = slab_alloc(s, flags, _RET_IP_);
trace_kmalloc(_RET_IP_, ret, size, s->size, flags);
ret = kasan_kmalloc(s, ret, size, flags);
return ret;
}
EXPORT_SYMBOL(__kmalloc);
/*
* Find the kmem_cache structure that serves a given size of
* allocation
*/
struct kmem_cache *kmalloc_slab(size_t size, gfp_t flags)
{
unsigned int index;
if (size <= 192) {
// 小于等于192都按4.10的表来
if (!size)
return ZERO_SIZE_PTR;
index = size_index[size_index_elem(size)];
} else {
if (WARN_ON_ONCE(size > KMALLOC_MAX_CACHE_SIZE))
return NULL;
// fls:返回输入参数的最高有效bit位(从低位往左数最后的有效bit位)的序号,
// 该序号与常规0起始序号不同,它是1起始的(当没有有效位时返回0)
// 假设size = 1234-1 = 1233 fls = (0b10011010001) = 11
// 相当于用kmalloc-2048去申请内存
index = fls(size - 1);
}
return kmalloc_caches[kmalloc_type(flags)][index];
}
/**
* fls - find last (most-significant) bit set
* @x: the word to search
*
* This is defined the same way as ffs.
* Note fls(0) = 0, fls(1) = 1, fls(0x80000000) = 32.
*/
static __always_inline int fls(unsigned int x)
{
return x ? sizeof(x) * 8 - __builtin_clz(x) : 0;
}
5.3 kfree
void kfree(const void *x)
{
struct page *page;
void *object = (void *)x;
trace_kfree(_RET_IP_, x);
if (unlikely(ZERO_OR_NULL_PTR(x)))
return;
// 详见下面分析
page = virt_to_head_page(x);
// 通过 PageSlab(page) 检查释放内存块所在物理内存页
// struct page 结构中的 flag 属性是否设置了 PG_slab 标识
// 如果 page 不在 slab cache 的管理体系中,则直接释放回伙伴系统
if (unlikely(!PageSlab(page))) {
unsigned int order = compound_order(page);
BUG_ON(!PageCompound(page));
kfree_hook(object);
mod_lruvec_page_state(page, NR_SLAB_UNRECLAIMABLE_B,
-(PAGE_SIZE << order));
// 直接释放到伙伴系统中
__free_pages(page, order);
return;
}
slab_free(page->slab_cache, page, object, NULL, 1, _RET_IP_);
}
EXPORT_SYMBOL(kfree);
static inline struct page *virt_to_head_page(const void *x)
{
// 将虚拟地址转成页
struct page *page = virt_to_page(x);
// 这里涉及到复合页的概念
return compound_head(page);
}
static inline struct page *compound_head(struct page *page)
{
// 这里读取该页的复合页首地址
unsigned long head = READ_ONCE(page->compound_head);
// 如果该page不是首页,即最低位被置1了
if (unlikely(head & 1))
// 那么-1后就变成复合页首页地址了,然后返回复合页首页地址
return (struct page *) (head - 1);
// 否则直接返回
return page;
}
static inline unsigned int compound_order(struct page *page)
{
if (!PageHead(page))
return 0;
// 通过下面复合页图可知,复合页的第二个页的compound_order
// 存储该复合页的order
return page[1].compound_order;
}
复合页
如果设置了标志位__GFP_COMP并且分配了一个阶数大于0的页块,页分配器会把页块组成复合页(compound page)。复合页最常见的用处是创建巨型页。
复合页的第一页叫首页(head page),其他页都叫尾页(tail page)。一个由n阶页块组成的复合页的结构如图所示
(1)首页设置标志PG_head。
(2)第一个尾页的成员compound_mapcount表示复合页的映射计数,即多少个虚拟页映射到这个物理页,初始值是−1。这个成员和成员mapping组成一个联合体,占用相同的位置,其他尾页把成员mapping设置为一个有毒的地址。
(3)第一个尾页的成员compound_dtor存放复合页释放函数数组的索引,成员compound_order存放复合页的阶数n。这两个成员和成员lru.prev占用相同的位置。
(4)所有尾页的成员compound_head存放首页的地址,并且把最低位设置为1。这个成员和成员lru.next占用相同的位置。
判断一个页是复合页的成员的方法是:页设置了标志位PG_head(针对首页),或者页的成员compound_head的最低位是1(针对尾页)。
复合页的分配及标记:
当__alloc_pages分配标志gfp_flags指定了__GFP_COMP,那么内核必须将这些页组合成复合页compound page。复合页的尺寸要远大于当前分页系统支持的页面大小。并且一定是2^order * PAGE_SIZE大小。复合页主要用在HugeTLB相关的代码。复合页的引入是因为随着计算机物理内存容量不断增大,4G以上几乎成了标配,几十G的内存也很常见,而操作系统仍然使用4KB大小页面的基本单位,显得有些滞后。当采用4KB大小的页面时,想像一下当应用程序分配2MB内存,并进行访问时,共有512个页面,操作系统会经历512次TLB miss和512次缺页中断后,才可以把这2M地址空间全部映射到物理内存上;然而如果使用2MB大小的compand页,那么只需要一次TLB miss和一次缺页中断。当页面分配函数使用GFP_COMP进行页面分配时,分配函数会为每一个增加标志PG_Compound,我们称复合页中的第一个4KB页面为head page,后面的所有page 为tail page。每个page的private保存一个指针,head page的private指向本身,tail page的private指向head page。
Page页中的flag标记用来识别复合页。在复合页中,打头的第一个普通页成为“head page”,用PG_head标记,而后面的所有页被称为“tail pages”,用PG_tail标记。在64位系统中,可以有多余的标记来表示复合页的页头和页尾;但是在32位系统中却没有那么多的标记,因此采用了一种复用其他标记的方案,即将复合页中的所有页都用PG_compound标记,然后,对于尾页同时也使用PG_reclaim标记,这是因为PG_reclaim只有在页缓存中会用到,而复合页根本就不会在页缓存中使用。
5.4 slab_free
由kfree传进来的参数为:head = obj 也就是要释放的地址,tail = NULL,cnt=1
static __always_inline void slab_free(struct kmem_cache *s, struct page *page, void *head, void *tail, int cnt, unsigned long addr)
{
/*
* With KASAN enabled slab_free_freelist_hook modifies the freelist
* to remove objects, whose reuse must be delayed.
*/
if (slab_free_freelist_hook(s, &head, &tail, &cnt))
do_slab_free(s, page, head, tail, cnt, addr);
}
static inline bool slab_free_freelist_hook(struct kmem_cache *s,
void **head, void **tail,
int *cnt)
{
void *object;
void *next = *head;
// 如果*tail = NULL, *old_tail = *head
void *old_tail = *tail ? *tail : *head;
int rsize;
/* Head and tail of the reconstructed freelist */
*head = NULL;
*tail = NULL;
do {
// do的首次obj相当于传入的head
object = next;
// 如果是kfree路径进来的话,next就不知道指向啥东西了
// 也不会有第二次do的机会
next = get_freepointer(s, object);
if (slab_want_init_on_free(s)) {
/*
* Clear the object and the metadata, but don't touch
* the redzone.
*/
// 详见4.3 layout,这里就是清掉obj和fp这些,保留redzone
memset(object, 0, s->object_size);
rsize = (s->flags & SLAB_RED_ZONE) ? s->red_left_pad
: 0;
memset((char *)object + s->inuse, 0,
s->size - s->inuse - rsize);
}
/* If object's reuse doesn't have to be delayed */
if (!slab_free_hook(s, object)) {
/* Move object to the new freelist */
// 在do的首次*head = NULL,也就是该obj的fp=NULL
set_freepointer(s, object, *head);
// head指向该obj的地址
*head = object;
if (!*tail)
// tail不为空也指向obj地址
*tail = object;
} else {
/*
* Adjust the reconstructed freelist depth
* accordingly if object's reuse is delayed.
*/
--(*cnt);
}
// 对于kfree路径 只会循环一次
// 即该次do清空了obj,fp指向NULL,*head保持不变
} while (object != old_tail);
if (*head == *tail)
*tail = NULL;
// kfree路径执行完之后这里应该返回1
return *head != NULL;
}
5.5 do_slab_free
内存释放也分快速路径和慢速路径
/*
* Fastpath with forced inlining to produce a kfree and kmem_cache_free that
* can perform fastpath freeing without additional function calls.
*
* The fastpath is only possible if we are freeing to the current cpu slab
* of this processor. This typically the case if we have just allocated
* the item before.
*
* If fastpath is not possible then fall back to __slab_free where we deal
* with all sorts of special processing.
*
* Bulk free of a freelist with several objects (all pointing to the
* same page) possible by specifying head and tail ptr, plus objects
* count (cnt). Bulk free indicated by tail pointer being set.
*/
static __always_inline void do_slab_free(struct kmem_cache *s,
struct page *page, void *head, void *tail,
int cnt, unsigned long addr)
{
// tail是否空?不空就保持,空则等于head
// kfree路径进来的tail_obj = head = obj(要释放的地址)
void *tail_obj = tail ? : head;
struct kmem_cache_cpu *c;
unsigned long tid;
/* memcg_slab_free_hook() is already called for bulk free. */
if (!tail)
memcg_slab_free_hook(s, &head, 1);
redo:
/*
* Determine the currently cpus per cpu slab.
* The cpu may change afterward. However that does not matter since
* data is retrieved via this pointer. If we are on the same cpu
* during the cmpxchg then the free will succeed.
*/
// 确保当前这个slab是这个cpu上的
// 就算后面cpu变了也无所谓,cmpxchg会保证该cpu_slab能和cpu对应上
do {
tid = this_cpu_read(s->cpu_slab->tid);
c = raw_cpu_ptr(s->cpu_slab);
} while (IS_ENABLED(CONFIG_PREEMPTION) &&
unlikely(tid != READ_ONCE(c->tid)));
/* Same with comment on barrier() in slab_alloc_node() */
barrier();
if (likely(page == c->page)) {
// 如果要释放的obj对应的page就是当前pcp的缓存page
void **freelist = READ_ONCE(c->freelist);
// 给对应obj的fp设置freelist中的值,即之前freelist指向的obj
// 即该obj指向上一个freelist中的obj(单链表元素插入)
set_freepointer(s, tail_obj, freelist);
// 如果对比一致,freelist指向这个obj,形成单链表
if (unlikely(!this_cpu_cmpxchg_double(
s->cpu_slab->freelist, s->cpu_slab->tid,
freelist, tid,
head, next_tid(tid)))) {
// 没对应上就重来
note_cmpxchg_failure("slab_free", s, tid);
goto redo;
}
// 这里成功的话就是快速路径释放成功
stat(s, FREE_FASTPATH);
} else
// 慢速路径
__slab_free(s, page, head, tail_obj, cnt, addr);
}
快速路径下,相当于直接将obj插入freelist单链表中
5.6 __slab_free
Slab释放的慢速路径
/*
* Slow path handling. This may still be called frequently since objects
* have a longer lifetime than the cpu slabs in most processing loads.
*
* So we still attempt to reduce cache line usage. Just take the slab
* lock and free the item. If there is no additional partial page
* handling required then we can return immediately.
*/
static void __slab_free(struct kmem_cache *s, struct page *page,
void *head, void *tail, int cnt,
unsigned long addr)
{
void *prior;
int was_frozen;
struct page new;
unsigned long counters;
struct kmem_cache_node *n = NULL;
unsigned long flags;
stat(s, FREE_SLOWPATH);
if (kmem_cache_debug(s) &&
!free_debug_processing(s, page, head, tail, cnt, addr))
return;
do {
if (unlikely(n)) {
// 正常第一次do进不来
spin_unlock_irqrestore(&n->list_lock, flags);
n = NULL;
}
// 记录当前page的fp和conunters
prior = page->freelist;
counters = page->counters;
// tail obj的fp设置为 page->freelist指向的obj
set_freepointer(s, tail, prior);
new.counters = counters;
// 判断当前冻结状态
was_frozen = new.frozen;
// 在使用的数量 – cnt
new.inuse -= cnt;
// 当前在用数量为0或者page->freelist = NULL(该page没有剩余obj)
// 并且当前页没有处于冻结状态
if ((!new.inuse || !prior) && !was_frozen) {
if (kmem_cache_has_cpu_partial(s) && !prior) {
// 如果没开slabdebug的话,
// 并且没有可指向的剩余obj,设置成冻结
/*
* Slab was on no list before and will be
* partially empty
* We can defer the list move and instead
* freeze it.
*/
new.frozen = 1;
} else { /* Needs to be taken off a list */
// 进入需要加锁的情况
n = get_node(s, page_to_nid(page));
/*
* Speculatively acquire the list_lock.
* If the cmpxchg does not succeed then we may
* drop the list_lock without any processing.
*
* Otherwise the list_lock will synchronize with
* other processors updating the list of slabs.
*/
spin_lock_irqsave(&n->list_lock, flags);
}
}
// 如果对比是当前page的内容
// page->freelist指向被释放的obj, inuse减少
} while (!cmpxchg_double_slab(s, page,
prior, counters,
head, new.counters,
"__slab_free"));
if (likely(!n)) {
// 如果没加锁的情况,有以下两种可能
if (likely(was_frozen)) {
1. 这个page本身就已经处于冻结状态了,那随便释放无所谓了
/*
* The list lock was not taken therefore no list
* activity can be necessary.
*/
stat(s, FREE_FROZEN);
} else if (new.frozen) {
2. 如果是后来置的冻结,就将这个page加入到cpu_slab的partial中
/*
* If we just froze the page then put it onto the
* per cpu partial list.
*/
// 5.7详细分析
put_cpu_partial(s, page, 1);
stat(s, CPU_PARTIAL_FREE);
}
return;
}
// 如果这个page的inuse已经是0了,并且该node的partial数量大于
// min_partial 则可以直接释放掉
if (unlikely(!new.inuse && n->nr_partial >= s->min_partial))
goto slab_empty;
/*
* Objects left in the slab. If it was not on the partial list before
* then add it.
*/
if (!kmem_cache_has_cpu_partial(s) && unlikely(!prior)) {
// 开启slabdebug 并且该page之前已经用满的话
// 从full中删除(因为已经释放一个obj了就不是满的了)
remove_full(s, n, page);
// 加入到partial链表尾,见下面图示
add_partial(n, page, DEACTIVATE_TO_TAIL);
stat(s, FREE_ADD_PARTIAL);
}
spin_unlock_irqrestore(&n->list_lock, flags);
return;
slab_empty:
// 强制释放掉这个slab page
if (prior) {
/*
* Slab on the partial list.
*/
// 如果之前这个页还有可用obj,从partial中直接删掉,不管了
// 见下面图示
remove_partial(n, page);
stat(s, FREE_REMOVE_PARTIAL);
} else {
// 否则就是本身已经用满了的page,也不管了,直接删
/* Slab must be on the full list */
remove_full(s, n, page);
}
// 从上面删掉的slab page最终会被伙伴系统回收掉
spin_unlock_irqrestore(&n->list_lock, flags);
stat(s, FREE_SLAB);
// 最后这里调用伙伴系统,不详细分析了
discard_slab(s, page);
}
5.7 put_cpu_partial
/*
* Put a page that was just frozen (in __slab_free|get_partial_node) into a
* partial page slot if available.
*
* If we did not find a slot then simply move all the partials to the
* per node partial list.
*/
static void put_cpu_partial(struct kmem_cache *s, struct page *page, int drain)
{
#ifdef CONFIG_SLUB_CPU_PARTIAL
struct page *oldpage;
int pages;
int pobjects;
preempt_disable();
do {
pages = 0;
pobjects = 0;
// 获取当前cpu的partial的一个page
// do的第一次是获取cpu之前的page
// do的第二次partial指向刚传入的page
oldpage = this_cpu_read(s->cpu_slab->partial);
if (oldpage) {
// 如果这个page不是空的话
pobjects = oldpage->pobjects;
pages = oldpage->pages;
// 是否释放掉cpu中的partial?
// 如果obj数量大于cpu partial可以保持的大小
// 释放掉这个page
if (drain && pobjects > slub_cpu_partial(s)) {
unsigned long flags;
/*
* partial array is full. Move the existing
* set to the per node partial list.
*/
local_irq_save(flags);
// 下面详细分析
unfreeze_partials(s, this_cpu_ptr(s->cpu_slab));
local_irq_restore(flags);
oldpage = NULL;
pobjects = 0;
pages = 0;
stat(s, CPU_PARTIAL_DRAIN);
}
}
pages++;
// pobj加上当前页剩余可用的obj数量
pobjects += page->objects - page->inuse;
// 剩余的页面数
page->pages = pages;
page->pobjects = pobjects;
// 如果olpage没有被释放完,page->next指向cpu_slab->partial
// 指的obj,实际上这里就是单链表的插入
page->next = oldpage;
// 如果oldpage被释放掉,则cmpxchg返回partail一定不等于olpage
// 则第二次会继续走进来,否则partial指向page
} while (this_cpu_cmpxchg(s->cpu_slab->partial, oldpage, page)
!= oldpage);
// 如果cpu_partial = 0,即cpu不允许保持任何slab
// 那pcp中的slab全部干掉
if (unlikely(!slub_cpu_partial(s))) {
unsigned long flags;
local_irq_save(flags);
unfreeze_partials(s, this_cpu_ptr(s->cpu_slab));
local_irq_restore(flags);
}
preempt_enable();
#endif /* CONFIG_SLUB_CPU_PARTIAL */
}
/*
* Unfreeze all the cpu partial slabs.
*
* This function must be called with interrupts disabled
* for the cpu using c (or some other guarantee must be there
* to guarantee no concurrent accesses).
*/
static void unfreeze_partials(struct kmem_cache *s,
struct kmem_cache_cpu *c)
{
#ifdef CONFIG_SLUB_CPU_PARTIAL
struct kmem_cache_node *n = NULL, *n2 = NULL;
struct page *page, *discard_page = NULL;
// 获取cpu_slab partial中的page
while ((page = slub_percpu_partial(c))) {
struct page new;
struct page old;
#define slub_set_percpu_partial(c, p) \
({ \
slub_percpu_partial(c) = (p)->next; \
})
// partial = page->next 即当前这个page要被干掉了
slub_set_percpu_partial(c, page);
n2 = get_node(s, page_to_nid(page));
if (n != n2) {
if (n)
spin_unlock(&n->list_lock);
n = n2;
spin_lock(&n->list_lock);
}
do {
// 熟悉的操作
old.freelist = page->freelist;
old.counters = page->counters;
// page在这之前必须是冻结状态
VM_BUG_ON(!old.frozen);
new.counters = old.counters;
new.freelist = old.freelist;
// 解冻
new.frozen = 0;
} while (!__cmpxchg_double_slab(s, page,
old.freelist, old.counters,
new.freelist, new.counters,
"unfreezing slab"));
// 这里和__slab_free相同的判断方式
if (unlikely(!new.inuse && n->nr_partial >= s->min_partial)) {
// 这里实际上相当于单链表的反序了一下
// 之前在头的page变成尾,然后next指向NULL
// page->next 指向上一个page,依此类推
// 直到page = NULL && nr_partial < min_partial
page->next = discard_page;
discard_page = page;
} else {
// 剩下的加到node->partial链表尾
add_partial(n, page, DEACTIVATE_TO_TAIL);
stat(s, FREE_ADD_PARTIAL);
}
}
if (n)
spin_unlock(&n->list_lock);
while (discard_page) {
// 说明有要被slab抛弃的page了
// 后面调用伙伴系统将这些页全部释放掉
page = discard_page;
discard_page = discard_page->next;
stat(s, DEACTIVATE_EMPTY);
discard_slab(s, page);
stat(s, FREE_SLAB);
}
#endif /* CONFIG_SLUB_CPU_PARTIAL */
}
SLAB SYSFS
Slub在内核中专门注册了sysfs用于查看slab各个运行参数
具体参数如下所示
1. /sys/kernel/slab//各属性详解
1.1 不开启slab debug的部分属性
aliases:/sys/kernel/slab//aliases 是一个只读文件,它指定了多少个缓存已经合并到了这个缓存中
如下图在/sys/kernel/slab 中ls 发现会有软链的存在,也就是说可能多个相同大小的kmeme_cache共用的一个实际cache只不过起了不同的别名用于不同的用处
其中映射的“:A-0000128”,这种是由slab sysfs在注册时候创建的,中间的数字代表slab的obj大小,字母代表着改slab的特有属性
/* Create a unique string id for a slab cache:
*
* Format :[flags-]size
*/
static char *create_unique_id(struct kmem_cache *s)
{
char *name = kmalloc(ID_STR_LENGTH, GFP_KERNEL);
char *p = name;
BUG_ON(!name);
*p++ = ':';
/*
* First flags affecting slabcache operations. We will only
* get here for aliasable slabs so we do not need to support
* too many flags. The flags here must cover all flags that
* are matched during merging to guarantee that the id is
* unique.
*/
if (s->flags & SLAB_CACHE_DMA)
*p++ = 'd';
if (s->flags & SLAB_CACHE_DMA32)
*p++ = 'D';
if (s->flags & SLAB_RECLAIM_ACCOUNT)
*p++ = 'a';
if (s->flags & SLAB_CONSISTENCY_CHECKS)
*p++ = 'F';
if (s->flags & SLAB_ACCOUNT)
*p++ = 'A';
if (p != name + 1)
*p++ = '-';
p += sprintf(p, "%07u", s->size);
BUG_ON(p > name + ID_STR_LENGTH - 1);
return name;
}
align:对齐,例如kmalloc-128 如果不开启slab debug,对齐大小就是128
cpu_partial:pcp中可以保持的obj的数量,实际上就是kmem_cache->cpu_partial的值。这个值通过下面的函数进行设置
static void set_cpu_partial(struct kmem_cache *s)
{
#ifdef CONFIG_SLUB_CPU_PARTIAL
/*
* cpu_partial determined the maximum number of objects kept in the
* per cpu partial lists of a processor.
*
* Per cpu partial lists mainly contain slabs that just have one
* object freed. If they are used for allocation then they can be
* filled up again with minimal effort. The slab will never hit the
* per node partial lists and therefore no locking will be required.
*
* This setting also determines
*
* A) The number of objects from per cpu partial slabs dumped to the
* per node list when we reach the limit.
* B) The number of objects in cpu partial slabs to extract from the
* per node list when we run out of per cpu objects. We only fetch
* 50% to keep some capacity around for frees.
*/
// 如果开启了SLAB DEBUG,不允许CPU中保留partial
if (!kmem_cache_has_cpu_partial(s))
slub_set_cpu_partial(s, 0);
else if (s->size >= PAGE_SIZE)
slub_set_cpu_partial(s, 2);
else if (s->size >= 1024)
slub_set_cpu_partial(s, 6);
else if (s->size >= 256)
slub_set_cpu_partial(s, 13);
else
slub_set_cpu_partial(s, 30);
#endif
}
cpu_slabs:全部cpu上活跃的slab page数量,一般的cpu_slabs = cpu数量+ slabs_cpu_partial
slabs_cpu_partial:每个cpu上partial的page和可用obj数量
static ssize_t slabs_cpu_partial_show(struct kmem_cache *s, char *buf)
{
int objects = 0;
int pages = 0;
int cpu;
int len;
for_each_online_cpu(cpu) {
struct page *page;
page = slub_percpu_partial(per_cpu_ptr(s->cpu_slab, cpu));
if (page) {
pages += page->pages;
objects += page->pobjects;
}
}
len = sprintf(buf, "%d(%d)", objects, pages);
#ifdef CONFIG_SMP
for_each_online_cpu(cpu) {
struct page *page;
page = slub_percpu_partial(per_cpu_ptr(s->cpu_slab, cpu));
// C(CPU num) 可用obj数量 partial slab page数量
if (page && len < PAGE_SIZE - 20)
len += sprintf(buf + len, " C%d=%d(%d)", cpu,
page->pobjects, page->pages);
}
#endif
return len + sprintf(buf + len, "\n");
}
SLAB_ATTR_RO(slabs_cpu_partial);
destroy_by_rcu:决定slab是使用rcu释放路径还是正常slab管理的路径。本文档中没有具体分析RCU部分。当slab申请时带有 SLAB_TYPESAFE_BY_RCU ,则最后释放时采用RCU
min_partial:实际对应着kmem_cache-> min_partial,关键数据结构和4.2已经详细分析过了
object_size:就是这个slab的每一个obj的大小
total_objects:这个kmem_cache管理的全部node中对应的slab的obj的个数
partial:就是kmem_cache_node[n]-> nr_partial 的和,即该cache全部的partial数量
objects_partial:partial链表中所有page的inuse(在用obj)的和
objs_per_slab:每个slab page中有多少个obj,比如kmalloc-128 一个page有32个
objects = total_objects – (partial * objs_per_slab - objects_partial) 即该kmem_cache刨除partial中free的obj,全部在用的obj的数量
15878 = 16128 - (46*21 - 716)
static ssize_t partial_show(struct kmem_cache *s, char *buf)
{
return show_slab_objects(s, buf, SO_PARTIAL);
}
SLAB_ATTR_RO(partial);
static ssize_t cpu_slabs_show(struct kmem_cache *s, char *buf)
{
return show_slab_objects(s, buf, SO_CPU);
}
SLAB_ATTR_RO(cpu_slabs);
static ssize_t objects_show(struct kmem_cache *s, char *buf)
{
return show_slab_objects(s, buf, SO_ALL|SO_OBJECTS);
}
SLAB_ATTR_RO(objects);
static ssize_t objects_partial_show(struct kmem_cache *s, char *buf)
{
return show_slab_objects(s, buf, SO_PARTIAL|SO_OBJECTS);
}
SLAB_ATTR_RO(objects_partial);
static ssize_t show_slab_objects(struct kmem_cache *s,
char *buf, unsigned long flags)
{
unsigned long total = 0;
int node;
int x;
unsigned long *nodes;
nodes = kcalloc(nr_node_ids, sizeof(unsigned long), GFP_KERNEL);
if (!nodes)
return -ENOMEM;
if (flags & SO_CPU) {
int cpu;
for_each_possible_cpu(cpu) {
struct kmem_cache_cpu *c = per_cpu_ptr(s->cpu_slab,
cpu);
int node;
struct page *page;
page = READ_ONCE(c->page);
if (!page)
continue;
node = page_to_nid(page);
if (flags & SO_TOTAL)
x = page->objects;
else if (flags & SO_OBJECTS)
x = page->inuse;
else
x = 1;
total += x;
nodes[node] += x;
page = slub_percpu_partial_read_once(c);
if (page) {
node = page_to_nid(page);
if (flags & SO_TOTAL)
WARN_ON_ONCE(1);
else if (flags & SO_OBJECTS)
WARN_ON_ONCE(1);
else
x = page->pages;
total += x;
nodes[node] += x;
}
}
}
/*
* It is impossible to take "mem_hotplug_lock" here with "kernfs_mutex"
* already held which will conflict with an existing lock order:
*
* mem_hotplug_lock->slab_mutex->kernfs_mutex
*
* We don't really need mem_hotplug_lock (to hold off
* slab_mem_going_offline_callback) here because slab's memory hot
* unplug code doesn't destroy the kmem_cache->node[] data.
*/
#ifdef CONFIG_SLUB_DEBUG
if (flags & SO_ALL) {
struct kmem_cache_node *n;
for_each_kmem_cache_node(s, node, n) {
if (flags & SO_TOTAL)
x = atomic_long_read(&n->total_objects);
else if (flags & SO_OBJECTS)
x = atomic_long_read(&n->total_objects) -
count_partial(n, count_free);
else
x = atomic_long_read(&n->nr_slabs);
total += x;
nodes[node] += x;
}
} else
#endif
if (flags & SO_PARTIAL) {
struct kmem_cache_node *n;
for_each_kmem_cache_node(s, node, n) {
if (flags & SO_TOTAL)
x = count_partial(n, count_total);
else if (flags & SO_OBJECTS)
x = count_partial(n, count_inuse);
else
x = n->nr_partial;
total += x;
nodes[node] += x;
}
}
x = sprintf(buf, "%lu", total);
#ifdef CONFIG_NUMA
for (node = 0; node < nr_node_ids; node++)
if (nodes[node])
x += sprintf(buf + x, " N%d=%lu",
node, nodes[node]);
#endif
kfree(nodes);
return x + sprintf(buf + x, "\n");
}
shrink:可执行参数,尽可能回收obj
slab_size:obj包含元数据的大小,就是kmem_cache->size
1.2 开启slab debug
alloc/free_calls:开启slub_debug之后,可以看到哪些函数申请或者释放了slab内存
当slab_debug=fzput时(slub_debug的默认参数是fzpu),会打印出每个obj的申请/释放路径
validate:对slab进行校验
static long validate_slab_cache(struct kmem_cache *s)
{
int node;
unsigned long count = 0;
struct kmem_cache_node *n;
// 刷掉cpu上的slab
flush_all(s);
for_each_kmem_cache_node(s, node, n)
// 进行校验,slab数量判断,红区检查等
count += validate_slab_node(s, n);
return count;
}
static int validate_slab_node(struct kmem_cache *s,
struct kmem_cache_node *n)
{
unsigned long count = 0;
struct page *page;
unsigned long flags;
spin_lock_irqsave(&n->list_lock, flags);
// 判断全部partial中的slab
list_for_each_entry(page, &n->partial, slab_list) {
validate_slab(s, page);
count++;
}
// 数量没对上肯定有点问题
if (count != n->nr_partial)
pr_err("SLUB %s: %ld partial slabs counted but counter=%ld\n",
s->name, count, n->nr_partial);
if (!(s->flags & SLAB_STORE_USER))
goto out;
// 判断full中的slab
list_for_each_entry(page, &n->full, slab_list) {
validate_slab(s, page);
count++;
}
if (count != atomic_long_read(&n->nr_slabs))
pr_err("SLUB: %s %ld slabs counted but counter=%ld\n",
s->name, count, atomic_long_read(&n->nr_slabs));
out:
spin_unlock_irqrestore(&n->list_lock, flags);
return count;
}
static void validate_slab(struct kmem_cache *s, struct page *page)
{
void *p;
void *addr = page_address(page);
unsigned long *map;
slab_lock(page);
// 这里就不具体分析了,后面分析slub_debug在深入
if (!check_slab(s, page) || !on_freelist(s, page, NULL))
goto unlock;
/* Now we know that a valid freelist exists */
// 根据freelist设置一个位图,如果是free的就设置成1
map = get_map(s, page);
for_each_object(p, s, addr, page->objects) {
// 这里根据位图判断这个obj是使用的还是没使用
// 如果是使用了,红区填充应该是SLUB_RED_ACTIVE = 0xcc
// 否则就是 SLUB_RED_INACTIVE = 0xbb
u8 val = test_bit(__obj_to_index(s, addr, p), map) ?
SLUB_RED_INACTIVE : SLUB_RED_ACTIVE;
// obj红区 + 内存下毒 + fp检查
if (!check_object(s, page, p, val))
break;
}
put_map(map);
unlock:
slab_unlock(page);
}
2. /proc/slabinfo
打印出来的数据如下
Name 活跃的obj个数 总obj个数 包含元数据的obj大小 一个slab中有几个obj,一个slab中有几个page。后面slabdata则是:有几个活跃的slab和slab的总量
如下图,以 kamlloc为例(以下为开启了slub_debug之后的数据)
活跃:9747 总计:9850 obj包含元数据的大小:640
一个slab中有25个obj 该slab由4个page组成
Slab的总量 * 一个slab中有几个obj = 总obj个数
参考文档
- https://www.jianshu.com/p/95d68389fbd1
- https://zhuanlan.zhihu.com/p/358891862
- https://www.cnblogs.com/LoyenWang/p/11922887.html
- https://zhuanlan.zhihu.com/p/150541284
- http://www.wowotech.net/memory_management/427.html
- https://zhuanlan.zhihu.com/p/573338379