Linux 内存管理(4) - Slab system

  • 了解slab system.

1.Slab 基本原理

  Linux内核中基于伙伴算法实现的分配器适合大块内存的请求,它所分配的内存区是以页框为基本单位的。对于内核中小块连续内存的请求,比如说几个字节或者几百个字节,如果依然分配一个页框来来满足该请求,那么这很明显就是一种浪费,即产生内部碎片(internal fragmentation)。

  为了解决小块内存的分配,Linux内核基于Solaris 2.4中的slab分配算法实现了自己的slab分配器。除此之外,slab分配器另一个主要功能是作为一个高速缓存,它用来存储内核中那些经常分配并释放的对象。

  slab分配器基本思想是,先利用页面分配器分配出单个或者一组连续的物理页面,然后在此基础上将整块页面分割成多个相等的小内存单元,以满足小内存空间分配的需要。当然,为了有效的管理这些小的内存单元并保证极高的内存使用速度和效率。

  slab分配器三个基本目标:

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

2.结构组织

  SLAB 分配器是基于所谓“面向对象”的思想,当然,这里的“面向对象”跟 C++ 和 Java 等的“面向对象”是不一样的。这里的“面向对象”更加确切的说法是“面向对象类型”,不同的类型使用不同的 SLAB ,一个 SLAB 只分配一种类型。而且,SLAB 为了提高内核中一些十分频繁进行分配释放的“对象”的分配效率, SLAB 的做法是:每次释放掉某个对象之后,不是立即将其返回给伙伴系统(SLAB 分配器是建立在伙伴系统之上的),而是存放在一个叫 array_cache 的结构中,下次要分配的时候就可以直接从这里分配,从而加快了速度。

  概念说明如下:

  • 缓存(cache) : 其实就是一个管理结构头,它控制了每个 SLAB 的布局,具体的结构体是 struct kmem_cache 。

  • SLAB: 从伙伴系统分配的 2^order 个物理页就组成了一个 SLAB ,而后续的操作就是在这个 SLAB 上在进行细分的,具体的结构体是 struct slab 。

  • 对象(object) : 每一个 SLAB 都只针对一个数据类型,这个数据类型就被称为该 SLAB 的“对象”,将该对象进行对齐之后的大小就是该“对象”的大小,依照该大小将上面的 SLAB 进行切分,从而实现了想要的细粒度内存分配。

  • per-CPU 缓存: array_cache ,这个是为了加快分配,预先从 SLAB 中分配部分对象内存以加快速度。具体的结构体是 struct array_cache,包含在struct kmem_cache中。

  • 每个“对象”的缓存被组织成一个链表——cache_chain,然后每个缓存的 SLAB 被组织了三个不同的链表——slab_full,slab_partial 和 slab_free。

在这里插入图片描述
2.1.struct kmem_cache

struct kmem_cache {
    /* 指向包含空闲对象的本地高速缓存,每个CPU有一个该结构,当有对象释放时,优先放入本地CPU高速缓存中 */
    struct array_cache __percpu *cpu_cache;

/* 1) Cache tunables. Protected by slab_mutex */
    /* 要转移进本地高速缓存或从本地高速缓存中转移出去的对象的数量 */
    unsigned int batchcount;
    /* 本地高速缓存中空闲对象的最大数目 */
    unsigned int limit;
    /* 是否存在CPU共享高速缓存,CPU共享高速缓存指针保存在kmem_cache_node结构中 */
    unsigned int shared;

