slub: struct kmem_cache_nodeの使われ方

SLUBの場合のstruct kmem_cacheでstruct kmem_cache_node *node[MAX_NUMNODES];がどのように使われているのかというメモ。 kernelのバージョンは3.18。

struct kmem_cache_nodeはSLAB・SLUBの場合分けがifdefでされていて、debugオプション無しのSLUBの場合はこのようになる。

317 struct kmem_cache_node {
318         spinlock_t list_lock;
333 #ifdef CONFIG_SLUB
334         unsigned long nr_partial;
335         struct list_head partial;
341 #endif
342 
343 };

基本的にpartialなデータをリストで繋ぐための構造と言った感じ。

この構造体を操作するのは__add_partial()__remove_partial()の2関数。add_partial()はこのような処理でremove_partial()はこの逆の操作。見ての通り、リストに繋がるのはstruct pageです。これらの関数は関数名にアンダーバーが付かない版の関数から呼ばれます。

1492 static inline void
1493 __add_partial(struct kmem_cache_node *n, struct page *page, int tail)
1494 {
1495         n->nr_partial++;
1496         if (tail == DEACTIVATE_TO_TAIL)
1497                 list_add_tail(&page->lru, &n->partial);
1498         else
1499                 list_add(&page->lru, &n->partial);
1500 }

では、これらの関数はどんな時に呼ばれるのか? 主なケースとしてはkmem_cache_free()でslabオブジェクトを解放するときにadd_partial()が呼ばれ、kmem_cache_alloc()でslabオブジェクトを取得する場合にremove_partial()が呼ばれる。

というわけで、まずは__add_partial()が呼ばれる場合から。kmem_cache_free()の実質的な処理はslab_free()が行います。
この関数の中で以下の部分ですが、pageは解放しようとしているslabオブジェクトが存在するページです。これがc->page(cはstruct kmem_cache_cpu の変数で今動いているcpuと関連付いているのでc->pageは今のcpuで設定されたslabオブジェクトのページ)と同じなら新しくslabオブジェクトを作る時と同じようにset_freepointer()で空きオブジェクトとして設定し、そうでな場合に__slab_free()が呼ばれる。add_partial()が呼ばれるのはslab_free()が呼ばれる場合。

2661         if (likely(page == c->page)) {
2662                 set_freepointer(s, object, c->freelist);
〜略〜
2671                 }
2672                 stat(s, FREE_FASTPATH);
2673         } else
2674                 __slab_free(s, page, x, addr);

__slab_free()の処理もadd_partial()のところだけ見ると2箇所あって、ひとつはput_cpu_partial()からの場合。

