slub alloc and free
TRANSCRIPT
slub: alloc and freeMasami Ichikawa
@masami256
Intro• 今回はslabのalloc/free/discardの処理を見ていきます
• 前回はslabのデータ構造と作成部分を見ました
• Slub data structure
• http://www.slideshare.net/masamiichikawa/slub-data-structure
• 本スライドは上記資料の続編となってます
• slab cacheの新規作成部分は多少重複してます
Common variable name• slub.cでよく使われれる変数名
• このスライドでも単にcと書いている場合はstruct kmem_cache_cpuの変数を指します
• s
• struct kmem_cacheの変数
• c
• struct kmem_cache_cpuの変数
• n
• struct kmem_cache_nodeの変数
Important variables• s->cpu_slab
• 現在のcpuに紐づくstruct kmem_cache_cpuへのポインタ
• c->page
• slab objectが使用しているstruct pageへのポインタ
• c->freelist
• リストと名前が付いてるが次の空きslabオブジェクトを指す
Interfaces
Interfaces• 外部に公開している主なI/F
• kmem_cache_alloc()
• kmem_cache_alloc_node()
• CONFIG_NUMA=y
• kmem_cache_free()
• Slub内部のI/F
• slab_alloc_node()
• slab_free()
kmem_cache_alloc
• allocate対象のslabを管理するオブジェクト
• GFPフラグ
• kmalloc()で使うのと同じもの
void *kmem_cache_alloc(struct kmem_cache *, gfp_t flags);
slab_alloc_node
• allocate対象のslabを管理するオブジェクト
• GFPフラグ
• NUMAノード
• kmem_cache_alloc()から呼ばれた場合はNUMA_NO_NODEが使われる
• 呼び出し元のアドレス
• デバッグ用
• _RET_IP_マクロ(__builtin_return_address(0))を渡す
static __always_inline void *slab_alloc_node(struct kmem_cache *s, gfp_t gfpflags, int node, unsigned long addr)
kmem_cache_free
• free対象のslabを管理するオブジェクト
• free対象のslabキャッシュ
void kmem_cache_free(struct kmem_cache *, void *);
slab_free
• free対象のslabを管理するオブジェクト
• free対象のobjectを含むpage
• free対象のslabキャッシュ
• 呼び出し元のアドレス
• slab_alloc_node()と同様にデバッグ用
static __always_inline void slab_free(struct kmem_cache *s, struct page *page, void *x, unsigned long addr)
Implementation
Allocation
Allocation flow-> kmem_cache_alloc() -> slab_alloc_node() current cpuにslabキャッシュがある?(c->freelist != NULL)
* あれば、次回alloc時の返却用オブジェクトをキャッシュに載せ、freelistを返す * なければ__slab_alloc()でpartial listを探す -> __slab_alloc() NUMAノードを見ていって使えそうなslabキャッシュがある? * 使えるものがあればそれを返す * 見つからなければnew_slab_objects()で更に検索 * __slab_alloc()以降で返ってきたオブジェクトをfreelistとして設定 -> new_slab() partial listの検索と、必要ならslab cacheの新規作成 * partial listを辿って使えるものがあればそれを返却 * なければnew_slab()で新規作成 -> new_slab() allocate_slab()でpageを確保 * 確保したpageに対してslab objectを設定
slab_alloc_node()• 下記の処理でslabを新規に作るかチェック
• unlikelyの条件に入らない場合(fast path)
• 次回のallocに備える処理を実行し、objectを返す
• unlikelyな条件に該当した場合(slow path)
• 空きslabの探索と必要なら新規作成
• __slab_alloc()を呼び出し
object = c->freelist; page = c->page; if (unlikely(!object || !node_match(page, node))) {
nodeがNUMA_NO_NODEな場合は1が返る。 kmem_cache_alloc()の場合、nodeはNUMA_NO_NODEが使われる。
slab_alloc_node()• fast pathでは次回のallocに向けて先読みを実行
void *next_object = get_freepointer_safe(s, object);
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; } prefetch_freepointer(s, next_object);
s->cpu_slab->freelist == objecdt && s->cpu_slab->tid == tid ならnext_object,next_tid(tid)が新しいfreelist,tidになる
次回のkmem_cache_alloc()で返すobjectをcpuのキャッシュに載せる
次の空きobject取得
__slab_alloc()• 使えるslabキャッシュ or 新規作成し、freelistを設定
• c->pageがNULLならslabキャッシュの新規作成
• node指定がある場合
• pageとpageがあるnodeが一致しない場合は新規作成
• pageとkmem_cache_alloc()で使用したGFPフラグが違う場合も新規作成
• page->freelistがNULL(空きオブジェクト無し)の場合も新規作成
• freelistにslab objectがあればload_freelistラベルの処理へ
__slab_alloc()• freelistの設定
• 今回返却分はすでにあるのでload_freelistで次回分を設定
load_freelist: c->freelist = get_freepointer(s, freelist); c->tid = next_tid(c->tid); local_irq_restore(flags); return freelist;
次回のallocで返すobject、tidを設定
__slab_alloc()• slab cacheの新規作成
new_slab:
if (c->partial) { page = c->page = c->partial; c->partial = page->next; stat(s, CPU_PARTIAL_ALLOC); c->freelist = NULL; goto redo; }
freelist = new_slab_objects(s, gfpflags, node, &c);
if (unlikely(!freelist)) { slab_out_of_memory(s, gfpflags, node); local_irq_restore(flags); return NULL; }
page = c->page; if (likely(!kmem_cache_debug(s) && pfmemalloc_match(page, gfpflags))) goto load_freelist;
新規作成前にcpuに関連するpartial listから使えるキャッシュを探す (NUMAノードとの比較部分から再実行)
使えそうなものがなかったので新規作成
問題なければload_freelistラベルに飛んでfreelistを設定
new_slab_objects()• 最初にget_partial()でpartial listより使えるslabを探す
static void *get_partial(struct kmem_cache *s, gfp_t flags, int node, struct kmem_cache_cpu *c) { void *object; int searchnode = node;
if (node == NUMA_NO_NODE) searchnode = numa_mem_id(); else if (!node_present_pages(node)) searchnode = node_to_mem_node(node);
object = get_partial_node(s, get_node(s, searchnode), c, flags); if (object || node != NUMA_NO_NODE) return object;
return get_any_partial(s, flags, c); }
検索対象のnodeを決めて検索を実行
全NUMAゾーンから使えるslab cacheがあるか探す
new_slab_objects()• 使えるslab cacheがなければnew_slab()で新規作成
if (page) { c = raw_cpu_ptr(s->cpu_slab); if (c->page) flush_slab(s, c);
freelist = page->freelist; page->freelist = NULL;
stat(s, ALLOC_SLAB); c->page = page; *pc = c; }
既存のキャッシュがあったらslabをcから外す
get_partial_node() list_for_each_entry_safe(page, page2, &n->partial, lru) { void *t;
if (!pfmemalloc_match(page, flags)) continue;
t = acquire_slab(s, n, page, object == NULL, &objects); if (!t) break;
available += objects; if (!object) { c->page = page; stat(s, ALLOC_FROM_PARTIAL); object = t; } else { put_cpu_partial(s, page, 0); stat(s, CPU_PARTIAL_NODE); } if (!kmem_cache_has_cpu_partial(s) || available > s->cpu_partial / 2) break; }
nで指定されたnodeのpartial listを辿る
slabのあるpageを既存のpageと入れ替え
acquire_slab() freelist = page->freelist; counters = page->counters; new.counters = counters; *objects = new.objects - new.inuse; if (mode) { new.inuse = page->objects; new.freelist = NULL; } else { new.freelist = freelist; }
VM_BUG_ON(new.frozen); new.frozen = 1;
if (!__cmpxchg_double_slab(s, page, freelist, counters, new.freelist, new.counters, "acquire_slab")) return NULL;
page->freelist = new.freelist page->counters = new.counters
get_any_partial()
• 全てのNUMA nodeに対してget_partial_node()を実行して使えるslabを検索
new_slab()• allocate_slab()でfreelist用のpageを確保
• slab objectを初期化
for_each_object_idx(p, idx, s, start, page->objects) { setup_object(s, page, p); if (likely(idx < page->objects)) set_freepointer(s, p, p + s->size); else set_freepointer(s, p, NULL); }
for_each_object_idxマクロの終了条件はidx <= page->objects
allocate_slab()
• pageの確保
• 実際の確保はalloc_slab_page()が行う
alloc_slab_page()• 実際にpage確保の関数を呼ぶのが個々
• NUMAノードの指定有無で呼ぶ関数が変わる
• node == NUMA_NO_NODE(ノード不問)
• alloc_pages()
• node != NUMA_NO_NODE
• alloc_pages_exact_node()
setup_object()
• setup_object_debug()でデバッグ用のオブジェクト設定
• POISON、RED ZONE、Tracking
• s->ctorが設定されていればコンストラクタ関数の呼び出し
set_freepointer()• object + s->offsetのところに次のobjectのアドレスを設定
static inline void set_freepointer(struct kmem_cache *s, void *object, void *fp) { *(void **)(object + s->offset) = fp; }
for_each_object_idx(p, idx, s, start, page->objects) { setup_object(s, page, p); if (likely(idx < page->objects)) set_freepointer(s, p, p + s->size); else set_freepointer(s, p, NULL); }
get_freepointer()• object + s->offsetに書かれているアドレスにあるobjectを返す
static inline void *get_freepointer(struct kmem_cache *s, void *object) { return *(void **)(object + s->offset); }
Free
Freeing flow-> kmem_cache_free() free対象のslab objectを含むpageを取得 -> slab_free() freeしようとしているobjectがc->pageにある? * yesの場合はset_freepointer()でfreeの処理を行う * noの場合は__slab_free()で処理を行う -> __slab_free()
slab_free() if (likely(page == c->page)) { set_freepointer(s, object, c->freelist);
if (unlikely(!this_cpu_cmpxchg_double( s->cpu_slab->freelist, s->cpu_slab->tid, c->freelist, tid, object, next_tid(tid)))) {
note_cmpxchg_failure("slab_free", s, tid); goto redo; } stat(s, FREE_FASTPATH); } else __slab_free(s, page, x, addr);
次の空きobjectをc->freelistに
今freeしたobjectを次回返すように
slow path
解放するobjectがc->pageにある?
__slab_free()• freeするobjectが指す次のobjectを設定
• freeするobjectが所属するstruct pageを設定
• 設定したpageを適切なリストへ登録・解除
• すべてのobjectが未使用になったらslabそのものを破棄できる
__slab_free()prior = page->freelist; counters = page->counters; set_freepointer(s, object, prior); new.counters = counters; was_frozen = new.frozen; new.inuse--;
do { } whileで下記の条件中繰り返す !cmpxchg_double_slab(s, page, prior, counters, object, new.counters, “__slab_free") ↑ざっくりとした内容としてはpage->freelist, page->countersがprior, countersと同じなら以下をする page->freelist = object page->counters = new.couters
if ((!new.inuse || !prior) && !was_frozen) { if (kmem_cache_has_cpu_partial(s) && !prior) { new.frozen = 1;
} else { /* Needs to be taken off a list */ n = get_node(s, page_to_nid(page)); spin_lock_irqsave(&n->list_lock, flags);
}
上記ループ中のnew.inuse--の後にある処理。 別nodeにあるobjectをfreeするかで後の処理が変わる
__slab_free()if (likely(!n)) { if (new.frozen && !was_frozen) {
put_cpu_partial(s, page, 1); stat(s, CPU_PARTIAL_FREE);
} if (was_frozen) stat(s, FREE_FROZEN);
return; }
これまでの処理でnode(n)が設定されておらず、再設定したpageにはfrozen bitがセットされている場合はカレントcpuのpartial listへpageをつなぐ
frozenが1ということはs->cpu_slabにあったということ。was_frozenが0の場合は、freするobjectがあるpageはアクティブではなかった。
__slab_free()if (unlikely(!new.inuse && n->nr_partial >= s->min_partial)) goto slab_empty;
slab_empty: if (prior) { remove_partial(n, page); } else { remove_full(s, n, page); }
spin_unlock_irqrestore(&n->list_lock, flags); discard_slab(s, page);
freeによって使用中objectがslabからなくなった場合はリストからpageを外してdiscard_slab()でslabを破棄する
__slab_free()if (!kmem_cache_has_cpu_partial(s) && unlikely(!prior)) { if (kmem_cache_debug(s)) remove_full(s, n, page); add_partial(n, page, DEACTIVATE_TO_TAIL); stat(s, FREE_ADD_PARTIAL);
}
空きobject無しの状態からfreeによって空きobjectができた場合はpageをfull listからpartial listへ移動
Misc
flush_slab()• deactivate_slab()でcpuからslabを外すのとcのメンバ変数初期化
• c->tid
• next_tid()で次のtidをセット
• c->page
• NULL
• c->freelist
• NULL
deactivate_slab()• s->cpu_slabからslabを外す
• slab_alloc()やnew_slab_objects()で使用
• 処理は2段階
• slabの状態により最終的に下記の処理が行われる
• partial listへ追加 or 削除
• full listへの追加 or 削除
• slabの破棄(discard_slab())
CONFIG_SLUB_DEBUG=y
deactivate_slab while (freelist && (nextfree = get_freepointer(s, freelist))) { void *prior; unsigned long counters;
do { prior = page->freelist; counters = page->counters; set_freepointer(s, freelist, prior); new.counters = counters; new.inuse--; VM_BUG_ON(!new.frozen);
} while (!__cmpxchg_double_slab(s, page, prior, counters, freelist, new.counters, "drain percpu freelist"));
freelist = nextfree; }
page->freelist != prior && page->counters != counters なら page->freelist = freelist page->counters = new.counters
page->freelistに空きobjectを設定していく
newはstruct page
deactivate_slab() old.freelist = page->freelist; old.counters = page->counters; VM_BUG_ON(!old.frozen);
/* Determine target state of the slab */ new.counters = old.counters; if (freelist) { new.inuse--; set_freepointer(s, freelist, old.freelist); new.freelist = freelist; } else new.freelist = old.freelist;
new.freelistが最終的にpage->freelistになる
deactivate_slab()
if (!new.inuse && n->nr_partial >= s->min_partial) m = M_FREE; else if (new.freelist) { m = M_PARTIAL; } else { m = M_FULL; }
slabの使用状況セットし、 M_FREE以外の場合は名前に該当するlistへの追加 or 削除が行われる
if (!__cmpxchg_double_slab(s, page, old.freelist, old.counters, new.freelist, new.counters, "unfreezing slab"))
freelistの入れ替え
deactivate_slab()
if (m == M_FREE) { stat(s, DEACTIVATE_EMPTY); discard_slab(s, page); stat(s, FREE_SLAB);
}
使用中のobjectがなければslabを破棄
discard_slab()• slabを解放するときに用
• 処理方法として、RCUを使うか設定できる
• 実際の処理は__free_slab()で行う
• 紛らわしいけど前者はslabが使用しているpageを解放するときに、後者はslab objectのfree処理で使用
• free_slab()、__free_slab()
• slab_free()、__slab_free()
__free_slab()• check_object()でobjectが壊れたりしていないかチェック
• pageにセットされているflagのクリア
• __free_page()でpageの解放
• memcg_unchar_slab()でmemcgからpageの使用量を減らす
Summary• kmem_cache_alloc()
• c->cpu_slabに空きobjectがない場合、下記の順番で使えるobjectを探す
• 自NUMAノードのpartial list
• 他ノードのpartial list
• alloc_pages()で新規にページ確保
Summary• kmem_cache_free()
• free対象のobjectがc->cpu_slabにあれば、次回のkmem_cache_alloc()でそのobjectを返すようにする
• c->cpu_slabになければobjectがあるリストを探して処理
• freeによって使用中objectがなくなればslabを解放
• 空きobject無し状態からのfreeならfull listからpartial listへ移動
References
• Slab allocators in the Linux Kernel: SLAB, SLOB, SLUB
• http://events.linuxfoundation.org/sites/events/files/slides/slaballocators.pdf