ZONE 타입

<kernel v5.0>

리눅스에서 물리 메모리 주소 영역을 ZONE 단위로 구분하여 관리한다. 각각의 ZONE은 다음과 같다.

  • ZONE_DMA
  • ZONE_DMA32
  • ZONE_NORMAL
  • ZONE_HIGHMEM
  • ZONE_MOVABLE
  • ZONE_DEVICE

 

ZONE_DMA & ZONE_DMA32

DMA(Direct Memory Access)를 사용하여 메모리에 직접 접근하는 디바이스들을 사용하는 시스템에서 DMA를 사용하는 디바이스가 해당 시스템이 사용하는 주소 버스를 모두 커버하는 경우에는 별도로 ZONE_DMA를 구성할 필요 없이 기본 적으로 만들어지는 ZONE_NORMAL을 사용하여 메모리를 할당받아 사용할 수 있다. 그러나 DMA를 사용하는 디바이스가 시스템이 지원하는 물리 메모리 주소보다 작은 경우에는 별도로 ZONE_DMA를 구성해서 DMA 디바이스용으로 메모리 할당을 제한된 주소 미만으로 만들어놔야 그 DMA 디바이스가 접근할 수 있게된다. x86 및 arm의 경우 다음과 같은 조건에 따라 CONFIG_ZONE_DMA 및 CONFIG_ZONE_DMA32 사용 유무를 결정해서 사용한다.

  • DMA를 사용하는 디바이스 장치의 주소 버스가 시스템의 최대 물리 메모리 접근 주소까지 접근될 수 있게 설계된 경우 CONFIG_DMA를 설정할 필요 없다. 그러나 DMA를 사용하는 디바이스 장치의 주소 버스가 시스템의 최대 물리 메모리 접근 주소보다 접근할 수 없는 경우 CONFIG_DMA를 설정해서 사용한다.
    • 32bit x86:
      • CONFIG_ZONE_DMA 사용
    • 64bit x86:
      • CONFIG_ZONE_DMA 및 CONFIG_ZONE_DMA32 사용
    • arm:
      • default 로 ZONE_DMA를 사용하지 않는다. 단 특정 시스템의 경우 ZONE_DMA를 사용하고 사이즈를 지정하여 운영한다.
    • arm64:
  • DMA 컨트롤러 및 채널 참고

 

DMA 사이즈 설정

  • 32bit x86
    • 16MB로 고정되어 자동 구성된다.
    • 24bit address bus를 사용한 AT(80286) 시스템에 채용된 ISA 버스에서 동작하는 디바이스를 호환(backward compatible)시키기 위해 사용된다
  • 64bit x86
    • 4GB로 고정되어 자동 구성된다.
    • 32bit address만을 사용하는 기존 버스들(32bit EISA, PCI,  24bit ISA, …)에서 동작하는 디바이스를 호환시키기 위해 사용된다.
  • 32bit ARM
    • CONFIG_DMA를 사용하는 경우 DMA size가 flexible하게 구성된다.
    • 임베디드 시스템 회로(Circuit) 구성이 DMA 디바이스 장치에서 제한된 address bus 핀(pin)만 사용하는 경우 그 지정할 수 있는 주소만큼 영역을 제한시킨다.
      • 예) arm32에서 DMA 디바이스 장치가 28 bit address bus를 지원하는 경우
        • DMA size는 256MB로 설정해야 한다.
  • 64bit ARM
    • 물리 메모리 시작 주소를 기준으로 최대 4GB 까지의 물리 메모리 주소로 자동 구성된다.

 

다음 그림은 DMA를 사용하는 디바이스 장치가 어느 때 CONFIG_DMA를 설정해야 하는지를 나타낸다.

zone-dma-1

  • DMA를 사용하는 디바이스 장치가 32비트 주소를 지원하므로 특별히 ZONE_DMA를 설정할 필요 없다.

 

zone-dma-2

  • DMA를 사용하는 디바이스 장치가 제한된 24비트 주소만을 지원하는데 32비트 주소로 할당된 메모리는 디바이스 장치에서 접근할 수 없다.

 

zone-dma-3

  • DMA를 사용하는 디바이스 장치가 제한된 24비트 주소만을 지원하므로 관련 메모리 할당을 ZONE_DMA 영역에서 할당을 받아 사용한다.
    • ZONE_DMA 사이즈를 64MB(2^26)로 설정하여 사용한다.

 

ZONE_NORMAL

  • 시스템에서 지원하는 물리 메모리가 아키텍처의 가상 주소에 1:1로 미리 매핑되어 사용할 수 있는 만큼의 영역이다. 따라서 커널 메모리 할당 시 ZONE_DMA 영역과 함께 가장 빠르게 접근할 수 있는 메모리 영역이다.
    • 32bit 시스템에서는 4G의 공간을 user space와 kernel space로 나누어 사용하므로 user space는 비워두고 kernel space의 일부 영역을 사용하여 물리 메모리를 배치하는데 이에 사용된 물리 메모리 영역이 ZONE_NORMAL이다.
      • 32bit arm
        • 커널 크기 구성에 따라 다르다.
          • CONFIG_VMSPLIT_3G
            • normal zone의 최대 영역 크기=760MB
            • user space가 3G, kernel space가 1G
            • 대부분의 PC 리눅스와 임베디드 커널에서 사용한다.
          • CONFIG_VMSPLIT_2G
            • normal zone의 최대 영역 크기=1GB + 760MB
            • user space가 2G, kernel space가 2G
            • 라즈베리파이2 및 일부 임베디드 시스템이 사용하는 설정이다.
          • CONFIG_VMSPLIT_1G
            • normal zone의 최대 영역 크기=2GB + 760MB
            • user space가 1G, kernel space가 3G
            • 극단적인 설정이라 거의 사용되지 않는다.
      • 32bit x86
        • normal zone의 최대 영역 크기=896MB
    • 물론 ZONE_DMA 및 ZONE_DMA32도 가상 주소에 1:1로 매핑되어 있다.
    • 32 비트 시스템에서 normal zone은 크기가 제한되어 있으므로 최대한 커널 할당에 사용되는 비싼 영역이다.
    • 64bit ARM인 ARMv8 아키텍처에서는 최대 커널 주소 공간이 256T이고 그 중 절반의 공간인 128T 공간을 물리 메모리 배치로 사용할 수 있다. 즉 거대한 normal zone이며, 이 공간이 매우 크기 때문에 별도의 highmem zone을 생성해야할 이유가 없다.
      • ARMv8.2 아키텍처부터는 최대 52bit 물리 주소를 지원하여 4 Peta 공간을 지원한다.

 

다음 그림은 32bit 시스템에서의 ZONE_NORMAL 영역이 제한되어 있음을 보여준다.

zone-2a

 

