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 チップセット) であることを意味しています。
その比較的新しめの 2.6 カーネルでは、デバイスの存在を認識したり、 デバイスがなくなったりしたときに、netlink を介して、 ユーザにその旨を通知してくれるようになりました。
ユーザ空間で 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() を呼び出す箇所が膨大で、まとめきれませんでした