Linux之slub分配器分析

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分配器有以下三个基本目标:

  1. 减少伙伴算法在分配小块连续内存时所产生的内部碎片
  2. 将频繁使用的对象缓存起来,减少分配、初始化和释放对象的时间开销
  3. 通过着色技术调整对象以更好的使用硬件高速缓存

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个数
在这里插入图片描述

参考文档

  1. https://www.jianshu.com/p/95d68389fbd1
  2. https://zhuanlan.zhihu.com/p/358891862
  3. https://www.cnblogs.com/LoyenWang/p/11922887.html
  4. https://zhuanlan.zhihu.com/p/150541284
  5. http://www.wowotech.net/memory_management/427.html
  6. https://zhuanlan.zhihu.com/p/573338379
  • 25
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夜暝

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值