ZONE_HIGHMEM

ZONE_NORMAL이 처리하지 못하는 메모리 영역이 모두 ZONE_HIGHMEM으로 구성된다.

  • 32bit 시스템에서는 1:1 매핑이 일부만 가능하기 때문에 ZONE_NORMAL을 초과하는 메모리가 이 영역을 사용한다.
  • 64bit 시스템에서는 모든 물리 메모리가 1:1 매핑이 가능하므로 ZONE_HIGHMEM을 사용하지 않는다.
  • user 메모리의 할당 시 우선적으로 highmem 영역의 메모리를 먼저 소모시키고, 이를 다 소모한 경우에 한해 normal zone 영역을 사용한다.

 

ZONE_MOVABLE

이 영역은 아래와 같이 두 가지 목적으로 사용한다.

  • 단편화 방지
    • 마지막 zone에 대해서 각 노드의  일부 영역을 할당하여 버디 시스템으로 구현된 페이지 할당자가 메모리 파편화를 막기 위해 이 영역을 전용으로 사용한다.
    • 이 영역을 구성하지 않아도 버디 시스템은 migration 타입으로 분류하여 최대한 파편화를 막는데, 이 영역을 지정하는 경우 이 영역 전체가 movable 페이지 구성이 되므로 파편화를 막는데 더욱 큰 효율을 보일 수 있다.
    • 마지막(last) zone이란 것은 시스템에 설치된 zone 중 가장 상위에 존재하는 zone으로 시스템에 따라 가장 다른데 아래와 같이 높은 순부터 낮은 순으로 나열한다.
      • ZONE_HIGHMEM -> ZONE_NORMAL -> ZONE_DMA(또는 ZONE_DMA32)
      • ZONE_HIGHMEM이 없는 64비트 시스템에서는 ZONE_NORMAL이 될 수 있고 그것도 없는 경우 ZONE_DMA(또는 ZONE_DMA32)가 될 수 있다.
  • 핫플러그 메모리 지원
    • 버디 시스템의 메모리 파편화도 막으면서 동시에 최근에 구현된 “메모리 Hotplug”를 지원하기 위해 메모리 Hotplug를 원하는 노드 전체를 ZONE_MOVABLE로 구성하여 사용한다.

 

다음 그림은 NUMA 32bit ARM 시스템에서의 ZONE_MOVABLE 구성을 보여준다.

zone-3

 

다음 그림은 64bit ARM 시스템에서의 ZONE_MOVABLE 구성을 보여준다.

  • 커널 v4.16-rc1 부터 ZONE_DMA 대신 ZONE_DMA32를 사용한다.

zone-4

 

ZONE_DEVICE

  • 커널 v4.3에 새롭게 추가되었으며 대용량 persistent memory 디바이스 및 heterogeneous memory 디바이스 등에서 사용하기 위한 영역이다.
    • ZONE_DMA는 디바이스가 cpu의 주 메모리에 대한 접근을 하고자 하는 것과 비교를 해보면 ZONE_DEVICE는 다음을 추구한다.
      • cpu가 대용량의 디바이스 메모리에 접근하고자 한다.
        • persistent memrory 예) 주 메모리가 8G, 고속 디바이스 메모리가 512G
        • heterogeneous memory 예) 주 메모리가 2G, GPU 디바이스 메모리가 6G
      • 고속 처리를 위해 CPU 및 디바이스 측에서 캐시를 사용하고자 한다.
      • 동시성
        • 양쪽에서 h/w cache coherent가 동작되고자 한다.
        • cpu 측에서 atomic operation을 사용하고자 한다.
  • 필요조건
    • 메모리 핫플러그 및 핫리무브가 지원되어야 하며 64비트에서만 사용하는 SPARSEMEM_VMEMMAP 설정이 필요하다.
  • 서버급인 x86_64 및 PPC_64 아키텍처부터 구현되고 있다.
  • persistent memory 디바이스에 유저스페이스를 이용하는 DAX 매핑을 사용할 수도 있다.
  • heterogeneous memory 디바이스에서 접근하는 메모리를 CPU에서도 접근할 수 있다. (CONFIG_HMM)
    • 커널 v4.14에 추가되었다.
    • 전체 영역 또는 일부 영역을 지정한다.
    • ZONE_DEVICE를 통해 메모리 swap 장치로 사용할 수 있다.
    • private(CONFIG_DEVICE_PRIVATE)으로 설정된 HMM 메모리는 디바이스에서만 접근할 수 있고, CPU에서는 접근할 때에는 커널의 일반 메모리 관련 API를 사용하지 못하고,  HMM 전용 API를 통해서만 접근할 수 있다.
    • public(CONFIG_DEVICE_PUBLIC)으로 설정된 HMM 메모리는 디바이스와 CPU 양쪽에서 접근할 수 있다.
      • 커널에서 사용하는 모든 메모리 API들을 사용하여 접근할 수 있다.
      • 양방향 매핑에 대한 동기화 및 cache coherent 문제를 해결하도록 h/w가 지원해야 한다.
  • 고속 인터페이스
    • 가장 빠른 것은 디바이스 메모리가 SoC에 임베드되어 내부 고속 버스에 통합되는 경우이다.
      • 임베드된 GPU가 사용하는 GDDR 메모리
    • 외부 버스 중 가장 빠른 DRAM 메모리 버스를 사용할 수도 있다
      • 인텔의 옵테인 메모리(고가이며 CPU가 지원해야한다.)
    • PCIe를 사용하는 가장 대중화된 외부(시스템이 아닌 칩 외부) 고속 인터페이스이다.
      • 고속 SSD 및 인텔의 옵테인 메모리(CPU가 지원하지 못할 때 사용할 수 있는 저가용)
        • 참고로 기존 SSD가 연결되는 인터페이스는 SATA부터 출발했지만 현재는 PCIe를 사용하는 AHCI와 NVMe(AHCI보다 더 빠른 프로토콜)가 있다.
      • pc 장착용 GPU들도 PCIe를 사용한다.
      • 메모리 접근 속도에 비해 수십배 느린 PCIe 버스를 사용해야 하므로 CPU에서 접근할 때에는 느린 제약이 있다.
      • 디바이스 메모리에 대한 atomic operation 및 cache coherent 등이 그동안 지원되지 않았으며 최근에는 가능해졌다.
  • 참고:

 

참고

 

 

Slub Memory Allocator -9- (캐시 Shrink)

<kernel v5.0>

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);
        put_online_mems();
        put_online_cpus();
        return ret;
}
EXPORT_SYMBOL(kmem_cache_shrink);

요청한 슬랩 캐시에서 해지가능한 슬랩 페이지들을 찾아 모두 해지시킨다.

 

__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)
{
        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;

        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;
}

