Linux Kernel: cgroup, sysfs, kobject, uevent についての調べ物

cgroup v1 + memory コントローラーで制限を課した際に、sysfs のファイル = kboject が生成/削除されるタイミングやその仕組みを調べていました

例えば下記のような sysfs のファイルです

/sys/kernel/slab/dentry/cgroup/dentry(979:@hogehoge)/objects
/sys/kernel/slab/kmalloc-4k/cgroup/kmalloc-4k(979:@hogehoge)/objects
/sys/kernel/slab/kmalloc-32/cgroup/kmalloc-32(979:@hogehoge)/objects
/sys/kernel/slab/kmalloc-2k/cgroup/kmalloc-2k(979:@hogehoge)/objects
/sys/kernel/slab/kmalloc-1k/cgroup/kmalloc-1k(979:@hogehoge)/objects

ぱっと見、cgroup 内の slab が sysfs のファイルとして扱えることは分かっていたのですが、もうちょい調べました

sysfs と kboject について

sysfs と kobject については下記の wiki が非常に参考になります

カーネルは管理するデバイスドライバのようなオブジェクトを、カテゴリ毎の階層的に管理しています。カテゴリ毎に統合することで同じ処理を共有化でき、階層化することで、親子関係で必要とするオブジェクトを管理することができます。そしてこの構造をユーザ空間にエクスポートしているのが/sysとなります。

/sys下のディレクトリがオブジェクトそのもので、配下のファイルがそのオブジェクトの属性となります。配下にディレクトリがある場合、それはそのオブジェクトの子オブジェクトということです。

この処理を管理するのが、kobjectです

冒頭に記したファイルは slab キャッシュが kboject として作成された sysfs のファイルなのだと理解できました

実験の環境

上記の slab キャッシュが生成されるタイミングと cgroup の関係を実験で追いました

vagrant@bionic:~$ uname -a
Linux bionic 5.4.0-050400rc7-generic #201911102031 SMP Mon Nov 11 01:34:23 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux

実験の内容

まず cgroup v1 で memory コントローラーに新しいディレクトリを作成します

root@bionic:~# mkdir /sys/fs/cgroup/memory/@hogehoge

次にメモリコントローラーに pid を登録して、適当に ls コマンドなんぞを実行します

root@bionic:~# echo $$ > /sys/fs/cgroup/memory/@hogehoge
root@bionic:~# ls >/dev/null

すると /sys/kernel/slab 以下に以下のようなファイルが現れます ( 多過ぎるので grep でフィルターしています)

root@bionic:~# find /sys | grep @hogehoge | grep /objects$
/sys/kernel/slab/:A-0000192/cgroup/cred_jar(979:@hogehoge)/objects
/sys/kernel/slab/kmalloc-96/cgroup/kmalloc-96(979:@hogehoge)/objects
/sys/kernel/slab/:A-0000200/cgroup/vm_area_struct(979:@hogehoge)/objects
/sys/kernel/slab/kmalloc-64/cgroup/kmalloc-64(979:@hogehoge)/objects
/sys/kernel/slab/radix_tree_node/cgroup/radix_tree_node(979:@hogehoge)/objects
/sys/kernel/slab/dentry/cgroup/dentry(979:@hogehoge)/objects
/sys/kernel/slab/:A-0000064/cgroup/anon_vma_chain(979:@hogehoge)/objects
/sys/kernel/slab/anon_vma/cgroup/anon_vma(979:@hogehoge)/objects
/sys/kernel/slab/mm_struct/cgroup/mm_struct(979:@hogehoge)/objects
/sys/kernel/slab/:A-0000704/cgroup/files_cache(979:@hogehoge)/objects
/sys/kernel/slab/kmalloc-4k/cgroup/kmalloc-4k(979:@hogehoge)/objects
/sys/kernel/slab/kmalloc-32/cgroup/kmalloc-32(979:@hogehoge)/objects
/sys/kernel/slab/kmalloc-2k/cgroup/kmalloc-2k(979:@hogehoge)/objects
/sys/kernel/slab/kmalloc-1k/cgroup/kmalloc-1k(979:@hogehoge)/objects
/sys/kernel/slab/:A-0000080/cgroup/task_delay_info(979:@hogehoge)/objects
/sys/kernel/slab/kmalloc-192/cgroup/kmalloc-192(979:@hogehoge)/objects
/sys/kernel/slab/inode_cache/cgroup/inode_cache(979:@hogehoge)/objects
/sys/kernel/slab/:A-0000040/cgroup/pde_opener(979:@hogehoge)/objects
/sys/kernel/slab/task_struct/cgroup/task_struct(979:@hogehoge)/objects
/sys/kernel/slab/sighand_cache/cgroup/sighand_cache(979:@hogehoge)/objects
/sys/kernel/slab/:A-0000256/cgroup/filp(979:@hogehoge)/objects
/sys/kernel/slab/:A-0001088/cgroup/signal_cache(979:@hogehoge)/objects
/sys/kernel/slab/ext4_inode_cache/cgroup/ext4_inode_cache(979:@hogehoge)/objects
/sys/kernel/slab/:A-0000128/cgroup/pid(979:@hogehoge)/objects

