Vmap

비 연속으로 구성된 여러 개의 order 0 물리 페이지 프레임들을 page descriptor 배열 정보로 요청하는 경우 VMALLOC address space 공간의 빈 자리를 찾아 매핑하고 매핑된 가상 시작 주소를 반환한다.

  • 내부적으로는 VMALLOC address space 범위의 빈 공간 검색을 위해 RB 트리와 리스트를 사용한다.
    • vmap_area_root RB 트리
    • vmap_area_list 리스트
  • vmap()의 성능 향상을 위해 vunmap()에서 해제한 vm 정보를 곧바로 지우지 않고 재활용하기 위해 lazy free 기법을 사용한다.
    • free_vmap_cache 리스트
    • 추가로 다음 4개의 전역 변수 사용
      • cached_hole_size
      • cached_vstart
      • cached_align
      • vmap_area_pcpu_hole (초기값은 VMALLOC_END)
    • lazy free 상태를 표현하는 플래그 비트는 다음과 같다.
      • VM_LAZY_FREE (0x1)
      • VM_LAZY_FREEING (0x2)
      • VM_VM_AREA (0x4)
  • 대체 api로 lazy TLB flush를 추가한 vm_map_ram()이 준비되어 있다.

 

다음 그림은 vmap() 함수에 대해 연관 함수들과의 처리 흐름을 보여준다.

vmap-2

 

vmap()

mm/vmalloc.c

/**
 *      vmap  -  map an array of pages into virtually contiguous space
 *      @pages:         array of page pointers
 *      @count:         number of pages to map
 *      @flags:         vm_area->flags
 *      @prot:          page protection for the mapping
 *
 *      Maps @count pages from @pages into contiguous kernel virtual
 *      space.
 */
void *vmap(struct page **pages, unsigned int count,
                unsigned long flags, pgprot_t prot)
{
        struct vm_struct *area;

        might_sleep();

        if (count > totalram_pages)
                return NULL;

        area = get_vm_area_caller((count << PAGE_SHIFT), flags,
                                        __builtin_return_address(0));
        if (!area)
                return NULL;

        if (map_vm_area(area, prot, pages)) {
                vunmap(area->addr);
                return NULL;
        }

        return area->addr;
}
EXPORT_SYMBOL(vmap);

커널용 연속된 가상 주소 공간에 요청한 물리 페이지들을 매핑하고 매핑한 가상 주소를 반환한다. 실패 시 null을 반환한다.

  • might_sleep();
    • preempt point로 더 높은 우선 순위의 태스크가 리스케쥴링 요청을 하는 경우 sleep 한다.
  • if (count > totalram_pages) return NULL;
    • 전체 메모리 페이지보다 더 많은 페이지를 요구하는 경우 처리를 포기하고 null을 반환한다.
  • area = get_vm_area_caller((count << PAGE_SHIFT), flags, __builtin_return_address(0)); if (!area) return NULL;
    • vmap_area 및 vm_struct 정보를 구성한다. 실패하는 경우 null을 반환한다.
  • if (map_vm_area(area, prot, pages)) { vunmap(area->addr); return NULL; }
    • vm_struct 정보로 페이지들의 매핑을 시도하고 실패한 경우 해제 후 null을 반환한다.

 

다음 그림은 요청한 물리 페이지들에 대해 VMALLOC address space에서 빈 공간을 찾아 매핑을 하는 모습을 보여준다.

vmap-1b

 

get_vm_area_caller()

mm/vmalloc.c

struct vm_struct *get_vm_area_caller(unsigned long size, unsigned long flags,
                                const void *caller)
{
        return __get_vm_area_node(size, 1, flags, VMALLOC_START, VMALLOC_END,
                                  NUMA_NO_NODE, GFP_KERNEL, caller);
}

요청한 size(byte 단위)로 VMALLOC address space에서 빈 공간을 찾아 vm_area 및 vm_struct 정보를 구성해온다.

 

map_vm_area()

mm/vmalloc.c

int map_vm_area(struct vm_struct *area, pgprot_t prot, struct page **pages)
{
        unsigned long addr = (unsigned long)area->addr;
        unsigned long end = addr + get_vm_area_size(area);
        int err;

        err = vmap_page_range(addr, end, prot, pages);

        return err > 0 ? 0 : err;
}
EXPORT_SYMBOL_GPL(map_vm_area);