요청한 슬랩 캐시에서 해지가능한 슬랩 페이지들을 찾아 모두 해지시키기 위해 다음과 같은 과정을 수행한다.

  • n->partial 리스트의 슬랩 페이지들 중 사용중인 object가 없는 경우 슬랩 페이지들을 버디 시스템으로 되돌린다.
  • n->partial 리스트의 슬랩 페이지들 중 32개 미만의 free object들은 asscending 정렬한다.

 

  • 코드 라인 13에서 슬랩 캐시의 per cpu 슬랩 페이지들을 모두 n->partial 리스트로 옮긴다.
  • 코드 라인 14~17에서 슬랩 캐시의 노드를 순회하며 discard 리스트를 초기화하고, SHRINK_PROMOTE_MAX(32) 만큼 promote[] 리스트를 초기화한다.
  • 코드 라인 27~41에서 n->partial 리스트의 슬랩 페이지들을 순회하며 object가 모두 free object들로 구성된 슬랩 페이지는 discard 리스트에 추가하고, free object 수가 SHRINK_PROMOTE_MAX(32) 이하인 경우 promote[free-1] 리스트에 추가한다.
  • 코드 라인 47~48에서 n->partial 리스트의 선두에 promote[i] 리스트를 추가한다.
    • 결국 free object가 1~32개 순으로 정렬되어 선두에 추가된다.
    • 가장 선두에 free object가 가장 적은 슬랩 페이지를 위로 배치하여 object 할당 시 슬랩 페이지가 리스트 관리에서 빨리 없어질 수 있게 한다.
  • 코드 라인 53~54에서 discard 리스트에 있는 슬랩 페이지들을 모두 해제하여 버디 시스템으로 돌려준다.
  • 코드 라인 56~57에서 slub 디버그깅 중에 노드에 슬랩이 남아있는 경우 함수 종료 시 1을 반환하게 한다.

 

다음 그림은 현재 슬랩 캐시의 모든 슬랩 페이지들을 n->partial 리스트로 옮기고 정리하는 모습을 보여준다.

__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()은 비어 있는 함수이다.

 

참고

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

 

참고

Slub Memory Allocator -5- (Slub 할당)

<kernel v5.0>

슬랩 페이지 할당

페이지 할당자로부터 슬랩 페이지를 구성하기 위해 필요한 페이지 수 만큼 Page Frame을 할당 받아 온 후 이를 슬랩 페이지로 초기화한다.

 

다음 그림은 함수 흐름으로 new_slab() 함수가 페이지 할당자로 새로운 페이지를 할당 요청하는 과정을 나타낸다.

new_slab-1

 

새 슬랩 페이지 할당

new_slab()

mm/slub.c

static struct page *new_slab(struct kmem_cache *s, gfp_t flags, int node)
{
        if (unlikely(flags & GFP_SLAB_BUG_MASK)) {
                gfp_t invalid_mask = flags & GFP_SLAB_BUG_MASK;
                flags &= ~GFP_SLAB_BUG_MASK;
                pr_warn("Unexpected gfp: %#x (%pGg). Fixing up to gfp: %#x (%pGg). Fix your code!\n""
,
                                invalid_mask, &invalid_mask, flags, &flags);
                dump_stack();
        }

        return allocate_slab(s,
                flags & (GFP_RECLAIM_MASK | GFP_CONSTRAINT_MASK), node);
}

버디 시스템으로 부터 새 슬랩 페이지를 할당 받아온다.

  • 코드 라인 3~10에서 슬랩 페이지 할당자를 통해 dma32 및 highmem 요청을 한 경우 에러를 출력 후  BUG() 호출한다.
  • 코드 라인 12~13에서 새 슬랩 페이지를 할당 받아오며, 허용되는 gfp 플래그들은 다음을 참고한다.

 

GFP_SLAB_BUG_MASK

mm/internal.h

/* Do not use these with a slab allocator */
#define GFP_SLAB_BUG_MASK (__GFP_DMA32|__GFP_HIGHMEM|~__GFP_BITS_MASK)

슬랩 할당자에 요청할 수 없는 gfp 플래그들이다.

 

GFP_RECLAIM_MASK

mm/internal.h

/*
 * The set of flags that only affect watermark checking and reclaim
 * behaviour. This is used by the MM to obey the caller constraints
 * about IO, FS and watermark checking while ignoring placement
 * hints such as HIGHMEM usage.
 */
#define GFP_RECLAIM_MASK (__GFP_RECLAIM|__GFP_HIGH|__GFP_IO|__GFP_FS|\
                        __GFP_NOWARN|__GFP_RETRY_MAYFAIL|__GFP_NOFAIL|\
                        __GFP_NORETRY|__GFP_MEMALLOC|__GFP_NOMEMALLOC|\
                        __GFP_ATOMIC)

슬랩 할당자에 요청할 수 있는 reclaim 관련 gfp 플래그들이다.

 

GFP_CONSTRAINT_MASK

mm/internal.h

/* Control allocation cpuset and node placement constraints */
#define GFP_CONSTRAINT_MASK (__GFP_HARDWALL|__GFP_THISNODE)

슬랩 할당자에 요청할 수 있는 cpuset 및 node 관련 gfp 플래그들이다.

 

allocate_slab()

mm/slub.c

static struct page *allocate_slab(struct kmem_cache *s, gfp_t flags, int node)
{
        struct page *page;
        struct kmem_cache_order_objects oo = s->oo;
        gfp_t alloc_gfp;
        void *start, *p, *next;
        int idx, order;
        bool shuffle;

        flags &= gfp_allowed_mask;

        if (gfpflags_allow_blocking(flags))
                local_irq_enable();

        flags |= s->allocflags;

        /*
         * Let the initial higher-order allocation fail under memory pressure
         * so we fall-back to the minimum order allocation.
         */
        alloc_gfp = (flags | __GFP_NOWARN | __GFP_NORETRY) & ~__GFP_NOFAIL;
        if ((alloc_gfp & __GFP_DIRECT_RECLAIM) && oo_order(oo) > oo_order(s->min))
                alloc_gfp = (alloc_gfp | __GFP_NOMEMALLOC) & ~(__GFP_RECLAIM|__GFP_NOFAIL);

        page = alloc_slab_page(s, alloc_gfp, node, oo);
        if (unlikely(!page)) {
                oo = s->min;
                alloc_gfp = flags;
                /*
                 * Allocation may have failed due to fragmentation.
                 * Try a lower order alloc if possible
                 */
                page = alloc_slab_page(s, alloc_gfp, node, oo);
                if (unlikely(!page))
                        goto out;
                stat(s, ORDER_FALLBACK);
        }

        page->objects = oo_objects(oo);