「cgroup 作成 -> slab が初めて利用される際に sysfs kobject を作成」として遅延して作成を行っているようです。この点は後述でソースと付き合わせます

udevadm で観測する

カーネルは sysfs kboject を作成する際に uevent を生成しており、ユーザ空間で kobject の作成/削除を知ることができます

udevadm を使うと下記のように cgroup の slab キャッシュ生成の uevent をモニタできます ( udevadm は 同僚の id:buty4649 さんに教えてもらった )

vagrant@bionic:~$ udevadm monitor --kernel
monitor will print the received events for:
KERNEL - the kernel uevent

KERNEL[1337.719596] add      /kernel/slab/inode_cache/cgroup/inode_cache(979:@hogehoge) (cgroup)
KERNEL[1337.719651] add      /kernel/slab/kmalloc-192/cgroup/kmalloc-192(979:@hogehoge) (cgroup)
KERNEL[1337.719662] add      /kernel/slab/kmalloc-1k/cgroup/kmalloc-1k(979:@hogehoge) (cgroup)
KERNEL[1337.719672] add      /kernel/slab/dentry/cgroup/dentry(979:@hogehoge) (cgroup)
KERNEL[1337.719944] add      /kernel/slab/:A-0000256/cgroup/filp(979:@hogehoge) (cgroup)
KERNEL[1337.719982] add      /kernel/slab/task_struct/cgroup/task_struct(979:@hogehoge) (cgroup)
KERNEL[1337.720001] add      /kernel/slab/:A-0000192/cgroup/cred_jar(979:@hogehoge) (cgroup)
KERNEL[1337.720074] add      /kernel/slab/:A-0000080/cgroup/task_delay_info(979:@hogehoge) (cgroup)
KERNEL[1337.720102] add      /kernel/slab/:A-0000704/cgroup/files_cache(979:@hogehoge) (cgroup)
KERNEL[1337.720118] add      /kernel/slab/kmalloc-64/cgroup/kmalloc-64(979:@hogehoge) (cgroup)
KERNEL[1337.720133] add      /kernel/slab/kmalloc-2k/cgroup/kmalloc-2k(979:@hogehoge) (cgroup)
KERNEL[1337.720149] add      /kernel/slab/kmalloc-96/cgroup/kmalloc-96(979:@hogehoge) (cgroup)
KERNEL[1337.720164] add      /kernel/slab/:A-0000064/cgroup/anon_vma_chain(979:@hogehoge) (cgroup)
KERNEL[1337.720181] add      /kernel/slab/sighand_cache/cgroup/sighand_cache(979:@hogehoge) (cgroup)
KERNEL[1337.720197] add      /kernel/slab/:A-0001088/cgroup/signal_cache(979:@hogehoge) (cgroup)
KERNEL[1337.720254] add      /kernel/slab/mm_struct/cgroup/mm_struct(979:@hogehoge) (cgroup)
KERNEL[1337.720272] add      /kernel/slab/:A-0000200/cgroup/vm_area_struct(979:@hogehoge) (cgroup)
KERNEL[1337.720286] add      /kernel/slab/anon_vma/cgroup/anon_vma(979:@hogehoge) (cgroup)
KERNEL[1337.720692] add      /kernel/slab/:A-0000128/cgroup/pid(979:@hogehoge) (cgroup)
KERNEL[1337.724002] add      /kernel/slab/:A-0000040/cgroup/pde_opener(979:@hogehoge) (cgroup)
KERNEL[1337.724048] add      /kernel/slab/kmalloc-32/cgroup/kmalloc-32(979:@hogehoge) (cgroup)
KERNEL[1337.724078] add      /kernel/slab/kmalloc-4k/cgroup/kmalloc-4k(979:@hogehoge) (cgroup)
KERNEL[1375.916726] add      /kernel/slab/ext4_inode_cache/cgroup/ext4_inode_cache(979:@hogehoge) (cgroup)
KERNEL[1375.916940] add      /kernel/slab/radix_tree_node/cgroup/radix_tree_node(979:@hogehoge) (cgroup)

