Vmap

 

Vmap

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

  • 내부적으로는 VMALLOC address space 범위의 빈 공간 검색을 위해 RB 트리와 리스트를 사용한다.
    • vmap_area_root RB 트리
    • vmap_area_list 리스트
    • free_vmap_cache
      • 빈 공간에 대한 검색을 빠르게 하기 위해 가장 최근에 등록하여 사용한 vma 또는 가장 최근에 free 한 vma의 이전(prev) vma 이 보관된다.
      • 관련 전역 변수 사용
        • cached_hole_size
        • cached_vstart
        • cached_align
        • vmap_area_pcpu_hole (초기값은 VMALLOC_END)

vmap을 사용하여 다음 2가지의 주소 공간에 메모리를 할당하는 api는 다음과 같다.

  • vmalloc()
    • 요청 size 만큼의 페이지를 할당하여 vmalloc address space 공간에 매핑
  • module_alloc()
    • 요청 size 만큼의 페이지를 할당하여 module address space 공간에 매핑

 

Lazy TLB Flushing(Free)

  • vunmap()을 수행하면 다음과 같은 처리항목이 수행되어야 하는데 즉각 처리되는 항목과 나중에 모아 처리할 항목을 분류한다.
    • 즉각 처리
      • 페이지 테이블에서 매핑 해제
      • 캐시 flush
    • 지연 처리
      • RB tree 및 리스트에서 vmap_area의 제거
      • 모든 cpu의 TLB flush
        • cpu가 많은 시스템에서 IPI(Inter Process Interrupt) 콜을 사용하면 각 cpu로 인터럽트가 발생되어 처리 성능을 떨어뜨리는 요인이 된다.
  • 삭제할 vma를 처리하지 않고 놔두었다가 일정량을 초과하거나 메모리 부족 시 한꺼번에 purge 처리하여 처리 성능을 높인다.
    • 이러한  해지를 유보하는 방법을 사용하여 vmap_area의 관리를 약 20배 이상 빠르게 처리를 하는 구현을 적용하였다.
  • lazy TLB free 상태를 표현하는 플래그 비트는 다음과 같다.
    • VM_LAZY_FREE (0x1)
      • 삭제 요청된 상태
    • VM_LAZY_FREEING (0x2)
      • purge 진행중인 상태
    • VM_VM_AREA (0x4)
      • 할당 상태

 

Vmap() 대체 API

  • vmap() 대체 api로 per-cpu map 기반의 lazy TLB flushing을 적용한 vm_map_ram() & vm_unmap_ram()이 준비되어 있다.
  • 이 API는 아직 많은 드라이버에 적용되어 사용하지는 않고 일부 드라이버들 에서만 사용하고 있다.

 

다음 그림은 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();
    • CONFIG_PREEMPT_VOLUNTARY 커널 옵션을 사용하는 경우 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 정보를 구성해온다.

 

__get_vm_area_node()

mm/vmalloc.c

static struct vm_struct *__get_vm_area_node(unsigned long size,
                unsigned long align, unsigned long flags, unsigned long start,
                unsigned long end, int node, gfp_t gfp_mask, const void *caller)
{
        struct vmap_area *va;
        struct vm_struct *area;

        BUG_ON(in_interrupt());
        if (flags & VM_IOREMAP)
                align = 1ul << clamp(fls(size), PAGE_SHIFT, IOREMAP_MAX_ORDER);

        size = PAGE_ALIGN(size);
        if (unlikely(!size))
                return NULL;

        area = kzalloc_node(sizeof(*area), gfp_mask & GFP_RECLAIM_MASK, node);
        if (unlikely(!area))
                return NULL;

        if (!(flags & VM_NO_GUARD))
                size += PAGE_SIZE;

        va = alloc_vmap_area(size, align, start, end, node, gfp_mask);
        if (IS_ERR(va)) {
                kfree(area);
                return NULL;
        }

        setup_vmalloc_vm(area, va, flags, caller);

        return area;
}