        order = compound_order(page);
        page->slab_cache = s;
        __SetPageSlab(page);
        if (page_is_pfmemalloc(page))
                SetPageSlabPfmemalloc(page);

        kasan_poison_slab(page);

        start = page_address(page);

        setup_page_debug(s, start, order);

        shuffle = shuffle_freelist(s, page);

        if (!shuffle) {
                start = fixup_red_left(s, start);
                start = setup_object(s, page, start);
                page->freelist = start;
                for (idx = 0, p = start; idx < page->objects - 1; idx++) {
                        next = p + s->size;
                        next = setup_object(s, page, next);
                        set_freepointer(s, p, next);
                        p = next;
                }
                set_freepointer(s, p, NULL);
        }

        page->inuse = page->objects;
        page->frozen = 1;

out:
        if (gfpflags_allow_blocking(flags))
                local_irq_disable();
        if (!page)
                return NULL;

        mod_lruvec_page_state(page,
                (s->flags & SLAB_RECLAIM_ACCOUNT) ?
                NR_SLAB_RECLAIMABLE : NR_SLAB_UNRECLAIMABLE,
                1 << oo_order(oo));

        inc_slabs_node(s, page_to_nid(page), page->objects);

        return page;
}

slub 페이지 할당을 받고 그 페이지에 포함된 free object의 초기화를 수행한다.

  • 모든 free object들을 순서대로 연결하고 마지막 object의 연결은 null로 한다.
    • slub에서 free object들끼리 단방향으로 연결된다.
    • slub을 처음 할당 받게 되면 그에 속한 모든 obect는 free 상태이므로 전체 object를 순서대로 연결한다.

 

  • 코드 라인 10~15에서 부트 타임이 아닌 경우 direct-reclaim이 허용된 경우 irq를 enable 한다.
    • 부트 타임에는 io, fs, reclaim을 허용하지 않는다.
  • 코드 라인 21~23에서 alloc_gfp 플래그에 nowarn, noretry를 추가하고 nofail 플래그를 제거한다. 만일 direct-reclaim이 허용된 경우 nomemalloc을 추가하고, reclaim 및 nofail을 제거한다.
  • 코드 라인 25~37에서 s->oo로 슬랩 페이지를 할당해온다. 만일 실패하는 경우 s->min으로 슬랩 페이지를 할당해온다.
  • 코드 라인 39~69에서 슬랩 페이지의 page 디스크립터를 다음과 같이 초기화하고, free object들의 FP를 모두 연결한다. 보안 옵션 중 CONFIG_SLAB_FREELIST_RANDOM 커널 옵션을 사용하면 free object 들에 대해 랜덤하게 순서를 바꿀 수 있다. 또한 CONFIG_SLAB_FREELIST_HARDENED 커널 옵션을사용하면FP들의내용이 encaptualization되어 알아볼 수 없게 숨길 수 있다.
    • fixup_red_left() 함수에서는 red-zone을 사용한 경우 red_left_pad 만큼 object 주소를 이동시켜 반환한다.
    • page->objects에 결정된 order에 해당하는 object 수를 저장한다.
    • page->slab_cache에 슬랩 캐시를 지정한다.
    • PG_slab 플래그를 추가한다.
    • pfmemalloc 페이지를 사용한 경우 PG_active 플래그를 추가한다.
    • page->inuse에 object 수를 저장한다.
    • page->frozen에 1을 대입한다. (per-cpu 캐시의 partial 리스트로 들어갈 예정이다)
    • poison 디버깅을 사용하는 경우 슬랩 페이지 전체에 POISON_INUSE(0x55)를 기록하여 처음 슬랩 object가 사용되지 않은 상태를 나타낸다.
  • 코드 라인 71~75에서 out: 레이블이다. 함수 처음에 irq를 enable한 경우 disable한다.
  • 코드 라인 77~80에서 reclaimable 슬랩 페이지를 할당한 경우 NR_SLAB_RECLAIMABLE 카운터를 증가시키고, 그렇지 않은 경우 NR_SLAB_UNRECLAIMABLE 카운터를 증가시킨다.
  • 코드 라인 82에서 s->node[]->nr_slabs를 1 증가시키고, s->node[]->total_objects 카운터에 할당한 objects 수를 추가한다.
  • 코드 라인 84에서 할당한 슬랩 페이지를 반환한다.

 

다음 그림은 order 1 슬랩 페이지를 할당 받은 후 초기화된 모습을 보여준다.

 

Slub with PFMEMALLOC

  • PF_MEMALLOC 또는 TIF_MEMDIE 플래그를 사용하는 경우 free 페이지가 부족하여 low 워터마크 기준 아래에 도달한 경우에도 ALLOC_NO_WATERMARKS 플래그를 사용하여 페이지를 확보할 수 있다.
    • 이렇게 확보한 페이지에는 page->pfmemalloc이 설정되어 있다.
      • get_page_from_freelist() -> 버디 할당 후 -> prep_new_page() 함수내에서 다음과 같이 설정된다.
        • page->pfmemalloc = !!(alloc_flags & ALLOC_NO_WATERMARKS);
    • slub 에서 PF_MEMALLOC 사용처
      • Swap over the NBD(Network Block Device)
        • NBD(Network Block Device) 드라이버로 TCP 통신을 통해 swap 할 수 있다.
          • SOCK_MEMALLOC 플래그가 PF_MEMALLOC을 사용하게 한다.
        • 디스크기반의 swap을 사용하지 않고 네트워크 기반의 swap 을 사용하는 경우에 메모리 부족시 skb 관련한 작은 버퍼들을 많이 할당받아 사용하는데 이의 할당이 원할하게 하도록 이러한 pfmemalloc 설정된 페이지를 사용할 수 있게 한다.
        • slub 페이지의 Active 플래그를 설정하여 이러한 pfmemalloc 상황으로 할당된 slub 페이지를 구별한다.
          • 이렇게 active 설정된 slub 페이지는 일반적인 경우에는 사용할 수 없다.

 

set_freepointer()

mm/slub.c

static inline void set_freepointer(struct kmem_cache *s, void *object, void *fp)
{
        unsigned long freeptr_addr = (unsigned long)object + s->offset;

#ifdef CONFIG_SLAB_FREELIST_HARDENED
        BUG_ON(object == fp); /* naive detection of double free or corruption */
#endif

        *(void **)freeptr_addr = freelist_ptr(s, fp, freeptr_addr);
}

slub object의 FP 위치에 다음 object의 주소를 기록한다. 만일 CONFIG_SLAB_FREELIST_HARDENED 커널 옵션을 사용한 경우 FP 내용은 알아볼 수 없게 encaptualization된다.

  • 코드 라인 3에서 FP(Free Pointer)는 항상 object에서 s->offset 만큼을 건너 뛴다.
  • 코드 라인 9에서 FP 위치에 다음 free object 주소를 기록한다.

 