また cgroup を削除する際にも udevadm でモニタできます. cgroup を削除すると ...

vagrant@bionic:~$ sudo rmdir /sys/fs/cgroup/memory/@hogehoge

どわっと出てきます

vagrant@bionic:~$ udevadm monitor --kernel
monitor will print the received events for:
KERNEL - the kernel uevent

KERNEL[1452.369778] remove   /kernel/slab/ext4_inode_cache/cgroup/ext4_inode_cache(979:@hogehoge) (cgroup)
KERNEL[1452.370209] remove   /kernel/slab/:A-0000080/cgroup/task_delay_info(979:@hogehoge) (cgroup)
KERNEL[1452.370376] remove   /kernel/slab/:A-0000040/cgroup/pde_opener(979:@hogehoge) (cgroup)
KERNEL[1452.370409] remove   /kernel/slab/:A-0000256/cgroup/filp(979:@hogehoge) (cgroup)
KERNEL[1452.370425] remove   /kernel/slab/:A-0000200/cgroup/vm_area_struct(979:@hogehoge) (cgroup)
KERNEL[1452.370499] remove   /kernel/slab/mm_struct/cgroup/mm_struct(979:@hogehoge) (cgroup)
KERNEL[1452.370678] remove   /kernel/slab/:A-0000704/cgroup/files_cache(979:@hogehoge) (cgroup)
KERNEL[1452.370709] remove   /kernel/slab/:A-0001088/cgroup/signal_cache(979:@hogehoge) (cgroup)
KERNEL[1452.370728] remove   /kernel/slab/sighand_cache/cgroup/sighand_cache(979:@hogehoge) (cgroup)
KERNEL[1452.370744] remove   /kernel/slab/task_struct/cgroup/task_struct(979:@hogehoge) (cgroup)
KERNEL[1452.370790] remove   /kernel/slab/:A-0000192/cgroup/cred_jar(979:@hogehoge) (cgroup)
KERNEL[1452.370847] remove   /kernel/slab/:A-0000064/cgroup/anon_vma_chain(979:@hogehoge) (cgroup)
KERNEL[1452.371028] remove   /kernel/slab/anon_vma/cgroup/anon_vma(979:@hogehoge) (cgroup)
KERNEL[1452.371125] remove   /kernel/slab/:A-0000128/cgroup/pid(979:@hogehoge) (cgroup)
KERNEL[1452.371142] remove   /kernel/slab/radix_tree_node/cgroup/radix_tree_node(979:@hogehoge) (cgroup)
KERNEL[1452.371544] remove   /kernel/slab/kmalloc-4k/cgroup/kmalloc-4k(979:@hogehoge) (cgroup)
KERNEL[1452.371625] remove   /kernel/slab/kmalloc-2k/cgroup/kmalloc-2k(979:@hogehoge) (cgroup)
KERNEL[1452.371642] remove   /kernel/slab/kmalloc-1k/cgroup/kmalloc-1k(979:@hogehoge) (cgroup)
KERNEL[1452.371659] remove   /kernel/slab/kmalloc-192/cgroup/kmalloc-192(979:@hogehoge) (cgroup)
KERNEL[1452.371674] remove   /kernel/slab/kmalloc-96/cgroup/kmalloc-96(979:@hogehoge) (cgroup)
KERNEL[1452.371690] remove   /kernel/slab/kmalloc-64/cgroup/kmalloc-64(979:@hogehoge) (cgroup)
KERNEL[1452.373237] remove   /kernel/slab/kmalloc-32/cgroup/kmalloc-32(979:@hogehoge) (cgroup)

