🎉

kmem_cache_init解説

2023/07/01に公開

SLABアロケータの準備をする関数であるkmem_cache_initについてのまとめ。
linux kernelのバージョンは最新の6.4です。

スラブアロケータとは

スラブアロケータはbuddy systemとユーザの間にあるメモリ管理システムです。メモリをページ単位で管理してるbuddy systemからページを貰ってきて、ユーザが求める大きさで切り売りして管理する機構です。
ユーザがスラブアロケータからメモリをもらう方法は2通りあります。1つは、kmallocを使う方法とkmem_cache_allocを使う方法です。この2つの違いはメモリを使うデータ構造を指定するかどうかです。ユーザはkmallocにほしいメモリサイズを伝える一方、kmem_cache_allocではメモリを必要とする構造体を指定します(例えばinode,dentry,task_struct等)。システムが頻繁に使う構造体の領域をキャッシュとして確保しておくことで高速なメモリ確保を実現しています。
この時スラブと呼ばれる1ページから2ページほどのメモリ領域の中に(例えばinodeなどの)構造体を確保するための領域を大量に用意しておき、呼ばれた際にその中で空いている領域を渡します。

linuxのシステムでは/proc/slabinfoからスラブの状況を見ることができます。

~$ cat /proc/slabinfo
slabinfo - version: 2.1
# name            <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables <limit> <batchcount> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavail>
...
dentry            270846 270858    192   42    2 : tunables    0    0    0 : slabdata   6449   6449      0
...
kmalloc-16         24263  26624     16  256    1 : tunables    0    0    0 : slabdata    104    104      0
kmalloc-8          15863  15872      8  512    1 : tunables    0    0    0 : slabdata     31     31      0
...

例えば上のdentryでは、1つのスラブは2ページの大きさでその中にdentry42個分の領域があります。

スラブアロケータの実装にはSLAB,SLUB,SLOBなどがあり、現在多くのシステムで標準として用いられているのは高速で高機能なSLUBです。今回解説するSLABは古典的な実装でありシンプルな設計になっています。SLOBは組み込み用の省メモリなものですが近年はあまり使われていません。

データ構造

slabアロケータを理解する上で特に重要な構造体は

  • struct kmem_cache (slab_def.h)
  • struct kmem_cache_node (slab.h)

グローバル変数は、

  • extern struct list_head slab_caches; (slab.h)
  • extern struct kmem_cache *kmem_cache; (slab.h)

初期化に必要な変数として

  • static struct kmem_cache kmem_cache_boot = {...}; (slab.c)
  • static struct kmem_cache_node __initdata init_kmem_cache_node[NUM_INIT_LISTS];

などがあります。


(イメージ図)

メモ:kmem_cache_nodeはkmalloc_nodeから(つまりkmallocから)作られる
kmem_cache_create -> setup_cpu_cache if FULL enable_cpucache -> do_tune_cpucache

struct kmem_cache {
	struct array_cache __percpu *cpu_cache; <---...

/* 4) cache creation/removal */
	const char *name; <---struct list_head list; <---int refcount;
	int object_size;
	int align;

...

	struct kmem_cache_node *node[MAX_NUMNODES]; <---};

①はcpuコアごとに作られるarray_cacheであり、スラブから最近使われたwarmな領域を参照することで高速なkmalloc(),kmem_cache_alloc()を実現するための変数です。これに対して④はスラブを包括的に管理するための変数で、slab_fullやslab_partial等スラブへの直接的な参照を持っています。MAX_NUMNODESはNUMAのノード数上限を指しており、個人利用で一般的なSMP構成では1です。
②はキャッシュの名前です。例えばstruct inodeを確保するためのキャッシュなら"inode"を持ちます。
③はキャッシュの双方向連結リスト用のポインタです。イメージ図にあるように、slab_cachesによって全てのキャッシュはリストに保持されます。
ここでは解説しませんが、これに加えてkmem_cache構造体はstruct array_cache __percpu *cpu_cacheというメンバを持っており、これによりメモリの確保を高速化しています。少し前までのバージョンではNUMA用に別のコードがあって、kmem_cacheとkmem_cache_nodeを分けずにそのまま書かれていたようです。

