Slub Memory Allocator -8- (캐시 Shrink)

 

kmem_cache_shrink()

mm/slab_common.c

/**
 * kmem_cache_shrink - Shrink a cache.
 * @cachep: The cache to shrink.
 *
 * Releases as many slabs as possible for a cache.
 * To help debugging, a zero exit status indicates all slabs were released.
 */
int kmem_cache_shrink(struct kmem_cache *cachep)
{
        int ret;

        get_online_cpus();
        get_online_mems();
        ret = __kmem_cache_shrink(cachep, false);
        put_online_mems();
        put_online_cpus();
        return ret;
}
EXPORT_SYMBOL(kmem_cache_shrink);

캐시에서 해지시킬 수 있는 slub을 모두 찾아 해지시킨다.

  • ?get_online_cpus()
    • CONFIG_HOTPLUG_CPU 커널 옵션이 설정된 경우?cpu_hotplug.refcount++
  • get_online_mems();
    • CONFIG_MEMORY_HOTPLUG 커널 옵션이 설정된 경우 mem_hotplug.refcount++
  • ret = __kmem_cache_shrink(cachep, false);
    • per cpu 캐시에 있는 slub page들을 per 노드로 옮기고 정리한다. (deactivate는 false)
  • put_online_mems()
    • CONFIG_MEMORY_HOTPLUG 커널 옵션이 설정된 경우 mem_hotplug.refcount–
  • put_online_cpus()
    • CONFIG_HOTPLUG_CPU 커널 옵션이 설정된 경우?cpu_hotplug.refcount–

 

__kmem_cache_shrink()

mm/slub.c

/*
 * kmem_cache_shrink discards empty slabs and promotes the slabs filled
 * up most to the head of the partial lists. New allocations will then
 * fill those up and thus they can be removed from the partial lists.
 *
 * The slabs with the least items are placed last. This results in them
 * being allocated from last increasing the chance that the last objects
 * are freed in them.
 */