uevent について

uevent 自体は以下のような目的で作られた仕組みのようです

USB や SDIO のように稼動中の抜き差し(ホットプラグ)に対応したバスの場合、新しいデバイスの挿入検出ごとに UEVENT と呼ばれるカーネルイベントが発生します

UEVENT には検出されたデバイスを示す ID 文字列が付加され、これをデバイスエイリアスと呼んでいます。デバイスエイリアスは "pci:v0000168Cd0000002A" のような文字列で、小文字がカテゴリを、大文字が数値を示しています。この場合は "pci:" が PCIバイスであること、"v0000168C" がベンダー ID=0x168C (Qualcom Atheros) であること、d0000002A がデバイス ID=0x2A (AR9280 チップセット) であることを意味しています。

http://www.silex.jp/blog/wireless/2015/08/linux.html より引用


その比較的新しめの 2.6 カーネルでは、デバイスの存在を認識したり、 デバイスがなくなったりしたときに、netlink を介して、 ユーザにその旨を通知してくれるようになりました。

http://www.usupi.org/sysad/114.html


ユーザ空間で uevent を受信するには netlink + NETLINK_KOBJECT_UEVENT を使うようです. netlink むずかしいよね

Linux Kernelからの情報をPF_NETLINK、NETLINK_KOBJECT_UEVENTのソケットで待ち受けるプログラムを実装したのですが

https://blog.bitmeister.jp/?p=2466

   NETLINK_KOBJECT_UEVENT (since Linux 2.6.10)
         Kernel messages to user space.

http://man7.org/linux/man-pages/man7/netlink.7.html

なるほどな ... slab キャッシュが生成/削除されたタイミングを uevent で受け取って何かするっていうユースケースはあるのかな??? 🤔こうやって 私自身が調査に使っているけど。

ソース

あれこれソースを追ったのを順番に整頓します

sysfs ファイルの生成まで

slab_pre_alloc_hook() で gfp フラグに __GFP_ACCOUNT を付けている、もしくは、SLAB_ACCOUNT フラグを付けて作った kmem_cache の場合に memcg_kmem_get_cache() に繋がります

static inline struct kmem_cache *slab_pre_alloc_hook(struct kmem_cache *s,
                             gfp_t flags)
{
    flags &= gfp_allowed_mask;

    fs_reclaim_acquire(flags);
    fs_reclaim_release(flags);

    might_sleep_if(gfpflags_allow_blocking(flags));

    if (should_failslab(s, flags))
        return NULL;

    if (memcg_kmem_enabled() &&
        ((flags & __GFP_ACCOUNT) || (s->flags & SLAB_ACCOUNT)))
        return memcg_kmem_get_cache(s); 👈

    return s;
}

memcg_kmem_get_cache() は指定した cgroup で kmem_cache が初期化されていなければ、memcg_schedule_kmem_cache_create ( work_struct で非同期に初期化する ) を呼び出します

struct kmem_cache *memcg_kmem_get_cache(struct kmem_cache *cachep)
{

...

    memcg_cachep = READ_ONCE(arr->entries[kmemcg_id]);

...

    if (unlikely(!memcg_cachep))
        memcg_schedule_kmem_cache_create(memcg, cachep); 👈👈
    else if (percpu_ref_tryget(&memcg_cachep->memcg_params.refcnt))
        cachep = memcg_cachep;

非同期でさらに呼び出しが続きます