다음 그림은 최대 5개의 object를 가진 slub page에서 3개의 free object가 구성되어 서로 연결된 상태이며, FP가 object 내부에 위치한 일반적인 경우와, FP를 object 다음에 위치한 경우의 FP(Free Pointer) 위치와 offset 값을 보여준다.

  • SLAB_POISON, SLAB_DESTROY_BY_RCU, 별도의 생성자를 사용한 슬랩 캐시는 2) 번과 같이 FP가 object 뒤로 이동된다.

 

gfp_allowed_mask

mm/page_alloc.c

gfp_t gfp_allowed_mask __read_mostly = GFP_BOOT_MASK;

부트 타임에 사용되어선 안될 플래그를 제외한 모든 비트를 가지고 있다.

 

GFP_BOOT_MASK

mm/internal.h

/* Control slab gfp mask during early boot */
#define GFP_BOOT_MASK (__GFP_BITS_MASK & ~(__GFP_RECLAIM|__GFP_IO|__GFP_FS))
  •  GFP_BOOT_MASK
    • reclaim, io, fs 플래그 요청은 커널 부트 프로세스 중에서 사용하면 안되므로 GFP 표준 마스크에서 제거한다.

 

alloc_slab_page()

mm/slub.c

/*
 * Slab allocation and freeing
 */
static inline struct page *alloc_slab_page(struct kmem_cache *s,
                gfp_t flags, int node, struct kmem_cache_order_objects oo)
{
        struct page *page;
        unsigned int order = oo_order(oo);

        if (node == NUMA_NO_NODE)
                page = alloc_pages(flags, order);
        else
                page = __alloc_pages_node(node, flags, order);

        if (page && memcg_charge_slab(page, flags, order, s)) {
                __free_pages(page, order);
                page = NULL;
        }

        return page;
}

memory control group의 통제하에 slab에 사용할 페이지를 할당 받아 온다. 실패하는 경우 null을 반환한다.

  • 코드 라인 7~10에서 노드를 지정하지 않은 경우 적절한 노드에서 order 페이지 할당을 처리하고, 노드가 지정된 경우 해당 노드에서 order 페이지를 할당한다.
  • 코드 라인 12~15에서 memory control group에 order 페이지 할당 요청하며 통제를 통해 slab 할당이 취소될 수 있다.
  • 코드 라인 17에서 할당한 페이지를 반환한다.

 

inc_slabs_node()

mm/slub.c

static inline void inc_slabs_node(struct kmem_cache *s, int node, int objects)
{
        struct kmem_cache_node *n = get_node(s, node);

        /*
         * May be called early in order to allocate a slab for the
         * kmem_cache_node structure. Solve the chicken-egg
         * dilemma by deferring the increment of the count during
         * bootstrap (see early_kmem_cache_node_alloc).
         */
        if (likely(n)) {
                atomic_long_inc(&n->nr_slabs);
                atomic_long_add(objects, &n->total_objects);
        }
}

CONFIG_SLUB_DEBUG 커널 옵션을 사용하는 경우에만 높은 확률로 per 노드에 대한 nr_slabs를 증가시키고 total_objects에 objects 만큼을 더한다.

 

SetPageSlabPfmemalloc()

include/linux/page-flags.h

static inline void SetPageSlabPfmemalloc(struct page *page)
{       
        VM_BUG_ON_PAGE(!PageSlab(page), page);
        SetPageActive(page);
}

페이지에서 PG_Active 플래그를 설정한다.

 

참고

Slub Memory Allocator -7- (Object 해제)

<kernel v5.0>

슬랩 Object 할당 해제

지정한 kmem_cache의 slub page에서 슬랩 object를 해제한다.

 

다음 그림은 슬랩  object를 해제할 때 호출되는 함수들의 흐름을 보여준다.

 

슬랩 object 할당 해제

슬랩 object의 할당 해제와 관련된 API는 다음 두 가지로 구분된다.

  • 싱글 슬랩 object 할당 해제
    • kmem_cache_free()
  • 벌크 단위 슬랩 object 할당 해제
    • kmem_cache_free_bulk()

 

kmem_cache_free()

mm/slub.c

void kmem_cache_free(struct kmem_cache *s, void *x)
{
        s = cache_from_obj(s, x);
        if (!s)
                return;
        slab_free(s, virt_to_head_page(x), x, _RET_IP_);
        trace_kmem_cache_free(_RET_IP_, x);
}
EXPORT_SYMBOL(kmem_cache_free);

싱글 슬랩 object를 할당 해제한다.

  • 코드 라인 3~5에서 슬랩 object가 가리키는 슬랩 캐시를 알아온다. 알아온 슬랩 캐시가 루트 캐시일 수도 있다. 그 외의 경우 인자로 요청한 슬랩 캐시를 그대로 사용한다.
  • 코드 라인 6에서 슬랩 object를 free한다.

 

cache_from_obj()

mm/slab.h

static inline struct kmem_cache *cache_from_obj(struct kmem_cache *s, void *x)
{
        struct kmem_cache *cachep;
        struct page *page;

        /*
         * When kmemcg is not being used, both assignments should return the
         * same value. but we don't want to pay the assignment price in that
         * case. If it is not compiled in, the compiler should be smart enough
         * to not do even the assignment. In that case, slab_equal_or_root
         * will also be a constant.
         */
        if (!memcg_kmem_enabled() && 
            !unlikely(s->flags & SLAB_CONSISTENCY_CHECKS))
                return s;
                        
        page = virt_to_head_page(x);
        cachep = page->slab_cache;
        if (slab_equal_or_root(cachep, s))
                return cachep;
                        
        pr_err("%s: Wrong slab cache. %s but object is from %s\n",
               __func__, cachep->name, s->name);
        WARN_ON_ONCE(1);
        return s;
}

슬랩 object가 가리키는 슬랩 캐시를 알아온다. 알아온 슬랩 캐시가 루트 캐시일 수도 있다. 그 외의 경우 인자로 요청한 슬랩 캐시를 반환한다.

  • 코드 라인 13~15에서 memcg를 활성화시키지 않았으면서 작은 확률로 SLAB_DEBUG_FREE 플래그를 사용하지 않은 경우 주어진 캐시를 그냥 반환한다.
  • 코드 라인 17~20에서 슬랩 페이지가 가리키는 슬랩 캐시가 인자로 요청한 슬랩 캐시와 동일하거나 루트 캐시인 경우 슬랩 페이지가 가리키는 슬랩 캐시를 반환한다.
  • 코드 라인 22~25에서 지정된 캐시가 잘못된 경우 에러 메시지를 출력하고 인자로 요청한 슬랩 캐시를 그대로 반환한다.

 

슬랩 object 할당 해제 – Fastpath & Slowpath