    /* 对象长度 + 填充字节 */
    unsigned int size;
    /* size的倒数,加快计算 */
    struct reciprocal_value reciprocal_buffer_size;
    
/* 2) touched by every alloc & free from the backend */
    /* 高速缓存永久属性的标识,如果SLAB描述符放在外部(不放在SLAB中),则CFLAGS_OFF_SLAB置1 */
    unsigned int flags;        /* constant flags */
    /* 每个SLAB中对象的个数(在同一个高速缓存中slab中对象个数相同) */
    unsigned int num;        /* # of objs per slab */
/* 3) cache_grow/shrink */
    /* 一个单独SLAB中包含的连续页框数目的对数 */
    unsigned int gfporder;
    /* 分配页框时传递给伙伴系统的一组标识 */
    gfp_t allocflags;
    /* SLAB使用的颜色个数 */
    size_t colour;            
    /* SLAB中基本对齐偏移,当新SLAB着色时,偏移量的值需要乘上这个基本对齐偏移量,理解就是1个偏移量等于多少个B大小的值 */
    unsigned int colour_off;    
    /* 空闲对象链表放在外部时使用,其指向的SLAB高速缓存来存储空闲对象链表 */
    struct kmem_cache *freelist_cache;
    /* 空闲对象链表的大小 */
    unsigned int freelist_size;
    /* 构造函数,一般用于初始化这个SLAB高速缓存中的对象 */
    void (*ctor)(void *obj);
/* 4) cache creation/removal */
    /* 存放高速缓存名字 */
    const char *name;
    /* 高速缓存描述符双向链表指针 */
    struct list_head list;
    int refcount;
    /* 高速缓存中对象的大小 */
    int object_size;
    int align;

/* 5) statistics */
    /* 统计 */
#ifdef CONFIG_DEBUG_SLAB
    unsigned long num_active;
    unsigned long num_allocations;
    unsigned long high_mark;
    unsigned long grown;
    unsigned long reaped;
    unsigned long errors;
    unsigned long max_freeable;
    unsigned long node_allocs;
    unsigned long node_frees;
    unsigned long node_overflow;
    atomic_t allochit;
    atomic_t allocmiss;
    atomic_t freehit;
    atomic_t freemiss;

    /* 对象间的偏移 */
    int obj_offset;
#endif /* CONFIG_DEBUG_SLAB */
#ifdef CONFIG_MEMCG_KMEM
    /* 用于分组资源限制 */
    struct memcg_cache_params *memcg_params;
#endif
    /* 结点链表,此高速缓存可能在不同NUMA的结点都有SLAB链表 */
    struct kmem_cache_node *node[MAX_NUMNODES];
};

2.2.kmem_cache_node

  在kmem_cache结构中,最重要的可能就属struct kmem_cache_node * node[Max_NUMNODES]这个指针数组,指向的struct kmem_cache_node中保存着slab链表,在NUMA架构中每个node对应数组中的一个元素,因为每个SLAB高速缓存都有可能在不同结点维护有自己的SLAB用于这个结点的分配。

/* SLAB链表结构 */
struct kmem_cache_node {
    /* 锁 */
    spinlock_t list_lock;

/* SLAB用 */
#ifdef CONFIG_SLAB
    /* 只使用了部分对象的SLAB描述符的双向循环链表 */
    struct list_head slabs_partial;    /* partial list first, better asm code */
    /* 不包含空闲对象的SLAB描述符的双向循环链表 */
    struct list_head slabs_full;
    /* 只包含空闲对象的SLAB描述符的双向循环链表 */
    struct list_head slabs_free;
    /* 高速缓存中空闲对象个数(包括slabs_partial链表中和slabs_free链表中所有的空闲对象) */
    unsigned long free_objects;
    /* 高速缓存中空闲对象的上限 */
    unsigned int free_limit;
    /* 下一个被分配的SLAB使用的颜色 */
    unsigned int colour_next;    /* Per-node cache coloring */
    /* 指向这个结点上所有CPU共享的一个本地高速缓存 */
    struct array_cache *shared;    /* shared per node */
    struct alien_cache **alien;    /* on other nodes */
    /* 两次缓存收缩时的间隔,降低次数,提高性能 */
    unsigned long next_reap;    
    /* 0:收缩  1:获取一个对象 */
    int free_touched;        /* updated without locking */
#endif

/* SLUB用 */
#ifdef CONFIG_SLUB
    unsigned long nr_partial;
    struct list_head partial;
#ifdef CONFIG_SLUB_DEBUG
    atomic_long_t nr_slabs;
    atomic_long_t total_objects;
    struct list_head full;
#endif
#endif
};

  在这个结构中,最重要的就是slabs_partial、slabs_full、slabs_free这三个链表头。

  • slabs_partial:维护部分对象被使用了的SLAB链表,保存的是SLAB描述符。
  • slabs_full:维护所有对象都被使用了的SLAB链表,保存的是SLAB描述符。
  • slabs_free:维护所有对象都没被使用的SLAB链表,保存的是SLAB描述符。

在这里插入图片描述

2.3.SLAB分配器的实现