요청한 vm_struct 정보로 가상 주소 범위를 매핑한다.

 

vmap_page_range()

mm/vmalloc.c

static int vmap_page_range(unsigned long start, unsigned long end,
                           pgprot_t prot, struct page **pages)
{
        int ret;

        ret = vmap_page_range_noflush(start, end, prot, pages);
        flush_cache_vmap(start, end);
        return ret;
}

요청한 가상 주소 범위를 매핑하고 그 공간을 flush한다.

 

vmap_page_range_noflush()

mm/vmalloc.c

/*
 * Set up page tables in kva (addr, end). The ptes shall have prot "prot", and
 * will have pfns corresponding to the "pages" array.
 *
 * Ie. pte at addr+N*PAGE_SIZE shall point to pfn corresponding to pages[N]
 */
static int vmap_page_range_noflush(unsigned long start, unsigned long end,
                                   pgprot_t prot, struct page **pages)
{
        pgd_t *pgd;
        unsigned long next;
        unsigned long addr = start;
        int err = 0;
        int nr = 0;

        BUG_ON(addr >= end);
        pgd = pgd_offset_k(addr);
        do {
                next = pgd_addr_end(addr, end);
                err = vmap_pud_range(pgd, addr, next, prot, pages, &nr);
                if (err)
                        return err;
        } while (pgd++, addr = next, addr != end);

        return nr;
}

요청 가상 주소 범위에 해당하는 커널 페이지 테이블을 **pages 와 속성 정보를 사용하여 매핑한다.

 

flush_cache_vmap()

arch/arm/include/asm/cacheflush.h()

/*
 * flush_cache_vmap() is used when creating mappings (eg, via vmap,
 * vmalloc, ioremap etc) in kernel space for pages.  On non-VIPT
 * caches, since the direct-mappings of these pages may contain cached
 * data, we need to do a full cache flush to ensure that writebacks
 * don't corrupt data placed into these pages via the new mappings.
 */
static inline void flush_cache_vmap(unsigned long start, unsigned long end)
{
        if (!cache_is_vipt_nonaliasing())
                flush_cache_all();
        else
                /*
                 * set_pte_at() called from vmap_pte_range() does not
                 * have a DSB after cleaning the cache line.
                 */
                dsb(ishst);
}

요청한 가상 주소 범위에 대해 flush를 한다.

  • 아키텍처가 pipt 캐시를 사용하고나 vipt nonaliasing을 지원하는 경우 flush를 할 필요가 없어서 성능이 크게 개선된다.

 

vunmap()

mm/vmalloc.c

/**
 *      vunmap  -  release virtual mapping obtained by vmap()
 *      @addr:          memory base address
 *
 *      Free the virtually contiguous memory area starting at @addr,
 *      which was created from the page array passed to vmap().
 * 
 *      Must not be called in interrupt context.
 */
void vunmap(const void *addr)
{
        BUG_ON(in_interrupt());
        might_sleep();
        if (addr)
                __vunmap(addr, 0);
}                           
EXPORT_SYMBOL(vunmap);

vmap() 함수로 vmalloc address space에 매핑한 가상 주소 영역의 매핑을 해제한다. 다만 물리 페이지는 해제 시키지 않는다.

 

다음 그림은 vunmap() 함수에 대해 연관 함수들과의 처리 흐름을 보여준다.

vunmap-2

 

다음 그림은 vummap() 함수가 요청한 가상 주소로 vmap_area()를 찾아 그에 해당하는 매핑을 삭제하는 모습을 보여준다.

vunmap-1b

 

__vunmap()

mm/vmalloc.c