요청 가상 주소 범위에서 요청 size가 들어갈 수 있는 빈 자리를 찾아 그 가상 주소로 vmap_area와 vm_struct 정보를 구성하여 반환한다.

  • if (flags & VM_IOREMAP) align = 1ul << clamp(fls(size), PAGE_SHIFT, IOREMAP_MAX_ORDER);
    • io 리매핑이 요청된 경우 size를 가까운 2의 차수 단위로 align 값을 정하되, 그 값은 2 ^ (PAGE_SHIFT(12) ~ 24) 범위내에서 정한다.
      • arm: 4K ~ 16M
  • size = PAGE_ALIGN(size); if (unlikely(!size)) return NULL;
    • size를 페이지 단위로 round up하고 size가 지정되지 않은 경우 null을 반환한다.
  • area = kzalloc_node(sizeof(*area), gfp_mask & GFP_RECLAIM_MASK, node); if (unlikely(!area)) return NULL;
    • vm_struct 구조체를 구성하기 위해 그 크기만큼 할당받는다.
  • if (!(flags & VM_NO_GUARD)) size += PAGE_SIZE;
    • no guard 플래그를 요청하지 않은 경우 가드용으로 1 페이지를 확보하기 위해 size에 1 페이지를 추가한다.
  • va = alloc_vmap_area(size, align, start, end, node, gfp_mask); if (IS_ERR(va)) { kfree(area); return NULL; }
    • 요청한 가상 주소 범위에서 빈 매핑 공간을 찾아 vmap_area를 할당 구성하고 RB트리 및 리스트에 insert한 후 엔트리 정보를 반환한다.
  • setup_vmalloc_vm(area, va, flags, caller);
    • vm_struct 및 vmap_area에 정보를 설정한다.

 

vmap_area 할당

alloc_vmap_area()

mm/vmalloc.c

/*
 * Allocate a region of KVA of the specified size and alignment, within the
 * vstart and vend.
 */
