読者です 読者をやめる 読者になる 読者になる

slub: kmem_cache構造体とslab object作成時のめも

linux kernel

slabを作る時とallocするときで似たようなパスを通ってちょっと混乱してしまったのでその辺のメモです。

slabを作るときはkmem_cache_create()が呼ばれて、そこから呼ばれるdo_kmem_cache_create()がメイン処理。

まずslabキャッシュの作り方の復習。slabキャッシュ用の適当な構造体とそれを管理するkmem_cache構造体のオブジェクトを準備。

struct slub_test {
        int n;
        char s[16];
};

static struct kmem_cache *slub_test_cachep;

slub_test_cachepのインスタンスを作る。

     slub_test_cachep = KMEM_CACHE(slub_test, SLAB_PANIC);

キャッシュを取得

    struct slub_test *ptr = kmem_cache_alloc(slub_test_cachep, GFP_KERNEL);

使い方はこんな感じ。

で、本題に戻る。
do_kmem_cache_create()の最初にあるkmem_cache_zalloc()は作ろうとしているslabを管理するkmem_cache構造体のオブジェクトを作成する。

144         err = -ENOMEM;
145         s = kmem_cache_zalloc(kmem_cache, GFP_KERNEL);
146         if (!s)
147                 goto out;

kmem_cache_zalloc()に渡しているkmem_cacheはkmem_cache構造体。これはkmem_cache構造体を管理するためのkmem_cache構造体。ようは、スラブキャッシュを管理するオブジェクトを管理するためにslabを使っている。そして、返ってきたsが先の例だとslub_test_cachepになる。ここまでの時点ではslab objectに関してはまだ何も設定されていない。

次にslabに関するデータを設定していく。sizeとobject_sizeに関しては前回のslub: objectのsize設定 - φ(・・*)ゞ ウーン カーネルとか弄ったりのメモを参照。

149         s->name = name;
150         s->object_size = object_size;
151         s->size = size;
152         s->align = align;
153         s->ctor = ctor;

その後、__kmem_cache_create() => kmem_cache_open() => calculate_sizes()ときてs->object_sizeが設定されるというのも前回見たところ。

kmem_cache_alloc()の実際の処理になるのはslab_alloc_node()

その中で、slab objectの設定をするのはobject(freelist)がNULLの場合。kmem_cache_alloc()を最初に読んだ時とか。

