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

다음 그림은 slub 캐시를 삭제하는 과정을 보여준다.

kmem_cache_destroy-1a

 

kmem_cache_destroy()

mm/slab_common.c

void kmem_cache_destroy(struct kmem_cache *s)
{
        struct kmem_cache *c, *c2;
        LIST_HEAD(release);
        bool need_rcu_barrier = false;
        bool busy = false;

        BUG_ON(!is_root_cache(s));

        get_online_cpus();
        get_online_mems();

        mutex_lock(&slab_mutex);

        s->refcount--;
        if (s->refcount)
                goto out_unlock;

        for_each_memcg_cache_safe(c, c2, s) {
                if (do_kmem_cache_shutdown(c, &release, &need_rcu_barrier))
                        busy = true;
        }

        if (!busy)
                do_kmem_cache_shutdown(s, &release, &need_rcu_barrier);

out_unlock:
        mutex_unlock(&slab_mutex);

        put_online_mems();
        put_online_cpus();

        do_kmem_cache_release(&release, need_rcu_barrier);
}
EXPORT_SYMBOL(kmem_cache_destroy);

지정된 캐시를 삭제한다.

  • s->refcount–; if (s->refcount) goto out_unlock;
    • 레퍼런스 카운터를 감소시키고 0이 아니면 함수를 빠져나간다.
      • alias 캐시를 생성하고 삭제하는 경우 레퍼런스 카운터만 감소시켜야 한다.
      • 레퍼런스 카운터가 0이되는 순간 실제 캐시를 삭제시킬 수 있다.
  • for_each_memcg_cache_safe(c, c2, s) { if (do_kmem_cache_shutdown(c, &release, &need_rcu_barrier)) busy = true; }
    • 모든 memcg 캐시를 루프를 돌며 shutdown 시킨다. 만일 rcu 방식으로 삭제되어야 하는 경우 busy가 설정된다.
  • if (!busy) do_kmem_cache_shutdown(s, &release, &need_rcu_barrier);
    • busy가 아닌 경우 지정된 캐시에 등록된 모든 slub object들을 해제하고 per cpu 캐시와 per 노드 관리 구조도 모두 제거한 후 껍데기만 남은 캐시를 release 리스트에 추가한다.
  • do_kmem_cache_release(&release, need_rcu_barrier);
    • rcu에 의해 삭제되어야 하는 경우 rcu로 release 리스트에 등록된 모든 캐시를 삭제 처리한다. 또한 sysfs로 만들어진 모든 링크를 삭제한다.

 

do_kmem_cache_shutdown()

mm/slab_common.c

static int do_kmem_cache_shutdown(struct kmem_cache *s,
                struct list_head *release, bool *need_rcu_barrier)
{
        if (__kmem_cache_shutdown(s) != 0) {
                printk(KERN_ERR "kmem_cache_destroy %s: "
                       "Slab cache still has objects\n", s->name);
                dump_stack();
                return -EBUSY;
        }

        if (s->flags & SLAB_DESTROY_BY_RCU)
                *need_rcu_barrier = true;

#ifdef CONFIG_MEMCG_KMEM
        if (!is_root_cache(s))
                list_del(&s->memcg_params.list);
#endif
        list_move(&s->list, release);
        return 0;
}

지정된 캐시에 등록된 모든 slub object들을 해제하고 per cpu 캐시와 per 노드 관리 구조도 모두 제거한 후 껍데기만 남은 캐시를 release 리스트에 추가한다.

  • if (__kmem_cache_shutdown(s) != 0) { printk(KERN_ERR “kmem_cache_destroy %s: ” “Slab cache still has objects\n”, s->name); dump_stack(); return -EBUSY; }
    • 지정된 캐시의 per cpu 캐시와 per 노드에 등록되어 있는 slub page들을 모두 해제하는데 실패한 경우 에러 메시지를 출력하고 -EBUSY를 반환한다.
  • if (s->flags & SLAB_DESTROY_BY_RCU) *need_rcu_barrier = true;
    • 캐시가 slub 삭제에 rcu 방식을 사용하는 경우 need_rcu_barrier를 true로 설정한다.
  • if (!is_root_cache(s)) list_del(&s->memcg_params.list);
    • 지정된 캐시가 루트 캐시가 아닌 경우 루트 캐시에서 memcg를 제거한다.
  • list_move(&s->list, release);
    • 지정된 캐시를 인수 release로 받은 리스트로 옮긴다.

 

__kmem_cache_shutdown()

mm/slub.c

int __kmem_cache_shutdown(struct kmem_cache *s)
{
        return kmem_cache_close(s);
}

지정된 캐시의 per cpu 캐시와 per 노드에 등록되어 있는 slub page들을 모두 해제한 후 per cpu 캐시 객체와 per 노드 객체 또한 할당 해지한다.

 

kmem_cache_close()

mm/slub.c

/*
 * Release all resources used by a slab cache.
 */
static inline int kmem_cache_close(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;
        }
        free_percpu(s->cpu_slab);
        free_kmem_cache_nodes(s);
        return 0;
}

지정된 캐시의 per cpu 캐시와 per 노드에 등록되어 있는 slub page들을 모두 해제한 후 per cpu 캐시 객체와 per 노드 객체 또한 할당 해지한다.

  • flush_all(s);
    • per cpu 캐시에 있는 slub page들을 모두 per 노드의 partial 리스트로 옮긴다.
  • for_each_kmem_cache_node(s, node, n) { free_partial(s, n);
    • 지정된 캐시의 모든 per 노드를 대상으로 partial 리스트에 등록되어 있는 모든 slub page들을 해제한다.
  • if (n->nr_partial || slabs_node(s, node)) return 1; }
    • 만일 nr_partial이 0보다 크거나 노드의 n->nr_slabs가 0보다 큰 경우 slub 페이지들이 삭제되지 않아 실패로 1을 반환하면서 함수를 빠져나간다.
  • free_percpu(s->cpu_slab);
    • 지정된 캐시의 per-cpu 타입으로 구성된 전체 per cpu의 할당을 해제한다.
  •  free_kmem_cache_nodes(s);
    • 지정된 캐시의 전체 per 노드 object에 대해 할당을 해제한다.

 

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

모든 노드를 검색하여 slab(slub) __s에서 노드 id __n인 경우

 

get_node()

mm/slab.h

static inline struct kmem_cache_node *get_node(struct kmem_cache *s, int node)
{
        return s->node[node];
}

slab(slub)에서 지정된 노드의 kmem_cache_node 정보를 반환한다.

 

free_kmem_cache_nodes()

mm/slub.c

static void free_kmem_cache_nodes(struct kmem_cache *s)
{
        int node;
        struct kmem_cache_node *n;

        for_each_kmem_cache_node(s, node, n) {
                kmem_cache_free(kmem_cache_node, n);
                s->node[node] = NULL;
        }
}

지정된 캐시의 전체 per 노드 object에 대해 할당을 해제한다.

  • 캐시에 있는 per 노드 객체들은 kmem_cache_node 캐시에서 할당 받은 slub object들로 구성되어 있다.

 

참고

답글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다.