<kernel v5.0>
슬랩 캐시 삭제
다음 그림은 slub 캐시를 삭제하는 과정을 보여준다.
kmem_cache_destroy()
mm/slab_common.c
void kmem_cache_destroy(struct kmem_cache *s) { int err; if (unlikely(!s)) return; flush_memcg_workqueue(s); get_online_cpus(); get_online_mems(); mutex_lock(&slab_mutex); s->refcount--; if (s->refcount) goto out_unlock; err = shutdown_memcg_caches(s); if (!err) err = shutdown_cache(s); if (err) { pr_err("kmem_cache_destroy %s: Slab cache still has objects\n", s->name); dump_stack(); } out_unlock: mutex_unlock(&slab_mutex); put_online_mems(); put_online_cpus(); } EXPORT_SYMBOL(kmem_cache_destroy);
요청한 슬랩 캐시를 삭제한다.
- 코드 라인 8에서 memcg 슬랩 캐시와 관련된 워크큐를 flush 한다.
- 코드 라인 15~17에서 슬랩 캐시의 레퍼런스 카운터를 감소시키고 0이 아니면 함수를 빠져나간다.
- alias 캐시를 생성하고 삭제하는 경우 레퍼런스 카운터만 감소시켜야 한다.
- 레퍼런스 카운터가 0이되는 순간 실제 캐시를 삭제시킬 수 있다.
- 코드 라인19~21에서 모든 memcg 캐시를 루프를 돌며 shutdown 시킨다. 에러 없이 처리된 경우 요청한 캐시에 등록된 모든 슬랩 object들을 해제하고 per cpu와 per 노드 관리 구조도 모두 제거한 후 껍데기만 남은 캐시를 release 리스트에 추가한다.
flush_memcg_workqueue()
mm/slab_common.c
static void flush_memcg_workqueue(struct kmem_cache *s) { mutex_lock(&slab_mutex); s->memcg_params.dying = true; mutex_unlock(&slab_mutex); /* * SLUB deactivates the kmem_caches through call_rcu. Make * sure all registered rcu callbacks have been invoked. */ if (IS_ENABLED(CONFIG_SLUB)) rcu_barrier(); /* * SLAB and SLUB create memcg kmem_caches through workqueue and SLUB * deactivates the memcg kmem_caches through workqueue. Make sure all * previous workitems on workqueue are processed. */ flush_workqueue(memcg_kmem_cache_wq); }
memcg 슬랩 캐시용 memcg_kmem_cache_wq 워크큐를 flush 한다.
shutdown_memcg_caches()
mm/slab_common.c
static int shutdown_memcg_caches(struct kmem_cache *s) { struct memcg_cache_array *arr; struct kmem_cache *c, *c2; LIST_HEAD(busy); int i; BUG_ON(!is_root_cache(s)); /* * First, shutdown active caches, i.e. caches that belong to online * memory cgroups. */ arr = rcu_dereference_protected(s->memcg_params.memcg_caches, lockdep_is_held(&slab_mutex)); for_each_memcg_cache_index(i) { c = arr->entries[i]; if (!c) continue; if (shutdown_cache(c)) /* * The cache still has objects. Move it to a temporary * list so as not to try to destroy it for a second * time while iterating over inactive caches below. */ list_move(&c->memcg_params.children_node, &busy); else /* * The cache is empty and will be destroyed soon. Clear * the pointer to it in the memcg_caches array so that * it will never be accessed even if the root cache * stays alive. */ arr->entries[i] = NULL; } /* * Second, shutdown all caches left from memory cgroups that are now * offline. */ list_for_each_entry_safe(c, c2, &s->memcg_params.children, memcg_params.children_node) shutdown_cache(c); list_splice(&busy, &s->memcg_params.children); /* * A cache being destroyed must be empty. In particular, this means * that all per memcg caches attached to it must be empty too. */ if (!list_empty(&s->memcg_params.children)) return -EBUSY; return 0; }
요청한 슬랩 캐시의 모든 memcg 캐시를 루프를 돌며 shutdown 시킨다. 성공하는 경우 0을 반환하고, 실패하는 경우 -EBUSY를 반환한다.
shutdown_cache()
mm/slab_common.c
static int shutdown_cache(struct kmem_cache *s) { /* free asan quarantined objects */ kasan_cache_shutdown(s); if (__kmem_cache_shutdown(s) != 0) return -EBUSY; memcg_unlink_cache(s); list_del(&s->list); if (s->flags & SLAB_TYPESAFE_BY_RCU) { #ifdef SLAB_SUPPORTS_SYSFS sysfs_slab_unlink(s); #endif list_add_tail(&s->list, &slab_caches_to_rcu_destroy); schedule_work(&slab_caches_to_rcu_destroy_work); } else { #ifdef SLAB_SUPPORTS_SYSFS sysfs_slab_unlink(s); sysfs_slab_release(s); #else slab_kmem_cache_release(s); #endif } return 0; }
요청한 슬랩 캐시 및 관련 sysfs를 삭제한다
- 코드 라인 4에서 kasan 관련 데이터들을 할당 해제한다.
- 코드 라인 6~7에서 요청한 슬랩 캐시를 삭제한다.
- 코드 라인 9에서 슬랩 캐시의 memcg용 링크를 삭제한다.
- 코드 라인 10에서 슬랩 캐시를 전역 슬랩 리스트에서 제거한다.
- 코드 라인 12~25에서 슬랩 캐시의 sysfs 관련 디렉토리를 언링크하고 제거한다. rcu를 사용한 슬랩 캐시는 &slab_caches_to_rcu_destroy 리스트에 슬랩 캐시를 추가한 후 rcu를 통해 slab_caches_to_rcu_destroy_workfn() 함수에서 제거하고, 그렇지 않은 슬랩 캐시는 즉각 제거한다.
- “/sys/kernel/slab/<slab 명>
__kmem_cache_shutdown()
mm/slub.c
/* * Release all resources used by a slab cache. */
int __kmem_cache_shutdown(struct kmem_cache *s) { int node; struct kmem_cache_node *n; flush_all(s); /* Attempt to free all objects */ for_each_kmem_cache_node(s, node, n) { free_partial(s, n); if (n->nr_partial || slabs_node(s, node)) return 1; } sysfs_slab_remove(s); return 0; }
요청한 슬랩 캐시의 모든 슬랩 페이지들을 해제하여 버디 시스템으로 회수시킨다.
- 코드 라인 6에서 요청한 슬랩 캐시의 per-cpu 슬랩 페이지들을 n->partial 리스트로 옮긴다.
- 코드 라인 8~12에서 요청한 슬랩 캐시를 해제하여 모두 버디 시스템으로 회수시킨다. 단 사용 중인 슬랩 object가 존재하는 경우 실패로 1을 반환한다.
- 코드 라인 13에서 요청한 캐시의 sysfs 관련 object들을 워크큐를 통해 제거한다. 워크큐를 통해 동작되는 스케줄 워크 함수는 sysfs_slab_remove_workfn() 함수이다.
- 코드 라인 14에서 슬랩 캐시 제거가 성공한 경우 0을 반환한다.
슬랩 캐시용 sysfs 관련
RCU를 사용한 슬랩 캐시용 sysfs 제거
slab_caches_to_rcu_destroy_workfn()
mm/slab_common.c
static void slab_caches_to_rcu_destroy_workfn(struct work_struct *work) { LIST_HEAD(to_destroy); struct kmem_cache *s, *s2; /* * On destruction, SLAB_TYPESAFE_BY_RCU kmem_caches are put on the * @slab_caches_to_rcu_destroy list. The slab pages are freed * through RCU and and the associated kmem_cache are dereferenced * while freeing the pages, so the kmem_caches should be freed only * after the pending RCU operations are finished. As rcu_barrier() * is a pretty slow operation, we batch all pending destructions * asynchronously. */ mutex_lock(&slab_mutex); list_splice_init(&slab_caches_to_rcu_destroy, &to_destroy); mutex_unlock(&slab_mutex); if (list_empty(&to_destroy)) return; rcu_barrier(); list_for_each_entry_safe(s, s2, &to_destroy, list) { #ifdef SLAB_SUPPORTS_SYSFS sysfs_slab_release(s); #else slab_kmem_cache_release(s); #endif } }
rcu를 사용하는 슬랩 캐시가 삭제될 때 rcu를 통해 이 함수가 호출되어 슬랩 캐시의 sysfs용 kobject를 제거한다.
sysfs_slab_release()
mm/slub.c
void sysfs_slab_release(struct kmem_cache *s) { if (slab_state >= FULL) kobject_put(&s->kobj); }
워크큐를 사용한 슬랩 캐시용 sysfs 제거
sysfs_slab_remove_workfn()
mm/slub.c
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 디렉토리를 제거한다.
memcg_unlink_cache()
mm/slab_common.c
static void memcg_unlink_cache(struct kmem_cache *s) { if (is_root_cache(s)) { list_del(&s->root_caches_node); } else { list_del(&s->memcg_params.children_node); list_del(&s->memcg_params.kmem_caches_node); } }
슬랩 캐시의 memcg용 링크를 삭제한다.
sysfs_slab_unlink()
mm/slub.c
void sysfs_slab_unlink(struct kmem_cache *s) { if (slab_state >= FULL) kobject_del(&s->kobj); }
슬랩 캐시의 sysfs 디렉토리를 제거한다.
슬랩 캐시 노드 순환자(iterator)
for_each_kmem_cache_node()
mm/slab.h
/* * Iterator over all nodes. The body will be executed for each node that has * a kmem_cache_node structure allocated (which is true for all online nodes) */
#define for_each_kmem_cache_node(__s, __node, __n) \ for (__node = 0; __node < nr_node_ids; __node++) \ if ((__n = get_node(__s, __node))) #endif
슬랩 캐시(@__s)의 모든 노드를 순회한다.
- 출력 인자 __node = 0부터 시작하는 <nid>
- 출력 인자 __n = kmem_cache_node 구조체 포인터
get_node()
mm/slab.h
static inline struct kmem_cache_node *get_node(struct kmem_cache *s, int node) { return s->node[node]; }
요청한 슬랩 캐시에서 @node의 kmem_cache_node 정보를 반환한다.
참고
- Slab Memory Allocator -1- (구조) | 문c
- Slab Memory Allocator -2- (캐시 초기화) | 문c
- Slub Memory Allocator -3- (캐시 생성) | 문c
- Slub Memory Allocator -4- (Order 계산) | 문c
- Slub Memory Allocator -5- (Slub 할당) | 문c
- Slub Memory Allocator -6- (Object 할당) | 문c
- Slub Memory Allocator -7- (Object 해제) | 문c
- Slub Memory Allocator -8- (Drain/Flash 캐시) | 문c
- Slub Memory Allocator -9- (캐시 Shrink) | 문c
- Slub Memory Allocator -10- (Slub 해제) | 문c
- Slub Memory Allocator -11- (캐시 삭제) | 문c – 현재 글
- Slub Memory Allocator -12- (Slub 디버깅) | 문c
- Slub Memory Allocator -13- (slabinfo) | 문c