天天看點

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。
Linux 記憶體管理(4) - Slab system

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描述符。
Linux 記憶體管理(4) - Slab system

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/

繼續閱讀