slab_free()

mm/slub.c

static __always_inline void slab_free(struct kmem_cache *s, struct page *page,
                                      void *head, void *tail, int cnt,
                                      unsigned long addr)
{
        /*
         * With KASAN enabled slab_free_freelist_hook modifies the freelist
         * to remove objects, whose reuse must be delayed.
         */
        if (slab_free_freelist_hook(s, &head, &tail))
                do_slab_free(s, page, head, tail, cnt, addr);
}

슬랩 object 할당 해제 전 디버그 관련 전처리 루틴을 수행하고, 문제 없는 경우 @head ~ @tail 까지의 슬랩 object를 할당해제한다. @tail이 null인 경우 @head 하나만 할당해제한다.

 

do_slab_free()

mm/slub.c

/*
 * Fastpath with forced inlining to produce a kfree and kmem_cache_free that
 * can perform fastpath freeing without additional function calls.
 *
 * The fastpath is only possible if we are freeing to the current cpu slab
 * of this processor. This typically the case if we have just allocated
 * the item before.
 *
 * If fastpath is not possible then fall back to __slab_free where we deal
 * with all sorts of special processing.
 *
 * Bulk free of a freelist with several objects (all pointing to the
 * same page) possible by specifying head and tail ptr, plus objects
 * count (cnt). Bulk free indicated by tail pointer being set.
 */
static __always_inline void do_slab_free(struct kmem_cache *s,
                                struct page *page, void *head, void *tail,
                                int cnt, unsigned long addr)
{
        void *tail_obj = tail ? : head;
        struct kmem_cache_cpu *c;
        unsigned long tid;
redo:
        /*
         * Determine the currently cpus per cpu slab.
         * The cpu may change afterward. However that does not matter since
         * data is retrieved via this pointer. If we are on the same cpu
         * during the cmpxchg then the free will succeed.
         */
        do {
                tid = this_cpu_read(s->cpu_slab->tid);
                c = raw_cpu_ptr(s->cpu_slab);
        } while (IS_ENABLED(CONFIG_PREEMPT) &&
                 unlikely(tid != READ_ONCE(c->tid)));

        /* Same with comment on barrier() in slab_alloc_node() */
        barrier();

        if (likely(page == c->page)) {
                set_freepointer(s, tail_obj, c->freelist);

                if (unlikely(!this_cpu_cmpxchg_double(
                                s->cpu_slab->freelist, s->cpu_slab->tid,
                                c->freelist, tid,
                                head, next_tid(tid)))) {

                        note_cmpxchg_failure("slab_free", s, tid);
                        goto redo;
                }
                stat(s, FREE_FASTPATH);
        } else
                __slab_free(s, page, head, tail_obj, cnt, addr);

}

@head ~ @tail 까지의 슬랩 object를 할당해제한다. @tail이 null인 경우 @head 하나만 할당해제한다. (Fastpath, Slowpath)

  • 코드 라인 8에서 redo: 레이블이다. fastpath 용도로 per cpu 캐시로 부터 object 할당 해제 시도 시 트랜잭션 id가 바뀌어 실패한 경우 다시 반복할 위치다.
  • 코드 라인 15~19에서  tid 및 per-cpu 캐시를 atomic하게 읽어온다. 현재 이 시점에서 preemption이 언제라도 가능하기 때문에 수행 중 태스크 전환되었다 다시 돌아왔을 수 있다. 따라서 이 루틴에서는 tid와 캐시가 같은 cpu에서 획득된 것을 보장하게 하기 위해 뒤에서 확인 과정을 수행한다.
  • 코드 라인 22에서 인터럽트 마스크 없이 slab의 할당/해제 알고리즘이 동작하려면 cpu_slab 데이터를 읽는 순서에 의존하게 된다. object와 page보다 먼저 tid를 읽기 위해 컴파일러로 하여금 optimization을 하지 않도록 컴파일러 배리어를 사용하여 명확히 동작 순서를 구분하게 하였다.
  • 코드 라인 24~35에서 높은 확률로 슬랩 페이지가 c->page와 같은 경우 per cpu 캐시의 freelist 선두에 끼워 넣을 1 개 이상의 object들을 추가하고, FREE_FASTPATH 카운터를 증가시킨다. 만일 atomic 치환이 실패한 경우 redo: 레이블로 이동하여 다시 시도한다. (Fastpath)
  • 코드 라인 36~37에서 슬랩 페이지가 현재 per cpu 캐시의 페이지와 다른 경우 slowpath 방식으로 object를 free 한다. (Slowpath 호출)

 

다음 그림은 object를 해제할 때 fastpath 루틴이 동작하여 현재의 per cpu 캐시의 freelist의 선두에 1개의 free object만 insert한 것을 보여준다.

slab_free-1a

 

슬랩 object 할당 해제 – Slowpath

__slab_free()

mm/slub.c -1/2-

/*
 * Slow path handling. This may still be called frequently since objects
 * have a longer lifetime than the cpu slabs in most processing loads.
 *
 * So we still attempt to reduce cache line usage. Just take the slab
 * lock and free the item. If there is no additional partial page
 * handling required then we can return immediately.
 */
static void __slab_free(struct kmem_cache *s, struct page *page,
                        void *head, void *tail, int cnt,
                        unsigned long addr)