int __kmem_cache_shrink(struct kmem_cache *s, bool deactivate)
{
        int node;
        int i;
        struct kmem_cache_node *n;
        struct page *page;
        struct page *t;
        struct list_head discard;
        struct list_head promote[SHRINK_PROMOTE_MAX];
        unsigned long flags;
        int ret = 0;

        if (deactivate) {
                /*
                 * Disable empty slabs caching. Used to avoid pinning offline
                 * memory cgroups by kmem pages that can be freed.
                 */
                s->cpu_partial = 0;
                s->min_partial = 0;

                /*
                 * s->cpu_partial is checked locklessly (see put_cpu_partial),
                 * so we have to make sure the change is visible.
                 */
                kick_all_cpus_sync();
        }

지정된 캐시의 현재 per cpu 캐시의 모든 slub page를 per 노드로 옮기고 아래와 같이 정리한다.

  • per 노드의 slub page에 할당되어 사용중인 object가 없는 경우 slub page를 해제하여 버디 시스템으로 되돌린다.
  • per 노드의 slub page에 있는 32개 미만의 free object들은 asscending 정렬한다.

 

  • if (deactivate) {?s->cpu_partial = 0;?s->min_partial = 0;?kick_all_cpus_sync(); }
    • 인수 deactivate가 true 설정된 경우 s->cpu_partial에 0을 대입하여 앞으로 per cpu 캐시의 partial 리스트에 추가되지 않도록 제한시키고,s->min_partial에 0을 대입하여 앞으로 per 노드의 partial 리스트에 추가되지 않도록 제한시킨다.
    • 각 cpu에서 s->cpu_partial 변수가 업데이트되게 하기 위해 IPI call을 사용하여 각 cpu에서 아무것도 없는 함수를 실행시켜 잠시 기다리게 하는 방법으로 동기화를 한다.

 

       ?flush_all(s);

        for_each_kmem_cache_node(s, node, n) {
                INIT_LIST_HEAD(&discard);
                for (i = 0; i < SHRINK_PROMOTE_MAX; i++)
                        INIT_LIST_HEAD(promote + i);

                spin_lock_irqsave(&n->list_lock, flags);

                /*
                 * Build lists of slabs to discard or promote.
                 *
                 * Note that concurrent frees may occur while we hold the
                 * list_lock. page->inuse here is the upper limit.
                 */
                list_for_each_entry_safe(page, t, &n->partial, lru) {
                        int free = page->objects - page->inuse;

                        /* Do not reread page->inuse */
                        barrier();

                        /* We do not keep full slabs on the list */
                        BUG_ON(free <= 0);

                        if (free == page->objects) {
                                list_move(&page->lru, &discard);
                                n->nr_partial--;
                        } else if (free <= SHRINK_PROMOTE_MAX)
                                list_move(&page->lru, promote + free - 1);
                }

                /*
                 * Promote the slabs filled up most to the head of the
                 * partial list.
                 */
                for (i = SHRINK_PROMOTE_MAX - 1; i >= 0; i--)
                        list_splice(promote + i, &n->partial);

                spin_unlock_irqrestore(&n->list_lock, flags);

                /* Release empty slabs */
                list_for_each_entry_safe(page, t, &discard, lru)
                        discard_slab(s, page);

                if (slabs_node(s, node))
                        ret = 1;
        }

        return ret;
}
  • flush_all(s);
    • per cpu 캐시에 있는 slub page들을 모두 per 노드로 옮긴다. 가능하면 사용하지 않은 slub page들에 대해서는 해지하여 버디 시스템으로 돌려보낸다.
  • for_each_kmem_cache_node(s, node, n) {
    • per 노드에 대해 루프를 돈다.
  • INIT_LIST_HEAD(&discard);
    • discard 리스트를 초기화한다.
  • for (i = 0; i < SHRINK_PROMOTE_MAX; i++)?INIT_LIST_HEAD(promote + i);
    • SHRINK_PROMOTE_MAX(32) 만큼 promote[] 리스트를 초기화하다.
  • list_for_each_entry_safe(page, t, &n->partial, lru) {
    • per 노드의 partial 리스트의 모든 slub 페이지 수만큼 루프를 돈다.
  • int free = page->objects – page->inuse;
    • slub page의 object 수에서 사용중인 수를 뺀 남은 free object 수를 구한다.
  • if (free == page->objects) {?list_move(&page->lru, &discard);?n->nr_partial–;
    • 만일 free 수가 object의 총 수와 같은 경우(사용중인 object가 없는 empty slub) 이 slub page를 discard 리스트로 이동시키고 partial 리스트 카운터를 감소시킨다.
  • } else if (free <= SHRINK_PROMOTE_MAX)?list_move(&page->lru, promote + free – 1);
    • 만일 free object 수가 SHRINK_PROMOTE_MAX(32) 이하인 경우 1~32개의 free object 별로 묶기 위해 promote[free-1] 리스트로 옮긴다.
  • for (i = SHRINK_PROMOTE_MAX – 1; i >= 0; i–)?list_splice(promote + i, &n->partial);
    • per 노드의 partial 리스트 끝에 promote[i] 리스트를 추가한다.
    • 기존에 per 노드의 partial 리스트에 있는 free object가 32개를 초과하는 slub page의 선두에 free object가 32개인 promote[31] 부터 free object가 1개인 promote[0]까지 insert 한다.
      • 결국 free object가 1~32개 순으로 정렬되어 선두에 추가된다.
  • list_for_each_entry_safe(page, t, &discard, lru)?discard_slab(s, page);
    • discard 리스트에 있는 slub 페이지들을 모두 해제하여 버디 시스템으로 돌려준다.
  • if (slabs_node(s, node))?ret = 1;
    • slub 디버그깅 중에 n->nr_slabs 값이 있는 경우

 

다음 그림은 현재 per cpu 캐시의 모든 slub page를 per 노드로 옮기고 정리하는 모습을 보여준다.

__kmem_cache_shrink-1

 

kick_all_cpus_sync()

kernel/smp.c

/**
 * kick_all_cpus_sync - Force all cpus out of idle
 *
 * Used to synchronize the update of pm_idle function pointer. It's
 * called after the pointer is updated and returns after the dummy
 * callback function has been executed on all cpus. The execution of
 * the function can only happen on the remote cpus after they have
 * left the idle function which had been called via pm_idle function
 * pointer. So it's guaranteed that nothing uses the previous pointer
 * anymore.
 */
void kick_all_cpus_sync(void)
{
        /* Make sure the change is visible before we kick the cpus */
        smp_mb();
        smp_call_function(do_nothing, NULL, 1);
}
EXPORT_SYMBOL_GPL(kick_all_cpus_sync);

IPI call을 사용하여 각 cpu를 호출하여?더미 callback을 수행시키게 한다.

  • pm_idle 함수 포인터의 update의 동기화를 하는데 사용된다.
  • do_nothing()은 비어 있는 함수이다.

 

참고

답글 남기기

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