【原创】(十一)Linux内存管理slub分配器

背景

  • Read the fucking source code! --By 鲁迅
  • A picture is worth a thousand words. --By 高尔基

说明:node

  1. Kernel版本:4.14
  2. ARM64处理器,Contex-A53,双核
  3. 使用工具:Source Insight 3.5, Visio

1. 概述

以前的文章分析的都是基于页面的内存分配,而小块内存的分配和管理是经过块分配器来实现的。目前内核中,有三种方式来实现小块内存分配:slab, slub, slob,最早有slab分配器,slub/slob分配器是改进版,slob分配器适用于小内存嵌入式设备,而slub分配器目前已逐渐成为主流块分配器。接下来的文章,就是以slub分配器为目标,进一步深刻。缓存

先来一个初印象:
数据结构

2. 数据结构

有四个关键的数据结构:函数

  • struct kmem_cache:用于管理SLAB缓存,包括该缓存中对象的信息描述,per-CPU/Node管理slab页面等;
    关键字段以下:
/*
 * Slab cache management.
 */
struct kmem_cache {
    struct kmem_cache_cpu __percpu *cpu_slab;       //每一个CPU slab页面
    /* Used for retriving partial slabs etc */
    unsigned long flags;
    unsigned long min_partial;
    int size;       /* The size of an object including meta data */
    int object_size;    /* The size of an object without meta data */
    int offset;     /* Free pointer offset. */
#ifdef CONFIG_SLUB_CPU_PARTIAL
    /* Number of per cpu partial objects to keep around */
    unsigned int cpu_partial;
#endif
    struct kmem_cache_order_objects oo;     //该结构体会描述申请页面的order值,以及object的个数

    /* 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 */
    void (*ctor)(void *);           // 对象构造函数
    int inuse;      /* Offset to metadata */
    int align;      /* Alignment */
    int reserved;       /* Reserved bytes at the end of slabs */
    int red_left_pad;   /* Left redzone padding size */
    const char *name;   /* Name (only for display!) */
    struct list_head list;  /* List of slab caches */       //kmem_cache最终会连接在一个全局链表中
    struct kmem_cache_node *node[MAX_NUMNODES];     //Node管理slab页面
};
  • struct kmem_cache_cpu:用于管理每一个CPU的slab页面,可使用无锁访问,提升缓存对象分配速度;
struct kmem_cache_cpu {
    void **freelist;    /* Pointer to next available object */                  //指向空闲对象的指针
    unsigned long tid;  /* Globally unique transaction id */                
    struct page *page;  /* The slab from which we are allocating */     //slab缓存页面
#ifdef CONFIG_SLUB_CPU_PARTIAL
    struct page *partial;   /* Partially allocated frozen slabs */
#endif
#ifdef CONFIG_SLUB_STATS
    unsigned stat[NR_SLUB_STAT_ITEMS];
#endif
};
  • struct kmem_cache_node:用于管理每一个Node的slab页面,因为每一个Node的访问速度不一致,slab页面由Node来管理;
/*
 * The slab lists for all objects.
 */
struct kmem_cache_node {
    spinlock_t list_lock;

#ifdef CONFIG_SLUB
    unsigned long nr_partial;    //slab页表数量
    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:用于描述slab页面struct page结构体中不少字段都是经过union联合体进行复用的。
    struct page结构中,用于slub的成员以下:
struct page {
    union {
       ...
        void *s_mem;            /* slab first object */
       ...
    };
    
    /* Second double word */
    union {
       ...
        void *freelist;     /* sl[aou]b first free object */
       ...
    };
    
    union {
       ...
        struct {
            union {
              ...
                struct {            /* SLUB */
                    unsigned inuse:16;
                    unsigned objects:15;
                    unsigned frozen:1;
                };
                ...
            };
       ...
        };       
    };   
    
    /*
     * Third double word block
     */
    union {
       ...
        struct {        /* slub per cpu partial pages */
            struct page *next;  /* Next partial slab */
#ifdef CONFIG_64BIT
            int pages;  /* Nr of partial slabs left */
            int pobjects;   /* Approximate # of objects */
#else
            short int pages;
            short int pobjects;
#endif
        };

        struct rcu_head rcu_head;   /* Used by SLAB
                         * when destroying via RCU
                         */
    };
    ...
        struct kmem_cache *slab_cache;  /* SL[AU]B: Pointer to slab */    
    ...
}

图来了:
工具

3. 流程分析

针对Slub的使用,能够从三个维度来分析:atom

