Slub Memory Allocator -11- (캐시 삭제)

<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 정보를 반환한다.

 

참고

댓글 남기기