2.3.1 SLAB分配器初始化

  mm_init()函数中,在调用mem_init()初始化伙伴管理算法后,紧接着调用的kmem_cache_init()便是slub分配算法的入口。其中该函数在/mm目录下有三处实现slab.c、slob.c和slub.c,表示不同算法下其初始化各异。

 526 static void __init mm_init(void)
 527 {
 528     /*
 529      * page_ext requires contiguous pages,
 530      * bigger than MAX_ORDER unless SPARSEMEM.
 531      */
 532     page_ext_init_flatmem();
 533     mem_init();
 534     kmem_cache_init();
 535     pgtable_init();
 536     debug_objects_mem_init();
 537     vmalloc_init();
 538     ioremap_huge_init();
 539     /* Should be run before the first non-init thread is created */
 540     init_espfix_bsp();
 541     /* Should be run after espfix64 is set up. */
 542     pti_init();                                                                                         
 543     pgd_cache_init();
 544 }

  系统启动时slab分配器初始化的函数为kmem_cache_init()和kmem_cache_init_late()。函数名中的“cache”是指slab分配器,也称作slab缓存,注意,它与CPU中的高速缓存没有关系。

start_kernel
	->mm_init
		->kmem_cache_init(); //默认slab分配器

  kmem_cache_init()函数为分配slab对象准备最基本的环境,它的工作就是初始化用于创建slab缓存的slab缓存。也就是说,一个slab缓存也应该是通过函数kmem_cache_create()来创建的,但是很容易想到,内核中的第一个slab缓存肯定不能通过这个函数来创建,在内核中使用一个编译时生成的静态变量作为第一个slab缓存。

  slab缓存用一个struct kmem_cache结构来描述。内核中的第一个slab缓存定义如下:

static struct kmem_cache cache_cache = {
    .batchcount = 1,
    .limit = BOOT_CPUCACHE_ENTRIES,
    .shared = 1,
    .buffer_size = sizeof(struct kmem_cache),
    .name = "kmem_cache",
};

  系统中所有的slab缓存都被放入一个全局链表中:

staticstruct list_head cache_chain;

分析kmem_cache_init:

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;

	kmem_cache_node = &boot_kmem_cache_node;
	kmem_cache = &boot_kmem_cache;

	create_boot_cache(kmem_cache_node, "kmem_cache_node",
		sizeof(struct kmem_cache_node), SLAB_HWCACHE_ALIGN);

	register_hotmemory_notifier(&slab_memory_callback_nb);

	/* Able to allocate the per node structures */
	slab_state = PARTIAL;

	create_boot_cache(kmem_cache, "kmem_cache",
			offsetof(struct kmem_cache, node) +
				nr_node_ids * sizeof(struct kmem_cache_node *),
		       SLAB_HWCACHE_ALIGN);

	kmem_cache = bootstrap(&boot_kmem_cache);

	/*
	 * Allocate kmem_cache_node properly from the kmem_cache slab.
	 * kmem_cache_node is separately allocated so no need to
	 * update any list pointers.
	 */
	kmem_cache_node = bootstrap(&boot_kmem_cache_node);

	/* Now we can use the kmem_cache to allocate kmalloc slabs */
	create_kmalloc_caches(0);

#ifdef CONFIG_SMP
	register_cpu_notifier(&slab_notifier);
#endif

	printk(KERN_INFO
		"SLUB: HWalign=%d, Order=%d-%d, MinObjects=%d,"
		" CPUs=%d, Nodes=%d\n",
		cache_line_size(),
		slub_min_order, slub_max_order, slub_min_objects,
		nr_cpu_ids, nr_node_ids);
}

  主要涉及的函数分别为create_boot_cache()、bootstrap()和create_kmalloc_caches()。

  • create_boot_cache
      该函数用于创建分配算法缓存,主要是把boot_kmem_cache_node结构初始化了。其内部的calculate_alignment()主要用于计算内存对齐值,而__kmem_cache_create()则是创建缓存的核心函数,其主要是把kmem_cache结构初始化了。
  • bootstrap
    bootstrap()函数主要是将临时kmem_cache向最终kmem_cache迁移,并修正相关指针,使其指向最终的kmem_cache。

2.3.2.API

  • 创建一个slab的API为:
    struct kmem_cache *kmem_cache_create(const char *name, size_t size, size_t align, unsigned long flags, void (*ctor)(void *))

  • 销毁slab的API为:
    void kmem_cache_destroy(struct kmem_cache *s)

  • 从分配算法中分配一个对象:
    void *kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags)

  • 将对象释放到分配算法中:
    void kmem_cache_free(struct kmem_cache *cachep, void *objp)

refer to

  • https://www.jeanleo.com/2018/09/07/%E3%80%90linux%E5%86%85%E5%AD%98%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E3%80%91slub%E5%88%86%E9%85%8D%E7%AE%97%E6%B3%95%EF%BC%882%EF%BC%89/
发布了161 篇原创文章 · 获赞 15 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/weixin_41028621/article/details/103551572