  1. slub缓存建立
  2. slub对象分配
  3. slub对象释放

下边将进一步分析。3d

3.1 kmem_cache_create

在内核中经过kmem_cache_create接口来建立一个slab缓存指针

先看一下这个接口的函数调用关系图:
code

  1. kmem_cache_create完成的功能比较简单,就是建立一个用于管理slab缓存kmem_cache结构,并对该结构体进行初始化,最终添加到全局链表中。kmem_cache结构体初始化,包括了上文中分析到的kmem_cache_cpukmem_cache_node两个字段结构。对象

  2. 在建立的过程当中,当发现已有的slab缓存中,有存在对象大小相近,且具备兼容标志的slab缓存,那就只须要进行merge操做并返回,而无需进一步建立新的slab缓存

  3. calculate_sizes函数会根据指定的force_order或根据对象大小去计算kmem_cache结构体中的size/min/oo等值,其中kmem_cache_order_objects结构体,是由页面分配order值和对象数量二者经过位域拼接起来的。

  4. 在建立slab缓存的时候,有一个先鸡后蛋的问题:kmem_cache结构体来管理一个slab缓存,而建立kmem_cache结构体又是从slab缓存中分配出来的对象,那么这个问题是怎么解决的呢?能够看一下kmem_cache_init函数,内核中定义了两个静态的全局变量kmem_cachekmem_cache_node,在kmem_cache_init函数中完成了这两个结构体的初始化以后,至关于就是建立了两个slab缓存,一个用于分配kmem_cache结构体对象的缓存池,一个用于分配kmem_cache_node结构体对象的缓存池。因为kmem_cache_cpu结构体是经过__alloc_percpu来分配的,所以不须要建立一个相关的slab缓存

3.2 kmem_cache_alloc

kmem_cache_alloc接口用于从slab缓存池中分配对象。

看一下大致的调用流程图:

从上图中能够看出,分配slab对象与Buddy System中分配页面相似,存在快速路径和慢速路径两种,所谓的快速路径就是per-CPU缓存,能够无锁访问,于是效率更高。

总体的分配流程大致是这样的:优先从per-CPU缓存中进行分配,若是per-CPU缓存中已经所有分配完毕,则从Node管理的slab页面中迁移slab页per-CPU缓存中,再从新分配。当Node管理的slab页面也不足的状况下,则从Buddy System中分配新的页面,添加到per-CPU缓存中。

仍是用图来讲明更清晰,分为如下几步来分配:

  1. fastpath
    快速路径下,以原子的方式检索per-CPU缓存的freelist列表中的第一个对象,若是freelist为空而且没有要检索的对象,则跳入慢速路径操做,最后再返回到快速路径中重试操做。

  2. slowpath-1
    将per-CPU缓存中page指向的slab页中的空闲对象迁移到freelist中,若是有空闲对象,则freeze该页面,没有空闲对象则跳转到slowpath-2

  3. slowpath-2
    将per-CPU缓存中partial链表中的第一个slab页迁移到page指针中,若是partial链表为空,则跳转到slowpath-3

  4. slowpath-3
    将Node管理的partial链表中的slab页迁移到per-CPU缓存中的page中,并重复第二个slab页将其添加到per-CPU缓存中的partial链表中。若是迁移的slab中空闲对象超过了kmem_cache.cpu_partial的一半,则仅迁移slab页,而且再也不重复。
    若是每一个Node的partial链表都为空,跳转到slowpath-4

  5. slowpath-4
    Buddy System中获取页面,并将其添加到per-CPU的page中。

3.2 kmem_cache_free

kmem_cache_free的操做,能够当作是kmem_cache_alloc的逆过程,所以也分为快速路径和慢速路径两种方式,同时,慢速路径中又分为了好几种状况,能够参考kmem_cache_alloc的过程。

调用流程图以下:

效果以下:

  1. 快速路径释放
    快速路径下,直接将对象返回到freelist中便可。

  2. put_cpu_partial
    put_cpu_partial函数主要是将一个刚freeze的slab页,放入到partial链表中。
    put_cpu_partial函数中调用unfreeze_partials函数,这时候会将per-CPU管理的partial链表中的slab页面添加到Node管理的partial链表的尾部。若是超出了Node的partial链表,溢出的slab页面中没有分配对象的slab页面将会返回到伙伴系统。

  3. add_partial
    添加slab页到Node的partial链表中。

  4. remove_partial
    从Node的partial链表移除slab页。

具体释放的流程走哪一个分支,跟对象的使用状况,partial链表的个数nr_partial/min_partial等相关,细节就再也不深刻分析了。

相关文章
相关标签/搜索