{
        void *prior;
        int was_frozen;
        struct page new;
        unsigned long counters;
        struct kmem_cache_node *n = NULL;
        unsigned long uninitialized_var(flags);

        stat(s, FREE_SLOWPATH);

        if (kmem_cache_debug(s) &&
            !free_debug_processing(s, page, head, tail, cnt, addr))
                return;

        do {
                if (unlikely(n)) {
                        spin_unlock_irqrestore(&n->list_lock, flags);
                        n = NULL;
                }
                prior = page->freelist;
                counters = page->counters;
                set_freepointer(s, tail, prior);
                new.counters = counters;
                was_frozen = new.frozen;
                new.inuse -= cnt;
                if ((!new.inuse || !prior) && !was_frozen) {

                        if (kmem_cache_has_cpu_partial(s) && !prior) {

                                /*
                                 * Slab was on no list before and will be
                                 * partially empty
                                 * We can defer the list move and instead
                                 * freeze it.
                                 */
                                new.frozen = 1;

                        } else { /* Needs to be taken off a list */

                                n = get_node(s, page_to_nid(page));
                                /*
                                 * Speculatively acquire the list_lock.
                                 * If the cmpxchg does not succeed then we may
                                 * drop the list_lock without any processing.
                                 *
                                 * Otherwise the list_lock will synchronize with
                                 * other processors updating the list of slabs.
                                 */
                                spin_lock_irqsave(&n->list_lock, flags);

                        }
                }

        } while (!cmpxchg_double_slab(s, page,
                prior, counters,
                head, new.counters,
                "__slab_free"));

@head ~ @tail 까지의 슬랩 object를 할당해제한다 (slowpath)

  • 코드 라인 13에서 FREE_SLOWPATH 카운터를 증가시킨다.
  • 코드 라인 15~17에서 SLAB_DEBUG_FLAGS 플래그를 사용한 경우 slub object free 디버깅을 위해 object를 해제하기 전에 체크하여 문제가 있는 경우 경고 메시지를 출력하여 알리고 처리를 중단하고 루틴을 빠져나간다.
  • 코드 라인 19에서 atomic 교체가 실패한 경우 반복될 위치이다.
  • 코드 라인 20~23에서 낮은 확률로 노드가 지정된 경우 노드락을 해제하고, 노드 지정을 포기한다.
  • 코드 라인 24~29에서 page->freelist 앞에 free object를 insert할 준비를 한다. inuse 카운터는 free할 object 수 만큼 감소시킨다.
  • 코드 라인 30~40에서 frozen 상태의 슬랩 페이지가 아니면서(!was_frozen)  c->freelist에 남은 free object들이 없는 경우 c->partial 리스트에 추가할 준비를 위해 슬랩 페이지를 forzen 상태로 바꿀 준비를 한다.
  • 코드 라인 42~55에서 frozen 상태의 슬랩 페이지가 아니면서(!was_frozen)  슬랩 페이지의 모든 object가 모두 사용 중이었다가 첫 free object가 발생한 경우라면 소속된 노드를 알아오고 노드락을 획득한다.
  • 코드 라인 58~61에서 atomic 하게 교체를 다음과 같이 수행하고실패하는경우반복한다.
    • if page->freelist == prior && page->counters == counters
      • page->freelist = @head (해지할 object들의 선두)
      • page->counters = new.counters

 

mm/slub.c -2/2-

        if (likely(!n)) {

                /*
                 * If we just froze the page then put it onto the
                 * per cpu partial list.
                 */
                if (new.frozen && !was_frozen) {
                        put_cpu_partial(s, page, 1);
                        stat(s, CPU_PARTIAL_FREE);
                }
                /*
                 * The list lock was not taken therefore no list
                 * activity can be necessary.
                 */
                if (was_frozen)
                        stat(s, FREE_FROZEN);
                return;
        }

        if (unlikely(!new.inuse && n->nr_partial >= s->min_partial))
                goto slab_empty;

        /*
         * Objects left in the slab. If it was not on the partial list before
         * then add it.
         */
        if (!kmem_cache_has_cpu_partial(s) && unlikely(!prior)) {
                if (kmem_cache_debug(s))
                        remove_full(s, n, page);
                add_partial(n, page, DEACTIVATE_TO_TAIL);
                stat(s, FREE_ADD_PARTIAL);
        }
        spin_unlock_irqrestore(&n->list_lock, flags);
        return;

slab_empty:
        if (prior) {
                /*
                 * Slab on the partial list.
                 */
                remove_partial(n, page);
                stat(s, FREE_REMOVE_PARTIAL);
        } else {
                /* Slab must be on the full list */
                remove_full(s, n, page);
        }

        spin_unlock_irqrestore(&n->list_lock, flags);
        stat(s, FREE_SLAB);
        discard_slab(s, page);
}
  • 코드 라인 1~18에서 per 노드로의 접근이 필요없는 경우이다. 다음 조건 항목들을 수행한 후 함수를 빠져나간다.
    • 만일 새롭게 frozen된 슬랩 페이지인 경우 c->partial 리스트에 추가하고 CPU_PARTIAL_FREE 카운터를 증가시킨다.
    • 기존에 frozen 상태였던 경우 FREE_FROZEN 카운터를 증가시킨다.
  • 코드 라인 20~21에서 낮은 확률로 슬랩 페이지의 모든 object가 free object가 된 경우이면서 노드의 partial 슬랩 수가 overflow된 경우 슬랩을 버디 시스템으로 반환하기 위해 slab_empty: 레이블로 이동한다.
  • 코드 라인 27~32에서 per cpu 캐시에서 partial 리스트가 지원되지 않으면서 낮은 확률로 c->freelist에 하나의 free object도 없었던 경우 n->partial 리스트의 마지막에 추가하고, FREE_ADD_PARTIAL 리스트에 추가한다.
  • 코드 라인 36에서 slab_empty: 레이블이다. free object로만 이루어진 슬랩 페이지를 버디 시스템으로 보내기 위해 이동해 올 레이블이다.
  • 코드 라인37~42에서 기존에 free object가 있었으면 n->partial 리스트에서 제거한 후 FREE_REMOVE_PARTIAL 카운터를 증가시킨다.
  • 코드 라인 43~46에서 기존에 free object가 하나도 없었으면 SLUB 디버깅 중에 연결되어 있던 full 리스트에서 제거한다
  • 코드 라인 48~50에서 노드 락을 풀고, FREE_SLAB 카운터를 증가시킨 후 슬랩 페이지를 버디 시스템으로 돌려보낸다.

 

다음 그림은 slub object를 Slowpath 단계에서 처리하는 방법을 보여준다.

c->partial에 지정한 슬랩 페이지 추가

put_cpu_partial()

mm/slub.c

/*
 * Put a page that was just frozen (in __slab_free) into a partial page
 * slot if available.
 *
 * If we did not find a slot then simply move all the partials to the
 * per node partial list.
 */
static void put_cpu_partial(struct kmem_cache *s, struct page *page, int drain)
{
#ifdef CONFIG_SLUB_CPU_PARTIAL
        struct page *oldpage;
        int pages;
        int pobjects;

        preempt_disable();
        do {
                pages = 0;
                pobjects = 0;
                oldpage = this_cpu_read(s->cpu_slab->partial);

                if (oldpage) {
                        pobjects = oldpage->pobjects;
                        pages = oldpage->pages;
                        if (drain && pobjects > s->cpu_partial) {
                                unsigned long flags;
                                /*
                                 * partial array is full. Move the existing
                                 * set to the per node partial list.
                                 */
                                local_irq_save(flags);
                                unfreeze_partials(s, this_cpu_ptr(s->cpu_slab));
                                local_irq_restore(flags);
                                oldpage = NULL;
                                pobjects = 0;
                                pages = 0; 
                                stat(s, CPU_PARTIAL_DRAIN);
                        }
                }

                pages++;
                pobjects += page->objects - page->inuse;

                page->pages = pages;
                page->pobjects = pobjects;
                page->next = oldpage;

        } while (this_cpu_cmpxchg(s->cpu_slab->partial, oldpage, page)
                                                                != oldpage);
        if (unlikely(!s->cpu_partial)) {
                unsigned long flags;

                local_irq_save(flags);
                unfreeze_partials(s, this_cpu_ptr(s->cpu_slab));
                local_irq_restore(flags);
        }
        preempt_enable();
#endif
}