2409         object = c->freelist;
2410         page = c->page;
2411         if (unlikely(!object || !node_match(page, node))) {
2412                 object = __slab_alloc(s, gfpflags, node, addr, c);
2413                 stat(s, ALLOC_SLOWPATH);
2414         } else {

そして__slab_alloc() => new_slab_objects() => new_slab()ときて、object用のpageを取得し、objectを設定していく。

new_slab()を見ていくと、最初はpageの確保。何page確保するかはkmem_cache構造体にあるkmem_cache_order_objects構造体からオーダーを取得している。

1382 static struct page *new_slab(struct kmem_cache *s, gfp_t flags, int node)
1383 {
1384         struct page *page;
1385         void *start;
1386         void *p;
1387         int order;
1388         int idx;
1389 
1390         BUG_ON(flags & GFP_SLAB_BUG_MASK);
1391 
1392         page = allocate_slab(s,
1393                 flags & (GFP_RECLAIM_MASK | GFP_CONSTRAINT_MASK), node);
1394         if (!page)
1395                 goto out;
1396 

pageを確保したらstruct pageからkmem_cacheを参照できるようにしたり等を行う。

1397         order = compound_order(page);
1398         inc_slabs_node(s, page_to_nid(page), page->objects);
1399         page->slab_cache = s;
1400         __SetPageSlab(page);
1401         if (page->pfmemalloc)
1402                 SetPageSlabPfmemalloc(page);
1403 

ここは単にpageの先頭アドレスを取得。

1404         start = page_address(page);
1405 

SLAB_POISONが立っていたらダミーデータの書き込みを行う。

1406         if (unlikely(s->flags & SLAB_POISON))
1407                 memset(start, POISON_INUSE, PAGE_SIZE << order);
1408

ここがslab objectを設定するところ。ここは後で見る。

1409         for_each_object_idx(p, idx, s, start, page->objects) {
1410                 setup_object(s, page, p);
1411                 if (likely(idx < page->objects))
1412                         set_freepointer(s, p, p + s->size);
1413                 else
1414                         set_freepointer(s, p, NULL);
1415         }
1416

最後にstruct pageにあるslub用の構造体の変数に値をセット。

1417         page->freelist = start;
1418         page->inuse = page->objects;
1419         page->frozen = 1;
1420 out:
1421         return page;
1422 }

先ほど飛ばしたobjectの設定部分。

このループは

1409         for_each_object_idx(p, idx, s, start, page->objects) {
1415         }
1416

このように展開される。

286 #define for_each_object_idx(__p, __idx, __s, __addr, __objects) \
287         for (__p = (__addr), __idx = 1; __idx <= __objects;\
288                         __p += (__s)->size, __idx++)

なので、pageの開始アドレスを起点にobject数分のループする感じ。

setup_object()はほぼデバッグ用途。ここでやるのはsetup_object_debug()の呼び出しと、コンストラクタが設定されていたらそれの呼び出し。

1374 static void setup_object(struct kmem_cache *s, struct page *page,
1375                                 void *object)
1376 {
1377         setup_object_debug(s, page, object);
1378         if (unlikely(s->ctor))
1379                 s->ctor(object);
1380 }

setup_object_debug()は最初にフラグをチェックして、デバッグ系の機能を使わないなら何もせず終了。

1005 static void setup_object_debug(struct kmem_cache *s, struct page *page,
1006                                                                 void *object)
1007 {
1008         if (!(s->flags & (SLAB_STORE_USER|SLAB_RED_ZONE|__OBJECT_POISON)))
1009                 return;
1010 
1011         init_object(s, object, SLUB_RED_INACTIVE);
1012         init_tracking(s, object);
1013 }

init_object()はこのような処理。__OBJECT_POISONが経っている場合はobjectのサイズ分の領域をダミーデータで埋める。SLAB_RED_ZONEはobject_sizeを超えた箇所への書き込みを検知するために使用する。なので、データを設定する位置がオブジェクトの後ろになっている。

663 static void init_object(struct kmem_cache *s, void *object, u8 val)
664 {
665         u8 *p = object;
666 
667         if (s->flags & __OBJECT_POISON) {
668                 memset(p, POISON_FREE, s->object_size - 1);
669                 p[s->object_size - 1] = POISON_END;
670         }
671 
672         if (s->flags & SLAB_RED_ZONE)
673                 memset(p + s->object_size, val, s->inuse - s->object_size);
674 }

init_tracking()はこのような処理。SLAB_STORE_USERが立っている場合、オブジェクトのアドレス、cpu、pidなどをデバッグ用に記録する。ここではそれの初期化。

534 static void init_tracking(struct kmem_cache *s, void *object)
535 {
536         if (!(s->flags & SLAB_STORE_USER))
537                 return;
538 
539         set_track(s, object, TRACK_FREE, 0UL);
540         set_track(s, object, TRACK_ALLOC, 0UL);
541 }

そんなわけでsetup_object()はデバッグのための初期化なので実は読み飛ばしてもOKな部分。 元のループに戻って次はここ。set_freepointer()がオブジェクトを設定する部分。

1411                 if (likely(idx < page->objects))
1412                         set_freepointer(s, p, p + s->size);
1413                 else
1414                         set_freepointer(s, p, NULL);

set_freepointer()はこのような処理。

276 static inline void set_freepointer(struct kmem_cache *s, void *object, void *fp)
277 {
278         *(void **)(object + s->offset) = fp;
279 }

ちなみにget_freepointer()は当然のようにset_freepointer()と逆の操作。

254 static inline void *get_freepointer(struct kmem_cache *s, void *object)
255 {
256         return *(void **)(object + s->offset);
257 }

offsetはどのように決めているかというと、slub.cの中ではcalculate_sizes()でのこれと、

2988         if (((flags & (SLAB_DESTROY_BY_RCU | SLAB_POISON)) ||
2989                 s->ctor)) {
2990                 /*
2991                  * Relocate free pointer after the object if it is not
2992                  * permitted to overwrite the first word of the object on
2993                  * kmem_cache_free.
2994                  *
2995                  * This is the case if we do RCU, have a constructor or
2996                  * destructor or are poisoning the objects.
2997                  */
2998                 s->offset = size;
2999                 size += sizeof(void *);
3000         }

kmem_cache_open()のここ。

3072                 if (get_order(s->size) > get_order(s->object_size)) {
3073                         s->flags &= ~DEBUG_METADATA_FLAGS;
3074                         s->offset = 0;
3075                         if (!calculate_sizes(s, -1))
3076                                 goto error;
3077                 }

どうもoffsetが0以外になるのはdebug用の機能を使う場合時の模様。

今日見たnew_slab()はkmem_cache_create()からも呼ばれるし、kmem_cache_alloc()からも呼ばれる。自分がわけわからなくなってたのはこの辺で、kmem_cache_create()のときはkmem_cache構造体をスラブキャッシュから確保、kmem_cache_alloc()はあるobject専用のkmem_cache構造体を使ってキャッシュから取得するというところを忘れてたせい。

【改訂新版】Linuxエンジニア養成読本 [クラウド時代も、システムの基礎と基盤はLinux! ] (Software Design plus)