2576         if (likely(!n)) {
2577 
2578                 /*
2579                  * If we just froze the page then put it onto the
2580                  * per cpu partial list.
2581                  */
2582                 if (new.frozen && !was_frozen) {
2583                         put_cpu_partial(s, page, 1);
2584                         stat(s, CPU_PARTIAL_FREE);
2585                 }

もう一箇所はここ。上記のところはちょっと面倒なのでこちらだけ見ていくと、kmem_cache_has_cpu_partial()はCONFIG_SLUB_CPU_PARTIALがセットされていなければ常にfalseを返し、priorがNULLなことはまず無いということでif文の中に入り、add_partial()が呼ばれる。

2598         /*
2599          * Objects left in the slab. If it was not on the partial list before
2600          * then add it.
2601          */
2602         if (!kmem_cache_has_cpu_partial(s) && unlikely(!prior)) {
2603                 if (kmem_cache_debug(s))
2604                         remove_full(s, n, page);
2605                 add_partial(n, page, DEACTIVATE_TO_TAIL);
2606                 stat(s, FREE_ADD_PARTIAL);
2607         }

そして、解放しようとしたslabオブジェクトだけを解放するのではなくて、pageそのものがkmem_cache_node構造体のpartialリストに繋げられる。

次に__remove_partial()を使うパターン。 ここではkmem_cache_alloc()時に新しいslabを作る必要があってnew_slab_objects()が呼ばれた場合。

2163 static inline void *new_slab_objects(struct kmem_cache *s, gfp_t flags,
2164                         int node, struct kmem_cache_cpu **pc)
2165 {
2166         void *freelist;
2167         struct kmem_cache_cpu *c = *pc;
2168         struct page *page;
2169 
2170         freelist = get_partial(s, flags, node, c);
2171 

get_partial()は最初にどのNUMAノードから検索するかを決めて、get_partial_node()でオブジェクトの取得を行う。kmem_cache_alloc()からの流れではnodeにはNUMA_NO_NODEが渡ってきて、どのノードでも良いとなっている。

1688 static void *get_partial(struct kmem_cache *s, gfp_t flags, int node,
1689                 struct kmem_cache_cpu *c)
1690 {
1691         void *object;
1692         int searchnode = node;
1693 
1694         if (node == NUMA_NO_NODE)
1695                 searchnode = numa_mem_id();
1696         else if (!node_present_pages(node))
1697                 searchnode = node_to_mem_node(node);
1698 
1699         object = get_partial_node(s, get_node(s, searchnode), c, flags);
1700         if (object || node != NUMA_NO_NODE)
1701                 return object;
1702 
1703         return

get_partial_node()はlist_for_each_entry_safeマクロでリストを手繰りながらacquire_slab()でオブジェクトの取得をする。

1593         list_for_each_entry_safe(page, page2, &n->partial, lru) {
1594                 void *t;
1595 
1596                 if (!pfmemalloc_match(page, flags))
1597                         continue;
1598 
1599                 t = acquire_slab(s, n, page, object == NULL, &objects);
1600                 if (!t)
1601                         break;
1602 
1603                 available += objects;
1604                 if (!object) {
1605                         c->page = page;
1606                         stat(s, ALLOC_FROM_PARTIAL);
1607                         object = t;
1608                 } else {
1609                         put_cpu_partial(s, page, 0);
1610                         stat(s, CPU_PARTIAL_NODE);
1611                 }

acquire_slab()ではどのようなことをするかというと、pageからfreelist等を取得して、そこから新しくstruct pageを作り__cmpxchg_double_slab()でデータの入れ替えをし、最後にpartialのリストからpageを外すということをする。

1529 static inline void *acquire_slab(struct kmem_cache *s,
1530                 struct kmem_cache_node *n, struct page *page,
1531                 int mode, int *objects)
1532 {
1533         void *freelist;
1534         unsigned long counters;
1535         struct page new;
1536 
1537         lockdep_assert_held(&n->list_lock);
1538 
1539         /*
1540          * Zap the freelist and set the frozen bit.
1541          * The old freelist is the list of objects for the
1542          * per cpu allocation list.
1543          */
1544         freelist = page->freelist;
1545         counters = page->counters;
1546         new.counters = counters;
1547         *objects = new.objects - new.inuse;
1548         if (mode) {
1549                 new.inuse = page->objects;
1550                 new.freelist = NULL;
1551         } else {
1552                 new.freelist = freelist;
1553         }
1554 
1555         VM_BUG_ON(new.frozen);
1556         new.frozen = 1;
1557 
1558         if (!__cmpxchg_double_slab(s, page,
1559                         freelist, counters,
1560                         new.freelist, new.counters,
1561                         "acquire_slab"))
1562                 return NULL;
1563 
1564         remove_partial(n, page);
1565         WARN_ON(!freelist);
1566         return freelist;
1567 }

そして、戻った先でstruct kmem_cache_cpuのc->pageに戻り値として返したfreelistをセットする。

ここまでで見てきたようにstruct kmem_cache_nodeが使われるのはslabオブジェクト(page)がstruct kmem_cache_cpuのpageでなくなる場合にadd_partial()によってリストに繋がれ、c->pageからも外れる。逆に、partialリストにつながっていたページをc->pageにセットする場合はremove_partial()でpartialリストから外す。

こう見るとリストの使いかたがSLABとは大分変わったんだなーというのがわかりますね。