static struct vmap_area *alloc_vmap_area(unsigned long size,
                                unsigned long align,
                                unsigned long vstart, unsigned long vend,
                                int node, gfp_t gfp_mask)
{
        struct vmap_area *va;
        struct rb_node *n;
        unsigned long addr;
        int purged = 0;
        struct vmap_area *first;

        BUG_ON(!size);
        BUG_ON(size & ~PAGE_MASK);
        BUG_ON(!is_power_of_2(align));

        va = kmalloc_node(sizeof(struct vmap_area),
                        gfp_mask & GFP_RECLAIM_MASK, node);
        if (unlikely(!va))
                return ERR_PTR(-ENOMEM);

        /*
         * Only scan the relevant parts containing pointers to other objects
         * to avoid false negatives.
         */
        kmemleak_scan_area(&va->rb_node, SIZE_MAX, gfp_mask & GFP_RECLAIM_MASK);

retry:
        spin_lock(&vmap_area_lock);
        /*
         * Invalidate cache if we have more permissive parameters.
         * cached_hole_size notes the largest hole noticed _below_
         * the vmap_area cached in free_vmap_cache: if size fits
         * into that hole, we want to scan from vstart to reuse
         * the hole instead of allocating above free_vmap_cache.
         * Note that __free_vmap_area may update free_vmap_cache
         * without updating cached_hole_size or cached_align.
         */
        if (!free_vmap_cache ||
                        size < cached_hole_size ||
                        vstart < cached_vstart ||
                        align < cached_align) {
nocache:
                cached_hole_size = 0;
                free_vmap_cache = NULL;
        }
        /* record if we encounter less permissive parameters */
        cached_vstart = vstart;
        cached_align = align;

요청한 가상 주소 범위에서 빈 매핑 공간을 찾아 vmap_area를 할당 구성하고 RB트리 및 리스트에 insert한 후 엔트리 정보를 반환한다.

  • vmap 캐시에서 먼저 검색하여 재사용할 수 있는지 확인한다.
    • 최종 등록한 vm 또는 최종 free한 vm의 이전(prev)vm부터 검색하면 빠른 성공을 기대할 수 있다.
  • 캐시에서 찾지 못한 경우 RB 트리로 구성된 전역 vmap_area_root에서 요청 시작 범위 바로 위에 있는 엔트리를 찾고
  • 이어서 리스트로 구성한 vmap_area_list에서 빈 공간을 찾는다.
  • 찾은 빈 공간에 별도로 메모리 할당 받은 vmap_area를 구성하고 insert 한다.
  • 만일 한 번의 검색에서 공간을 찾지 못하는 경우 해지를 유보(lazy)한 vmap_area를 flush한 후 다시 검색하여 공간을 찾는다.

 

  • va = kmalloc_node(sizeof(struct vmap_area), gfp_mask & GFP_RECLAIM_MASK, node); if (unlikely(!va)) return ERR_PTR(-ENOMEM);
    • vmap_area 구조체를 구성하기 위해 reclaim 관련 플래그만 사용하여 할당을 받고 할당 에러인 경우 -ENOMEM을 반환한다.
  • retry: spin_lock(&vmap_area_lock); if (!free_vmap_cache || size < cached_hole_size || vstart < cached_vstart || align < cached_align) {
    • spin-lock을 얻고 캐시된 노드 위치를 사용할 수  없는 조건인 경우 이 번 검색에 캐시를 사용하지 못하게 한다. 조건은 다음과 같다.
      • 캐시 바로 이전(prev) 공간에 있는 hole이 새로 요청하는 size를 커버할 수 있는 경우
      • 시작 요청 범위가 캐시 사용 시의 요청 범위보다 작은 경우
      • 요청 align 값이 캐시된 align 값 보다 작은 경우

 

        /* find starting point for our search */
        if (free_vmap_cache) {
                first = rb_entry(free_vmap_cache, struct vmap_area, rb_node);
                addr = ALIGN(first->va_end, align);
                if (addr < vstart)
                        goto nocache;
                if (addr + size < addr)
                        goto overflow;

        } else {
                addr = ALIGN(vstart, align);
                if (addr + size < addr)
                        goto overflow;

                n = vmap_area_root.rb_node;
                first = NULL;

                while (n) {
                        struct vmap_area *tmp;
                        tmp = rb_entry(n, struct vmap_area, rb_node);
                        if (tmp->va_end >= addr) {
                                first = tmp;
                                if (tmp->va_start <= addr)
                                        break;
                                n = n->rb_left;
                        } else
                                n = n->rb_right;
                }

                if (!first)
                        goto found;
        }

        /* from the starting point, walk areas until a suitable hole is found */
        while (addr + size > first->va_start && addr + size <= vend) {
                if (addr + cached_hole_size < first->va_start)
                        cached_hole_size = first->va_start - addr;
                addr = ALIGN(first->va_end, align);
                if (addr + size < addr)
                        goto overflow;

                if (list_is_last(&first->list, &vmap_area_list))
                        goto found;

                first = list_entry(first->list.next,
                                struct vmap_area, list);
        }

전역 free_vmap_cache가 가리키는 rb 노드를 first에 대입하고, 그 노드의 끝 주소를 addr에 대입한다. (여기서 부터 검색)

  • if (free_vmap_cache) { first = rb_entry(free_vmap_cache, struct vmap_area, rb_node);
    • 최종 등록하였거나 최근 free 시킨 va(vmap_area) 이전(prev) va를 보관한 free_vmap_cache에서 vm 엔트리를 가져온다.
  • addr = ALIGN(first->va_end, align); if (addr < vstart) goto nocache; if (addr + size < addr) goto overflow;
    • 만일 first 엔트리의 끝 주소가 요청 범위를 벗어난 경우 캐시를 사용하지 않게 하기 위해 nocache 레이블로 이동한다.
    • 또한 first 엔트리의 끝 주소에 size를 더해서 범위를 초과한 경우 overflow 레이블로 이동한다.

캐시에 없는 경우 전역 vmap_area_root RB 트리를 통해 요청 범위에서 가장 첫 va를 first에 대입한다.

  • } else { addr = ALIGN(vstart, align); if (addr + size < addr) goto overflow;
    • free_vmap_cache를 사용할 수 없는 경우 first 엔트리의 끝 주소에 size를 더해서 범위를 초과한 경우 overflow 레이블로 이동한다.
  • n = vmap_area_root.rb_node; first = NULL;
    • n에 전역 vmap_area_root의 첫 루트 노드를 대입한다.
  • while (n) { struct vmap_area *tmp; tmp = rb_entry(n, struct vmap_area, rb_node); if (tmp->va_end >= addr) { first = tmp; if (tmp->va_start <= addr) break; n = n->rb_left; } else
    n = n->rb_right; }

    • leaf 노드를 찾는다.
  • if (!first) goto found;
    • leaf 노드를 찾은 경우 found 레이블로 이동한다.

전역 first va(vmap_area)부터 리스트의 끝까지 요청 범위 내에서 size가 들어갈 수 있는 빈 공간을 찾는다.

  • while (addr + size > first->va_start && addr + size <= vend) {
    • 범위가 끝 날때까지 루프를 돈다.
  • if (addr + cached_hole_size < first->va_start) cached_hole_size = first->va_start – addr;
    • 현재 엔트리 이전에 공간이 비게되는 경우 cached_hole_size에 그 공간을 기억해둔다.
  • addr = ALIGN(first->va_end, align); if (addr + size < addr) goto overflow;
    • 현재 엔트리의 끝 주소 + size가 범위를 초과한 경우 overflow 레이블로 이동한다.
  • if (list_is_last(&first->list, &vmap_area_list)) goto found;
    • 현재 엔트리가 마지막 엔트리인 경우 found 레이블로 이동하고 그렇지 않은 경우 계속 루프를 수행한다.

 

found:
        if (addr + size > vend)
                goto overflow;

        va->va_start = addr;
        va->va_end = addr + size;
        va->flags = 0;
        __insert_vmap_area(va);
        free_vmap_cache = &va->rb_node;
        spin_unlock(&vmap_area_lock);

        BUG_ON(va->va_start & (align-1));
        BUG_ON(va->va_start < vstart);
        BUG_ON(va->va_end > vend);

        return va;

overflow:
        spin_unlock(&vmap_area_lock);
        if (!purged) {
                purge_vmap_area_lazy();
                purged = 1;
                goto retry;
        }
        if (printk_ratelimit())
                pr_warn("vmap allocation for size %lu failed: "
                        "use vmalloc=<size> to increase size.\n", size);
        kfree(va);
        return ERR_PTR(-EBUSY);
}

적절한 공간을 찾은 경우 RB 트리 및 리스트에 insert 하고 만일 요청 범위내에서 빈 공간을 찾을 수 없는 경우 lazy되어 캐시되어 있는 free 엔트리들을 삭제한 후 한 번만 다시 시도한다.

  • found: if (addr + size > vend) goto overflow;
    • 범위를 벗어난 경우 overflow 레이블로 이동한다.
  • va->va_start = addr; va->va_end = addr + size; va->flags = 0; __insert_vmap_area(va);
    • vmap area 엔트리를 구성하고 전역 vmap_area_list RB 트리 및 vmap_area_list에 끼워 넣는다.
  • free_vmap_cache = &va->rb_node; spin_unlock(&vmap_area_lock);
    • 방금 처리한 엔트리를 free_vmap_cache에 대입하고 사용한 lock을 풀고 vmap area 엔트리를 반환한다.
  • overflow: spin_unlock(&vmap_area_lock); if (!purged) { purge_vmap_area_lazy(); purged = 1; goto retry; }
    • 범위 내에서 빈 공간을 발견하지 못한 경우 캐시되어 있는 lazy free 엔트리들을 해제하고 다시 한 번만 시도한다.
  • kfree(va); return ERR_PTR(-EBUSY);
    • 매핑 공간을 찾지 못하면 할당 받은 영역을 해제하고 -EBUSY를 반환한다.

 

다음 그림은 매핑을 위해 요청 가상 주소 범위내에서 빈 공간을 찾을 때 먼저 free_vmap_cache 부터 size가 들어갈 빈공간을 검색하는 모습을 보여준다.

 

다음 그림은 매핑을 위해 요청 가상 주소 범위내에서 빈 공간을 찾을 때 RB 트리로 first vmap_area를 찾고 다시 리스트를 사용하여 size가 들어갈 빈공간을 검색하는 모습을 보여준다.

 

다음 그림은 free_vmap_cache와 cached_hole_size의 변화를 보여준다.

  • cached_hole_size는 캐시의 바로 앞 hole의 크기만을 기억한다.

 

vmap_area 추가

__insert_vmap_area()

mm/vmalloc.c

static void __insert_vmap_area(struct vmap_area *va)
{
        struct rb_node **p = &vmap_area_root.rb_node;
        struct rb_node *parent = NULL;
        struct rb_node *tmp;

        while (*p) {
                struct vmap_area *tmp_va;

                parent = *p;
                tmp_va = rb_entry(parent, struct vmap_area, rb_node);
                if (va->va_start < tmp_va->va_end)
                        p = &(*p)->rb_left;
                else if (va->va_end > tmp_va->va_start)
                        p = &(*p)->rb_right;
                else
                        BUG();
        }

        rb_link_node(&va->rb_node, parent, p);
        rb_insert_color(&va->rb_node, &vmap_area_root);

        /* address-sort this list */
        tmp = rb_prev(&va->rb_node);
        if (tmp) {
                struct vmap_area *prev;
                prev = rb_entry(tmp, struct vmap_area, rb_node);
                list_add_rcu(&va->list, &prev->list);
        } else
                list_add_rcu(&va->list, &vmap_area_list);
}

전역 vmap_area_root RB 트리와 전역 vmap_area_list에 vmap_area 엔트리를 insert 한다.

  • while() 문을 통해 vmap_area_root RB 트리에서 insert 할 leaf 노드를 찾는다.
  • rb_link_node() 함수를 사용하여 leaf 노드에 엔트리를 연결하고
  • rb_insert_color()를 통해 RB 트리의 밸런스를 균형있게 맞춘다.
  • 마지막으로 rcu를 사용하여 vmap_area_list에 vmap_area 엔트리를 끼워 넣는다.
    • RB 트리에 insert한 엔트리를 RB 트리를 이용하여 rb_prev()를 사용하는 경우 바로 앞에 있는 노드를 알아내어 리스트 연결에 끼워넣을 수 있다.

 

다음 그림은 vmap_area 엔트리를 추가할 때의 모습을 보여준다.

 

vm_area 매핑

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 정보로 가상 주소 범위를 매핑한다.

 

get_vm_area_size()

include/linux/vmalloc.h

static inline size_t get_vm_area_size(const struct vm_struct *area)
{
        if (!(area->flags & VM_NO_GUARD))
                /* return actual size without guard page */
                return area->size - PAGE_SIZE;
        else
                return area->size;

}

영역이 사용하는 페이지 수를 반환한다.

 

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를 할 필요가 없어서 성능이 크게 개선된다.

 

vm_struct 설정

setup_vmalloc_vm()

mm/vmalloc.c

static void setup_vmalloc_vm(struct vm_struct *vm, struct vmap_area *va,
                              unsigned long flags, const void *caller)
{
        spin_lock(&vmap_area_lock);
        vm->flags = flags;
        vm->addr = (void *)va->va_start;
        vm->size = va->va_end - va->va_start;
        vm->caller = caller;
        va->vm = vm;
        va->flags |= VM_VM_AREA;
        spin_unlock(&vmap_area_lock);
}

vm_struct 및 vmap_area에 정보를 설정한다.

 

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 정보를 해제한다.

 

vmap_area  삭제

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 정보를 알아온다.

  • 메모리 매핑은 해제하는데 vm_area는 VM_VM_AREA 플래그를 삭제하고 VM_LAZY_FREE 플래그를 추가하기만 한다.
  • 실제 삭제는 vmap_lazy_nr 갯수가 일정량을 초과하거나 메모리 부족 시 VM_LAZY_FREE 설정된 vma들을 한꺼번에 purge 처리한다.
    • vmalloc address space의 매핑이 해지되거나 수정되는 경우 모든 core에서 TLB 플러쉬가 발생되어야 하는데 이를 매 번 수행하는 경우 성능이 현저하게 저하되므로 삭제될 항목을 모아 두었다가  한꺼번에 삭제하는 방식을 취한다. 이를 Lazy TLB flushing이라 부른다.

 

  • 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페이지를 감소시킨다.

 

 

vmap_area 검색

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 정보를 찾아온다.

 

vmap_area free -1-

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를 수행한다.

 

vmap_area free -2- 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에 연결되어 있는 엔트리들을 모두 해제한다.

 

vmap_area free -3-

__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 보다 요청 가상 끝 주소가 요청 가상 끝 주소를 대입한다.
    • vmalloc address space의 최상단 부분은 per-cpu의 dynamic chunk가 생성되는 부분이고 최상단 hole의 끝 주소를 기억하기 위해 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까지의 인덱스 번호를 리턴한다.

 

참고

One thought to “Vmap”

답글 남기기

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