지정한 슬렙 페이지를 c->partial 리스트에 추가한다. 단 @drain이 true인 경우 c->partial 리스트가 overflow될 경우 기존에 있던 슬랩 페이지들을 모두 n->partial 리스트로 이동시킨다.

  • 코드 라인 8~41에서 preemption을 disable 한 상태에서 @c->partial에 슬랩 페이지를 추가한다. page->pobjects에는 기존 슬랩 페이지의 pobject 값과 이번 슬랩 페이지의 free object 수를 더해 대입한다. 만일 @drain이 true이고 슬랩 캐시내의 free object 수가 overflow된 경우 인 경우 c->partial 리스트 가 overflow될 경우 기존에 있던 c->partial 리스트의 슬랩 페이지들을 모두 n->partial 리스트로 이동시키고 CPU_PARTIAL_DRAIN 카운터를 증가시킨다.
  • 코드 라인 42~48에서 작은 확률로 per cpu 캐시의 partial 리스트에서 관리하는 제한 object 수가 0으로 설정된 경우 c->partial 리스트에 있는 모든 슬랩 페이지들을 n->partial 리스트로 옮긴다

 

다음 그림은 슬랩 페이지를 c->partial 리스트의 선두에 추가하는 것을 보여준다.

 

n->partial에 지정한 슬랩 페이지 추가

add_partial()

mm/slub.c

static inline void add_partial(struct kmem_cache_node *n,
                                struct page *page, int tail)
{
        lockdep_assert_held(&n->list_lock);
        __add_partial(n, page, tail);
}

지정된 슬랩 페이지를 n->partial 리스트의 지정된 위치(선두 또는 후미)에 추가한다.

 

__add_partial()

mm/slub.c

/*
 * Management of partially allocated slabs.
 */
static inline void
__add_partial(struct kmem_cache_node *n, struct page *page, int tail)
{
        n->nr_partial++;
        if (tail == DEACTIVATE_TO_TAIL)
                list_add_tail(&page->lru, &n->partial);
        else
                list_add(&page->lru, &n->partial);
}

지정된 슬랩 페이지를 n->partial 리스트의 지정된 위치(선두 또는 후미)에 추가하고 n->nr_partial을 증가 시킨다.

 

다음 그림은 slub page를 per 노드의 partial 리스트의 지정된 위치(선두 또는 후미)에 추가하는 모습을 보여준다.

add_partial-1

 

n->partial에서 지정한 슬랩 페이지 제거

remove_partial()

mm/slub.c

static inline void remove_partial(struct kmem_cache_node *n,
                                        struct page *page)
{
        lockdep_assert_held(&n->list_lock);
        __remove_partial(n, page);
}

지정된 슬랩 페이지를 n->partial 리스트에서 제거한다.

 

__remove_partial()

mm/slub.c

static inline void
__remove_partial(struct kmem_cache_node *n, struct page *page)
{
        list_del(&page->lru);
        n->nr_partial--;
}

지정된 슬랩 페이지를 n->partial 리스트에서 제거하고, n->nr_partial 을 감소시킨다.

 

다음 그림은 per 노드의 partial 리스트에서 제거하여 kmem_cache가 관리하지 않는 상태로 바꾼다.

remove_partial-1

 

c->freelist의 모든 슬랩 페이지 -> n->freelist로 이동

unfreeze_partials()

mm/slub.c

/*
 * Unfreeze all the cpu partial slabs.
 *
 * This function must be called with interrupts disabled
 * for the cpu using c (or some other guarantee must be there
 * to guarantee no concurrent accesses).
 */
static void unfreeze_partials(struct kmem_cache *s,
                struct kmem_cache_cpu *c)
{
#ifdef CONFIG_SLUB_CPU_PARTIAL
        struct kmem_cache_node *n = NULL, *n2 = NULL;
        struct page *page, *discard_page = NULL;

        while ((page = c->partial)) {
                struct page new;
                struct page old;

                c->partial = page->next;

                n2 = get_node(s, page_to_nid(page));
                if (n != n2) {
                        if (n)
                                spin_unlock(&n->list_lock);

                        n = n2;
                        spin_lock(&n->list_lock);
                }

                do {

                        old.freelist = page->freelist;
                        old.counters = page->counters;
                        VM_BUG_ON(!old.frozen);

                        new.counters = old.counters;
                        new.freelist = old.freelist;

                        new.frozen = 0;

                } while (!__cmpxchg_double_slab(s, page,
                                old.freelist, old.counters,
                                new.freelist, new.counters,
                                "unfreezing slab"));

                if (unlikely(!new.inuse && n->nr_partial >= s->min_partial)) {
                        page->next = discard_page;
                        discard_page = page;
                } else {
                        add_partial(n, page, DEACTIVATE_TO_TAIL);
                        stat(s, FREE_ADD_PARTIAL);
                }
        }

        if (n)
                spin_unlock(&n->list_lock);

        while (discard_page) {
                page = discard_page;
                discard_page = discard_page->next;

                stat(s, DEACTIVATE_EMPTY);
                discard_slab(s, page);
                stat(s, FREE_SLAB);
        }
#endif
}

c->partial 리스트에서 관리하는 모든 슬랩 페이지들을 n->partial 리스트의 후미에 추가한다. 만일 n->partial 리스트가 overflow되는 경우 overflow된 슬랩 페이지들 중 할당된 object가 없는 슬랩 페이지들을 버디 시스템으로 되돌려 준다.

 

다음 그림은 per cpu 캐시의 partial 리스트의 모든 slub page들을 per 노드의 partial 리스트로 옮기는 과정을 보여준다.

unfreeze_partials-1a

 

슬랩 페이지 -> 버디 시스템

discard_slab()

mm/slub.c

static void discard_slab(struct kmem_cache *s, struct page *page)
{
        dec_slabs_node(s, page_to_nid(page), page->objects);
        free_slab(s, page);
}

슬랩 페이지를 해제하여 버디 시스템으로 돌려보낸다. 그리고 n->nr_slabs(슬랩 페이지 수)를 감소시키고, n->total_objects 값도 그 objects 수 만큼 감소시킨다.

 

dec_slabs_node()

mm/slub.c

static inline void dec_slabs_node(struct kmem_cache *s, int node, int objects)
{
        struct kmem_cache_node *n = get_node(s, node);

        atomic_long_dec(&n->nr_slabs);
        atomic_long_sub(objects, &n->total_objects);
}

n->nr_slabs(슬랩 페이지 수)를 감소시키고, n->total_objects 값도 그 objects 수 만큼 감소시킨다.

 

참고