kmem_cache_init

コードは以下の通り

mm/slab.c
/*
 * Initialisation.  Called after the page allocator have been initialised and
 * before smp_init().
 */
void __init kmem_cache_init(void)
{
	int i;

	kmem_cache = &kmem_cache_boot;

	if (!IS_ENABLED(CONFIG_NUMA) || num_possible_nodes() == 1)
		use_alien_caches = 0;

	for (i = 0; i < NUM_INIT_LISTS; i++)
		kmem_cache_node_init(&init_kmem_cache_node[i]);

	/*
	 * Fragmentation resistance on low memory - only use bigger
	 * page orders on machines with more than 32MB of memory if
	 * not overridden on the command line.
	 */
	if (!slab_max_order_set && totalram_pages() > (32 << 20) >> PAGE_SHIFT)
		slab_max_order = SLAB_MAX_ORDER_HI;

	/* Bootstrap is tricky, because several objects are allocated
	 * from caches that do not exist yet:
	 * 1) initialize the kmem_cache cache: it contains the struct
	 *    kmem_cache structures of all caches, except kmem_cache itself:
	 *    kmem_cache is statically allocated.
	 *    Initially an __init data area is used for the head array and the
	 *    kmem_cache_node structures, it's replaced with a kmalloc allocated
	 *    array at the end of the bootstrap.
	 * 2) Create the first kmalloc cache.
	 *    The struct kmem_cache for the new cache is allocated normally.
	 *    An __init data area is used for the head array.
	 * 3) Create the remaining kmalloc caches, with minimally sized
	 *    head arrays.
	 * 4) Replace the __init data head arrays for kmem_cache and the first
	 *    kmalloc cache with kmalloc allocated arrays.
	 * 5) Replace the __init data for kmem_cache_node for kmem_cache and
	 *    the other cache's with kmalloc allocated memory.
	 * 6) Resize the head arrays of the kmalloc caches to their final sizes.
	 */

	/* 1) create the kmem_cache */

	/*
	 * struct kmem_cache size depends on nr_node_ids & nr_cpu_ids
	 */
	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);
	list_add(&kmem_cache->list, &slab_caches);
	slab_state = PARTIAL;

	/*
	 * Initialize the caches that provide memory for the  kmem_cache_node
	 * structures first.  Without this, further allocations will bug.
	 */
	new_kmalloc_cache(INDEX_NODE, KMALLOC_NORMAL, ARCH_KMALLOC_FLAGS);
	slab_state = PARTIAL_NODE;
	setup_kmalloc_cache_index_table();

	/* 5) Replace the bootstrap kmem_cache_node */
	{
		int nid;

		for_each_online_node(nid) {
			init_list(kmem_cache, &init_kmem_cache_node[CACHE_CACHE + nid], nid);

			init_list(kmalloc_caches[KMALLOC_NORMAL][INDEX_NODE],
					  &init_kmem_cache_node[SIZE_NODE + nid], nid);
		}
	}

	create_kmalloc_caches(ARCH_KMALLOC_FLAGS);
}

SLABアロケータを有効化するためまずkmem_cache構造体を確保するためのkmem_cacheを静的に作成し、kmalloc用のキャッシュを作成する必要があります。

  • static struct kmem_cache kmem_cache_boot = {...}; (slab.c)
  • static struct kmem_cache_node __initdata init_kmem_cache_node[NUM_INIT_LISTS];
    初期化用に上記の変数を用います。

流れは以下の通り。

  1. kmem_cacheをkmem_cache_bootの通りに設定し、create_boot_cache()によって作成する。この時内部のset_up_node()によりnodeにはinit_kmem_cache_nodeが代入される。

  2. kmem_cache_nodeを確保できるようにするためkmalloc用のキャッシュを一部作成する。これはkmem_cache_nodeがkmalloc_node()によって、つまりkmalloc()によって確保されるため。

  3. 静的に確保していたkmem_cache_nodeをkmalloc()で確保したkmem_cache_nodeの領域に移す。

  4. kmallocのキャッシュを全て作成する。

Discussion