static void __vunmap(const void *addr, int deallocate_pages)
{
        struct vm_struct *area;

        if (!addr)
                return;

        if (WARN(!PAGE_ALIGNED(addr), "Trying to vfree() bad address (%p)\n",
                        addr))
                return;

        area = remove_vm_area(addr);
        if (unlikely(!area)) {
                WARN(1, KERN_ERR "Trying to vfree() nonexistent vm area (%p)\n",
                                addr);
                return;
        }

        debug_check_no_locks_freed(addr, area->size);
        debug_check_no_obj_freed(addr, area->size);

        if (deallocate_pages) {
                int i;

                for (i = 0; i < area->nr_pages; i++) {
                        struct page *page = area->pages[i];

                        BUG_ON(!page);
                        __free_page(page);
                }

                if (area->flags & VM_VPAGES)
                        vfree(area->pages);
                else 
                        kfree(area->pages);
        }

        kfree(area);
        return;
}

요청 가상 주소로 vm 정보를 찾아 매핑을 제거하고 요청에 따라 각 페이지들을 해제하여 버디 시스템으로 돌려준다.

  • 요청 가상 주소를 RB 트리 vmap_area_root에 등록되어 있는 vmap_area 정보에서 검색하여 매치된 vmap_area 및 vm_struct 정보를 제거하고 매핑을 해제한 다음 deallocate_pages 인수의 요청 여부에 따라 물리 페이지들을 해제한다.

 

  • area = remove_vm_area(addr);
    • 요청 가상 주소를 RB 트리 vmap_area_root에 등록되어 있는 vmap_area 정보에서 검색하여 매치된 vmap_area 정보를 제거한 후 매핑을 해제하고 vm_struct 정보를 알아온다.
  • if (deallocate_pages) { int i; for (i = 0; i < area->nr_pages; i++) { struct page *page = area->pages[i]; __free_page(page); }
    • deallocate_pages 인수 요청이 설정된 경우 등록된 모든 페이지들을 해제하여 버디 시스템으로 돌려준다.
  • if (area->flags & VM_VPAGES) vfree(area->pages); else kfree(area->pages);
    • **pages가 vmalloc으로 할당된 경우는 vfree() 함수로 해제하고 그렇지 않은 경우 kfree() 함수로 해제한다.
  • kfree(area);
    • vm_struct 정보를 해제한다.

 

remove_vm_area()

mm/vmalloc.c

/**
 *      remove_vm_area  -  find and remove a continuous kernel virtual area
 *      @addr:          base address
 *
 *      Search for the kernel VM area starting at @addr, and remove it.
 *      This function returns the found VM area, but using it is NOT safe
 *      on SMP machines, except for its size or flags.
 */
struct vm_struct *remove_vm_area(const void *addr)
{
        struct vmap_area *va;

        va = find_vmap_area((unsigned long)addr);
        if (va && va->flags & VM_VM_AREA) {
                struct vm_struct *vm = va->vm;

                spin_lock(&vmap_area_lock);
                va->vm = NULL;
                va->flags &= ~VM_VM_AREA;
                spin_unlock(&vmap_area_lock);

                vmap_debug_free_range(va->va_start, va->va_end);
                kasan_free_shadow(vm);
                free_unmap_vmap_area(va);
                vm->size -= PAGE_SIZE;

                return vm;
        }
        return NULL;
}

요청 가상 주소를 RB 트리 vmap_area_root에 등록되어 있는 vmap_area 정보에서 검색하여 매치된 vmap_area 정보를 제거하고 매핑을 해제한 후 vm_struct 정보를 알아온다.

  • va = find_vmap_area((unsigned long)addr);
    • 요청 가상 주소를 RB 트리 vmap_area_root에 등록되어 있는 vmap_area 정보에서 검색하여 매치된 vmap_area 정보를 찾아온다.
  • if (va && va->flags & VM_VM_AREA) { struct vm_struct *vm = va->vm;
    • 찾은 vmap_area 정보의 플래그가 VM_VM_AREA로 설정된 경우 vm_struct 정보를 참조한다.
  • spin_lock(&vmap_area_lock); va->vm = NULL; va->flags &= ~VM_VM_AREA; spin_unlock(&vmap_area_lock)
    • vmap_area 정보에서 vm_struct의 연결을 제거하고 플래그도 VM을 제거한다.
  • free_unmap_vmap_area(va);
    • vmap_area 정보를 사용하여 매핑을 해제한다.
  • vm->size -= PAGE_SIZE;
    • vm의 사이에서 1페이지를 감소시킨다.

 

find_vmap_area()

mm/vmalloc.c

static struct vmap_area *find_vmap_area(unsigned long addr)
{
        struct vmap_area *va;

        spin_lock(&vmap_area_lock);
        va = __find_vmap_area(addr);
        spin_unlock(&vmap_area_lock);

        return va;
}

vmap_area lock으로 보호한 후 요청 가상 주소로 vmap_area 정보를 찾아온다. 못찾은 경우 null을 반환한다.

 

__find_vmap_area()

mm/vmalloc.c

static struct vmap_area *__find_vmap_area(unsigned long addr)
{
        struct rb_node *n = vmap_area_root.rb_node;

        while (n) {
                struct vmap_area *va;

                va = rb_entry(n, struct vmap_area, rb_node);
                if (addr < va->va_start)
                        n = n->rb_left;
                else if (addr >= va->va_end)
                        n = n->rb_right;
                else
                        return va;
        }

        return NULL;
}

요청 가상 주소로 vmap_area 정보를 찾아온다. 못찾은 경우 null을 반환한다.

  • 요청 가상 주소를 RB 트리 vmap_area_root에 등록되어 있는 vmap_area 정보에서 검색하여 매치된 vmap_area 정보를 찾아온다.

 

free_unmap_vmap_area()

mm/vmalloc.c

/*
 * Free and unmap a vmap area
 */
static void free_unmap_vmap_area(struct vmap_area *va)
{
        flush_cache_vunmap(va->va_start, va->va_end);
        free_unmap_vmap_area_noflush(va);
}

아키텍처에 따라 지정된 가상 주소 범위의 데이타 캐시를 비운후 해당 영역의 매핑을 해제한다.

 

flush_cache_vunmap()

arch/arm/include/asm/cacheflush.h

static inline void flush_cache_vunmap(unsigned long start, unsigned long end)
{
        if (!cache_is_vipt_nonaliasing())
                flush_cache_all();
}

아키텍처의 데이타 캐시가 vivt 타입이거나 vipt aliasing인 경우 캐시를 모두 비우게 한다.

  • 데이타 캐시가 pipt 타입이거나 vipt nonaliasing 타입인 경우 캐시를 비우지 않아도 되므로 성능이 향상된다.

 

free_unmap_vmap_area_noflush()

mm/vmalloc.c

/*
 * Free and unmap a vmap area, caller ensuring flush_cache_vunmap had been
 * called for the correct range previously.
 */
static void free_unmap_vmap_area_noflush(struct vmap_area *va)
{       
        unmap_vmap_area(va);
        free_vmap_area_noflush(va);
}

vmap_area가 사용하는 가상 주소 영역의 매핑을 커널 페이지 테이블에서 해제한다. 그런 후 vmap_area를 곧바로 삭제하지 않고 purge_list에 추가한다.

  • vmap_area가 재활용되는 경우 시간 소모가 큰 TLB 캐시를 flush 하지 않아도 되기 때문에 성능이 매우 좋아진다.

 

unmap_vmap_area()

mm/vmalloc.c

/*
 * Clear the pagetable entries of a given vmap_area
 */
static void unmap_vmap_area(struct vmap_area *va)
{
        vunmap_page_range(va->va_start, va->va_end);
}

vmap_area의 가상 주소 영역의 매핑을 커널 페이지 테이블에서 해제한다.

 

vunmap_page_range()

mm/vmalloc.c

static void vunmap_page_range(unsigned long addr, unsigned long end)
{
        pgd_t *pgd;
        unsigned long next;

        BUG_ON(addr >= end);
        pgd = pgd_offset_k(addr);
        do {
                next = pgd_addr_end(addr, end);
                if (pgd_none_or_clear_bad(pgd))
                        continue;
                vunmap_pud_range(pgd, addr, next);
        } while (pgd++, addr = next, addr != end);
}

요청 가상 주소 범위의 매핑을 커널 페이지 테이블에서 해제한다.

 

free_vmap_area_noflush()

mm/vmalloc.c

/*
 * Free a vmap area, caller ensuring that the area has been unmapped
 * and flush_cache_vunmap had been called for the correct range
 * previously.
 */
static void free_vmap_area_noflush(struct vmap_area *va)
{
        va->flags |= VM_LAZY_FREE;
        atomic_add((va->va_end - va->va_start) >> PAGE_SHIFT, &vmap_lazy_nr);
        if (unlikely(atomic_read(&vmap_lazy_nr) > lazy_max_pages()))
                try_purge_vmap_area_lazy();
}

vmap_area를 곧바로 삭제하지 않고 VM_LAZY_FREE 플래그를 설정한다. 페이지 수가 lazy_max_pages()를 초과하는 경우 pruge를 수행한다.

  • 매핑 해제할 페이지 수를 vmap_lazy_nr에 저장하고 작은 확률로 lazy_max_pages()를 초과하는 경우 purge를 수행한다.

 

lazy_max_pages()

mm/vmalloc.c

/*
 * lazy_max_pages is the maximum amount of virtual address space we gather up
 * before attempting to purge with a TLB flush.
 *
 * There is a tradeoff here: a larger number will cover more kernel page tables
 * and take slightly longer to purge, but it will linearly reduce the number of
 * global TLB flushes that must be performed. It would seem natural to scale
 * this number up linearly with the number of CPUs (because vmapping activity
 * could also scale linearly with the number of CPUs), however it is likely
 * that in practice, workloads might be constrained in other ways that mean
 * vmap activity will not scale linearly with CPUs. Also, I want to be
 * conservative and not introduce a big latency on huge systems, so go with
 * a less aggressive log scale. It will still be an improvement over the old
 * code, and it will be simple to change the scale factor if we find that it
 * becomes a problem on bigger systems.
 */
static unsigned long lazy_max_pages(void)
{
        unsigned int log;

        log = fls(num_online_cpus());

        return log * (32UL * 1024 * 1024 / PAGE_SIZE);
}

32M에 해당하는 페이지 수 x online cpu 수를 표현하는데 필요한 비트 수를 반환한다.

  • cpu가 많아지는 경우 TLB flush는 시스템의 전체적인 성능을 떨어뜨리므로 cpu 수가 많아질 수록 lazy_max_pages 수는 더 커져야 한다.

 

try_purge_vmap_area_lazy()

mm/vmalloc.c

/*
 * Kick off a purge of the outstanding lazy areas. Don't bother if somebody
 * is already purging.
 */
static void try_purge_vmap_area_lazy(void)
{
        unsigned long start = ULONG_MAX, end = 0;

        __purge_vmap_area_lazy(&start, &end, 0, 0);
}

가상 주소 범위에 포함된 vmap_area_list의 엔트리들을모두 purge_list에 옮기고 해당하는 페이지 수를 vmap_lazy_nr에서 감소시킨다. 가상 주소 범위의 TLB도 flush한다.

 

__purge_vmap_area_lazy()

mm/vmalloc.c

/*
 * Purges all lazily-freed vmap areas.
 *
 * If sync is 0 then don't purge if there is already a purge in progress.
 * If force_flush is 1, then flush kernel TLBs between *start and *end even
 * if we found no lazy vmap areas to unmap (callers can use this to optimise
 * their own TLB flushing).
 * Returns with *start = min(*start, lowest purged address)
 *              *end = max(*end, highest purged address)
 */
static void __purge_vmap_area_lazy(unsigned long *start, unsigned long *end,
                                        int sync, int force_flush)
{
        static DEFINE_SPINLOCK(purge_lock);
        LIST_HEAD(valist);
        struct vmap_area *va;
        struct vmap_area *n_va;
        int nr = 0;

        /*
         * If sync is 0 but force_flush is 1, we'll go sync anyway but callers
         * should not expect such behaviour. This just simplifies locking for
         * the case that isn't actually used at the moment anyway.
         */
        if (!sync && !force_flush) {
                if (!spin_trylock(&purge_lock))
                        return;
        } else
                spin_lock(&purge_lock);

        if (sync)
                purge_fragmented_blocks_allcpus();

        rcu_read_lock();
        list_for_each_entry_rcu(va, &vmap_area_list, list) {
                if (va->flags & VM_LAZY_FREE) {
                        if (va->va_start < *start)
                                *start = va->va_start;
                        if (va->va_end > *end)
                                *end = va->va_end;
                        nr += (va->va_end - va->va_start) >> PAGE_SHIFT;
                        list_add_tail(&va->purge_list, &valist);
                        va->flags |= VM_LAZY_FREEING;
                        va->flags &= ~VM_LAZY_FREE;
                }
        }
        rcu_read_unlock();

        if (nr)
                atomic_sub(nr, &vmap_lazy_nr);

        if (nr || force_flush)
                flush_tlb_kernel_range(*start, *end);

        if (nr) {
                spin_lock(&vmap_area_lock);
                list_for_each_entry_safe(va, n_va, &valist, purge_list)
                        __free_vmap_area(va);
                spin_unlock(&vmap_area_lock);
        }
        spin_unlock(&purge_lock);
}

요청 가상 주소 범위에 있는 모든 lazy free되어 있는 vmap_area 들을 찾아서 해제한다.

  • if (!sync && !force_flush) { if (!spin_trylock(&purge_lock)) return; } else spin_lock(&purge_lock);
    • purge용 lock을 획득 한다. sync와 force_flush가 설정되지 않은 경우 lock 획득이 실패하면 purge 처리를 포기한다.
  • if (sync) purge_fragmented_blocks_allcpus();
    • possible cpu 수 만큼 루프를 돌며 각 cpu에서 lazy 처리를 위해 삭제되지 않고 대기하는 vmap_block들을 모두 삭제한다.
  • list_for_each_entry_rcu(va, &vmap_area_list, list) {
    • vmap_area_list에 등록된 vmap_area 정보들 수 만큼 루프를 돈다.
  • if (va->flags & VM_LAZY_FREE) { if (va->va_start < *start) *start = va->va_start; if (va->va_end > *end) *end = va->va_end; nr += (va->va_end – va->va_start) >> PAGE_SHIFT; list_add_tail(&va->purge_list, &valist); va->flags |= VM_LAZY_FREEING; va->flags &= ~VM_LAZY_FREE; }
    • VM_LAZY_FREE가 설정된 엔트리들에서 요청 범위에 있는 엔트리들을 일단 임시 리스트인 valist에 옮기고 VM_LAZY_FREE 를 제거하고 VM_LAZY_FREEING으로 설정한다.
  • if (nr) atomic_sub(nr, &vmap_lazy_nr);
    • 처리할 페이지 수만큼을 vmap_lazy_nr에서 감소시킨다.
  • if (nr || force_flush) flush_tlb_kernel_range(*start, *end);
    • force_flush가 설정되었거나 처리할 페이지 수가 있는 경우 해당 범위의 TLB 엔트리들을 flush한다.
  • if (nr) { spin_lock(&vmap_area_lock); list_for_each_entry_safe(va, n_va, &valist, purge_list) __free_vmap_area(va); spin_unlock(&vmap_area_lock); }
    • valist에 연결되어 있는 엔트리들을 모두 해제한다.

 

__free_vmap_area()

mm/vmalloc.c

static void __free_vmap_area(struct vmap_area *va)
{
        BUG_ON(RB_EMPTY_NODE(&va->rb_node));

        if (free_vmap_cache) {
                if (va->va_end < cached_vstart) {
                        free_vmap_cache = NULL;
                } else {
                        struct vmap_area *cache;
                        cache = rb_entry(free_vmap_cache, struct vmap_area, rb_node);
                        if (va->va_start <= cache->va_start) {
                                free_vmap_cache = rb_prev(&va->rb_node);
                                /*
                                 * We don't try to update cached_hole_size or
                                 * cached_align, but it won't go very wrong.
                                 */
                        }
                }
        }
        rb_erase(&va->rb_node, &vmap_area_root);
        RB_CLEAR_NODE(&va->rb_node);
        list_del_rcu(&va->list);

        /*
         * Track the highest possible candidate for pcpu area
         * allocation.  Areas outside of vmalloc area can be returned
         * here too, consider only end addresses which fall inside
         * vmalloc area proper.
         */
        if (va->va_end > VMALLOC_START && va->va_end <= VMALLOC_END)
                vmap_area_pcpu_hole = max(vmap_area_pcpu_hole, va->va_end);

        kfree_rcu(va, rcu_head);
}

요청한 vmap_area를 RB 트리 vmap_area_root와 vmap_area_list에서 제거한 후 해제한다.

  • if (free_vmap_cache) { if (va->va_end < cached_vstart) { free_vmap_cache = NULL;
    • free_vmap_cache가 지정된 경우 그 영역 아래에 요청 영역이 위치하는 경우 free_vmap_cache에 null을 대입한다.
    • free_vmap_cache는 vmap_area_lock에 의해 보호된다.
  • } else { struct vmap_area *cache; cache = rb_entry(free_vmap_cache, struct vmap_area, rb_node); if (va->va_start <= cache->va_start) { free_vmap_cache = rb_prev(&va->rb_node); } }
    • free_vmap_cache에 등록된 엔트리의 시작 주소 이하에 요청 영역이 있는 경우 free_vmap_cache에 그 이전 노드를 대입한다.
  • rb_erase(&va->rb_node, &vmap_area_root); RB_CLEAR_NODE(&va->rb_node); list_del_rcu(&va->list);
    • RB 트리 vmap_area_root와 vmap_area_list에서 해당 vmap_area를 제거한다.
  • if (va->va_end > VMALLOC_START && va->va_end <= VMALLOC_END) vmap_area_pcpu_hole = max(vmap_area_pcpu_hole, va->va_end);
    • 요청 가상 주소 범위가 VMALLOC 영역 이내에 있는 경우 vmap_area_pcpu_hole 보다 요청 가상 끝 주소가 요청 가상 끝 주소를 대입한다.
  • kfree_rcu(va, rcu_head);
    • rcu의 grace period 이후에 kfree를 통해 object가 해제되도록 한다.

 

purge_fragmented_blocks_allcpus()

mm/vmalloc.c

static void purge_fragmented_blocks_allcpus(void)
{
        int cpu;

        for_each_possible_cpu(cpu)
                purge_fragmented_blocks(cpu);
}

possible cpu 수 만큼 루프를 돌며 각 cpu에서 lazy 처리를 위해 삭제되지 않고 대기하는 vmap_block들을 모두 삭제한다.

 

purge_fragmented_blocks()

mm/vmalloc.c

static void purge_fragmented_blocks(int cpu)
{
        LIST_HEAD(purge);
        struct vmap_block *vb;
        struct vmap_block *n_vb;
        struct vmap_block_queue *vbq = &per_cpu(vmap_block_queue, cpu);

        rcu_read_lock();
        list_for_each_entry_rcu(vb, &vbq->free, free_list) {

                if (!(vb->free + vb->dirty == VMAP_BBMAP_BITS && vb->dirty != VMAP_BBMAP_BITS))
                        continue;

                spin_lock(&vb->lock);
                if (vb->free + vb->dirty == VMAP_BBMAP_BITS && vb->dirty != VMAP_BBMAP_BITS) {
                        vb->free = 0; /* prevent further allocs after releasing lock */
                        vb->dirty = VMAP_BBMAP_BITS; /* prevent purging it again */
                        bitmap_fill(vb->dirty_map, VMAP_BBMAP_BITS);
                        spin_lock(&vbq->lock);
                        list_del_rcu(&vb->free_list);
                        spin_unlock(&vbq->lock);
                        spin_unlock(&vb->lock);
                        list_add_tail(&vb->purge, &purge);
                } else
                        spin_unlock(&vb->lock);
        }
        rcu_read_unlock();

        list_for_each_entry_safe(vb, n_vb, &purge, purge) {
                list_del(&vb->purge);
                free_vmap_block(vb);
        }
}

요청한 cpu에서 lazy 처리를 위해 삭제되지 않고 대기하는 vmap_block들을 모두 삭제한다.

  • vmap() 함수 대신 vm_map_ram()을 사용한 경우 per-cpu vmaps 기반의 lazy-TLB flush를 구현하였는데 이를 통해 더 빠른 처리를 가능하게 하였다.
  • vmap()과 vunmap()을 확장하여 대체하는 새로운 인터페이스는 vm_map_ram()과 vm_unmap_ram() 함수이다.
  • 현재 커널에서 vmalloc() 함수는 vm_map_ram()을  사용하지 않고 vmap()을 사용하고 있다.
    • 따라서 처리 속도는 빠르나 아직 자주 이용되지 않는 상황이다.

 

  • struct vmap_block_queue *vbq = &per_cpu(vmap_block_queue, cpu);
    • 현재 cpu의 vmap_block_queue 정보
  • list_for_each_entry_rcu(vb, &vbq->free, free_list) {
    • vmap_block_queue에 있는 의 free_list의 엔트리 수 만큼 루프를 돈다.
  • if (!(vb->free + vb->dirty == VMAP_BBMAP_BITS && vb->dirty != VMAP_BBMAP_BITS)) continue;
    • vmap_block의 free + dirty가 VMAP_BBMAP_BITS와 다르거나 dirty가 VMAP_BBMAP_BITS와 같은 경우 skip 한다.
    • VMALLOC_SPACE
      • VMALLOC_END – VMALLOC_START와 같이 계산하지 않고 32bit 시스템의 경우 대략 128M로 계산하고  64bit 시스템에서는 128G로 계산한다.
    • VMALLOC_PAGES
      • VMALLOC 페이지 수
        • rpi2: 0x8000
    • VMAP_BBMAP_BITS
      • VMALLOC 페이지 수 / NR_CPUS를 2의 차수 단위로 round up한 수 / 16하여 32 ~ 1024 범위로 제한한 값
        • rpi2: 0x200
  • spin_lock(&vb->lock); if (vb->free + vb->dirty == VMAP_BBMAP_BITS && vb->dirty != VMAP_BBMAP_BITS) {
    • 이 번엔 lock을 걸고 다시 한 번 위에서 한 조건의 반대인 경우
  • vb->free = 0; vb->dirty = VMAP_BBMAP_BITS;
    • vmap_block의 free에 0을 대입하고 dirty에 VMAP_BBMAP_BITS를 대입하여 purging이 다시 처리되지 않도록 막는다.
  • bitmap_fill(vb->dirty_map, VMAP_BBMAP_BITS); spin_lock(&vbq->lock); list_del_rcu(&vb->free_list); spin_unlock(&vbq->lock); spin_unlock(&vb->lock); list_add_tail(&vb->purge, &purge);
    • vmap_block의 dirty_map에 VMAP_BBMAP_BITS만큼 설정하고 free_list에서 제거한 후 임시 리스트인 purge로 옮긴다.
  • list_for_each_entry_safe(vb, n_vb, &purge, purge) { list_del(&vb->purge); free_vmap_block(vb); }
    • 마지막으로 로컬 리스트인 purge에 담긴 vmap_block을 purge 리스트에서 제거하고 free 시킨다.

 

free_vmap_block()

mm/vmalloc.c

static void free_vmap_block(struct vmap_block *vb)
{
        struct vmap_block *tmp;
        unsigned long vb_idx;

        vb_idx = addr_to_vb_idx(vb->va->va_start);
        spin_lock(&vmap_block_tree_lock);
        tmp = radix_tree_delete(&vmap_block_tree, vb_idx);
        spin_unlock(&vmap_block_tree_lock);
        BUG_ON(tmp != vb);

        free_vmap_area_noflush(vb->va);
        kfree_rcu(vb, rcu_head);
}

vmap_block을 해제한다.

  • radix 트리 vmap_block_tree에서 vb_idx를 제거하고 vmap_block의 vmap_area 및 vmap_block을 해제한다.

 

addr_to_vb_idx()

mm/vmalloc.c

/*
 * We should probably have a fallback mechanism to allocate virtual memory
 * out of partially filled vmap blocks. However vmap block sizing should be
 * fairly reasonable according to the vmalloc size, so it shouldn't be a
 * big problem.
 */

static unsigned long addr_to_vb_idx(unsigned long addr)
{
        addr -= VMALLOC_START & ~(VMAP_BLOCK_SIZE-1);
        addr /= VMAP_BLOCK_SIZE;
        return addr;
}

가상 주소를 vmap_block의 인덱스 값으로 변환한다.

  • 가상 주소를 32M 단위로 round down 한 후 32M 단위로 나눈 수를 반환한다.
    • VMAP_BLOCK_SIZE
      • rpi2: 0x200000 (32M)
  • 예) VMALLOC_START가 0xf000_0000인 경우
    • 0~3까지의 인덱스 번호를 리턴한다.

 

참고

답글 남기기

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