  • -> memcg_schedule_kmem_cache_create
  • -> memcg_kmem_cache_create_func
  • -> memcg_create_kmem_cache

👈 memcg_create_kmem_cache() ではcgroup の名前を含んだパスを生成している ( kmalloc-32(979:@hogehoge) みたいになる )

void memcg_create_kmem_cache(struct mem_cgroup *memcg,
                 struct kmem_cache *root_cache)
{

...

    cgroup_name(css->cgroup, memcg_name_buf, sizeof(memcg_name_buf));
    cache_name = kasprintf(GFP_KERNEL, "%s(%llu:%s)", root_cache->name, 👈
                   css->serial_nr, memcg_name_buf);
    if (!cache_name)
        goto out_unlock;

さらに進んで

  • -> create_cache
  • -> __kmem_cache_create
  • -> sysfs_slab_add

で、sysfs の kobject を作るコードにたどり着く. ここで wiki.bit-hive.com - sysfsとkobject の話がリンクする

static int sysfs_slab_add(struct kmem_cache *s)
{
    int err;
    const char *name;
    struct kset *kset = cache_kset(s);
    int unmergeable = slab_unmergeable(s);

    INIT_WORK(&s->kobj_remove_work, sysfs_slab_remove_workfn);

    if (!kset) {
        kobject_init(&s->kobj, &slab_ktype);
        return 0;
    }

    if (!unmergeable && disable_higher_order_debug &&
            (slub_debug & DEBUG_METADATA_FLAGS))
        unmergeable = 1;

    if (unmergeable) {
        /*
        * Slabcache can never be merged so we can use the name proper.
        * This is typically the case for debug situations. In that
        * case we can catch duplicate names easily.
        */
        sysfs_remove_link(&slab_kset->kobj, s->name);
        name = s->name;
    } else {
        /*
        * Create a unique name for the slab as a target
        * for the symlinks.
        */
        name = create_unique_id(s);
    }

    s->kobj.kset = kset;
    err = kobject_init_and_add(&s->kobj, &slab_ktype, NULL, "%s", name);
    if (err)
        goto out;

    err = sysfs_create_group(&s->kobj, &slab_attr_group);
    if (err)
        goto out_del_kobj;

#ifdef CONFIG_MEMCG
    if (is_root_cache(s) && memcg_sysfs_enabled) {
        s->memcg_kset = kset_create_and_add("cgroup", NULL, &s->kobj);
        if (!s->memcg_kset) {
            err = -ENOMEM;
            goto out_del_kobj;
        }
    }
#endif

    kobject_uevent(&s->kobj, KOBJ_ADD); 👈
    if (!unmergeable) {
        /* Setup first alias */
        sysfs_slab_alias(s, s->name);
    }
out:
    if (!unmergeable)
        kfree(name);
    return err;
out_del_kobj:
    kobject_del(&s->kobj);
    goto out;
}

👈 kobject_uevent(&s->kobj, KOBJ_ADD); で uevent を発行している

削除

sysfs ファイルの削除まで

長くなって削除までまとめるのがしんどくなったので、あっさりと ...

sysfs の kobject は下記の関数で非同期で削除されます ( kobject_uevent で uevent を発行している )

static void sysfs_slab_remove_workfn(struct work_struct *work)
{
    struct kmem_cache *s =
        container_of(work, struct kmem_cache, kobj_remove_work);

    if (!s->kobj.state_in_sysfs)
        /*
        * For a memcg cache, this may be called during
        * deactivation and again on shutdown.  Remove only once.
        * A cache is never shut down before deactivation is
        * complete, so no need to worry about synchronization.
        */
        goto out;

#ifdef CONFIG_MEMCG
    kset_unregister(s->memcg_kset);
#endif
    kobject_uevent(&s->kobj, KOBJ_REMOVE);
out:
    kobject_put(&s->kobj);
}

sysfs_slab_remove() までは、↓ のようにパスを辿っていく感じです

  • -> kmem_cache_destroy
  • -> shutdown_memcg_caches
  • -> shutdown_cache
  • -> __kmem_cache_shutdown *-> sysfs_slab_remove

kmem_cache_destroy() を呼び出す箇所が膨大で、まとめきれませんでした