vmalloc()

 

vmalloc()

mm/vmalloc.c

/**
 *      vmalloc  -  allocate virtually contiguous memory
 *      @size:          allocation size
 *      Allocate enough pages to cover @size from the page level
 *      allocator and map them into contiguous kernel virtual space.
 *
 *      For tight control over page level allocator and protection flags
 *      use __vmalloc() instead.
 */
void *vmalloc(unsigned long size)
{
        return __vmalloc_node_flags(size, NUMA_NO_NODE,
                                    GFP_KERNEL | __GFP_HIGHMEM);
}
EXPORT_SYMBOL(vmalloc);

커널을 위해 연속된 가상 메모리를 할당한다. 커널노드 구분 없이 highmem의 이용 가능하도록 __vmalloc_node_flags() 함수를 이어 호출한다.

 

다음 그림은 vmalloc() 함수와 관련 함수들간의 처리 흐름을 보여준다.

vmalloc-1

 

__vmalloc_node_flags()

mm/vmalloc.c

static inline void *__vmalloc_node_flags(unsigned long size,
                                        int node, gfp_t flags)
{
        return __vmalloc_node(size, 1, flags, PAGE_KERNEL,
                                        node, __builtin_return_address(0));
}

커널을 위해 요청 노드에서 연속된 가상 메모리를 할당한다.

 

__vmalloc_node()

mm/vmalloc.c

/**
 *      __vmalloc_node  -  allocate virtually contiguous memory
 *      @size:          allocation size
 *      @align:         desired alignment
 *      @gfp_mask:      flags for the page level allocator
 *      @prot:          protection mask for the allocated pages
 *      @node:          node to use for allocation or NUMA_NO_NODE
 *      @caller:        caller's return address
 *
 *      Allocate enough pages to cover @size from the page level
 *      allocator with @gfp_mask flags.  Map them into contiguous
 *      kernel virtual space, using a pagetable protection of @prot.
 */
static void *__vmalloc_node(unsigned long size, unsigned long align,
                            gfp_t gfp_mask, pgprot_t prot,
                            int node, const void *caller)
{
        return __vmalloc_node_range(size, align, VMALLOC_START, VMALLOC_END,
                                gfp_mask, prot, 0, node, caller);
}

커널을 위해 요청 노드에서 연속된 가상 메모리를 할당하되 가상 주소는 VMALLOC address space의 빈 공간을 사용하여 가상 주소를 매핑하게 한다.

 

__vmalloc_node_range()

mm/vmalloc.c

/**
 *      __vmalloc_node_range  -  allocate virtually contiguous memory
 *      @size:          allocation size
 *      @align:         desired alignment
 *      @start:         vm area range start
 *      @end:           vm area range end
 *      @gfp_mask:      flags for the page level allocator
 *      @prot:          protection mask for the allocated pages
 *      @vm_flags:      additional vm area flags (e.g. %VM_NO_GUARD)
 *      @node:          node to use for allocation or NUMA_NO_NODE
 *      @caller:        caller's return address
 *
 *      Allocate enough pages to cover @size from the page level
 *      allocator with @gfp_mask flags.  Map them into contiguous
 *      kernel virtual space, using a pagetable protection of @prot.
 */
void *__vmalloc_node_range(unsigned long size, unsigned long align,
                        unsigned long start, unsigned long end, gfp_t gfp_mask,
                        pgprot_t prot, unsigned long vm_flags, int node,
                        const void *caller)
{
        struct vm_struct *area;
        void *addr;
        unsigned long real_size = size;

        size = PAGE_ALIGN(size);
        if (!size || (size >> PAGE_SHIFT) > totalram_pages)
                goto fail;

        area = __get_vm_area_node(size, align, VM_ALLOC | VM_UNINITIALIZED |
                                vm_flags, start, end, node, gfp_mask, caller);
        if (!area)
                goto fail;

        addr = __vmalloc_area_node(area, gfp_mask, prot, node);
        if (!addr)
                return NULL;

        /*
         * In this function, newly allocated vm_struct has VM_UNINITIALIZED
         * flag. It means that vm_struct is not fully initialized.
         * Now, it is fully initialized, so remove this flag here.
         */
        clear_vm_uninitialized_flag(area);

        /*
         * A ref_count = 2 is needed because vm_struct allocated in
         * __get_vm_area_node() contains a reference to the virtual address of
         * the vmalloc'ed block.
         */
        kmemleak_alloc(addr, real_size, 2, gfp_mask);

        return addr;

fail:
        warn_alloc_failed(gfp_mask, 0,
                          "vmalloc: allocation failure: %lu bytes\n",
                          real_size);
        return NULL;
}

요청 노드에서 연속된 가상 메모리를 할당하되 가상 주소는 지정된 범위 이내의 빈 공간을 사용한 가상 주소를 매핑하게 한다.

  • size = PAGE_ALIGN(size); if (!size || (size >> PAGE_SHIFT) > totalram_pages) goto fail;
    • 요청 size가 0이거나 전체 메모리보다 큰 경우 fail 레이블을 경유해 null을 반환한다.
  • area = __get_vm_area_node(size, align, VM_ALLOC | VM_UNINITIALIZED | vm_flags, start, end, node, gfp_mask, caller); if (!area) goto fail;
    • 요청 가상 주소 범위에서 요청 size가 들어갈 수 있는 빈 자리를 찾아 그 가상 주소로 vmap_area와 vm_struct 정보를 구성하여 반환한다.
  • addr = __vmalloc_area_node(area, gfp_mask, prot, node); if (!addr) return NULL;
    • vm_struct 정보가 요청하는 가상 주소 영역 만큼 page descriptor 테이블을 할당받고 order 0 페이지들을 요청 수 만큼 할당하여 연결하고  페이지 테이블에 매핑한다.
  • clear_vm_uninitialized_flag(area);
    • vm에 대해 uninitialized 플래그를 클리어하여 vm이 초기화 되었음을 나타낸다.

 

다음 그림은 vmap_area와 vm_struct가 할당받아 구성되고 필요한 물리 페이지 수 만큼 page descriptor 배열을 할당 받고 그 수 만큼 order 0 페이지들을 할당받아 연결하고 매핑되는 모습을 보여준다.

__vmalloc_node_range-1

 

__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를 요청하지 않은 경우 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에 정보를 설정한다.

 

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

  • lazy free 캐시 리스트에서 먼저 검색하여 재사용할 수 있는지 확인한다.
  • 캐시에서 찾지 못한 경우 RB 트리로 구성된 전역 vmap_area_root에서 요청 시작 범위 바로 위에 있는 엔트리를 찾고
  • 이어서 리스트로 구성한 vmap_area_list에서 빈 공간을 찾는다.
  • 찾은 빈 공간에 별도로 메모리 할당 받은 vmap_area를 구성하고 insert 한다.
  • 만일 한 번의 검색에서 공간을 찾지 못하는 경우 해지를 유보(lazy)한 vmap_area를 flush한 후 다시 검색하여 공간을 찾는다.
  • 이러한  해지를 유보하는 방법을 사용하여 vmap_area의 관리를 약 20배 이상 빠르게 처리를 하는 구현을 적용하였다.

 

  • va = kmalloc_node(sizeof(struct vmap_area), gfp_mask & GFP_RECLAIM_MASK, node); if (unlikely(!va)) return ERR_PTR(-ENOMEM);
    • vmap_area 구조체를 구성하기 위해 할당을 받고 할당 에러인 경우 -ENOMEM을 반환한다.
  • retry: spin_lock(&vmap_area_lock); if (!free_vmap_cache || size < cached_hole_size || vstart < cached_vstart || align < cached_align) {
    • vmap_area_lock spin-lock을 얻고 free_vmap_cache가 0이거나 size, vstart, align 등이 각각 cached_hole_size, cached_vstart, cached_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 리스트를 통해 lazy하게 free시킨 vmap_area 엔트리들 중 시작 범위 바로 위에 있는 first 엔트리를 찾는다.

  • if (free_vmap_cache) { first = rb_entry(free_vmap_cache, struct vmap_area, rb_node);
    • 기존 free 시킨 vmap을 보관한 free_vmap_cache에서 처음 엔트리를 가져온다.
    • 참고: mm: vmap area cache
  • 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 트리를 통해 시작 범위 바로 위에 있는 first vmap_area 엔트리를 찾는다.

  • } 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 레이블로 이동한다.

전역 vmap_area_list를 통해 위 루틴에서 찾은 first 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 공간이 비어 있는 경우 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를 반환한다.

 

다음 그림은 매핑을 위해 요청 가상 주소 범위내에서 빈 공간을 찾아 vmap_area 정보를 구성해오는 모습을 보여준다.

alloc_vmap_area-1

 

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에 정보를 설정한다.

 

__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 엔트리를 추가할 때의 모습을 보여준다.

__insert_vmap_area-1

 

__vmalloc_area_node()

mm/vmalloc.c

static void *__vmalloc_area_node(struct vm_struct *area, gfp_t gfp_mask,
                                 pgprot_t prot, int node)
{
        const int order = 0;
        struct page **pages;
        unsigned int nr_pages, array_size, i;
        const gfp_t nested_gfp = (gfp_mask & GFP_RECLAIM_MASK) | __GFP_ZERO;
        const gfp_t alloc_mask = gfp_mask | __GFP_NOWARN;

        nr_pages = get_vm_area_size(area) >> PAGE_SHIFT;
        array_size = (nr_pages * sizeof(struct page *));

        area->nr_pages = nr_pages;
        /* Please note that the recursion is strictly bounded. */
        if (array_size > PAGE_SIZE) {
                pages = __vmalloc_node(array_size, 1, nested_gfp|__GFP_HIGHMEM,
                                PAGE_KERNEL, node, area->caller);
                area->flags |= VM_VPAGES;
        } else {
                pages = kmalloc_node(array_size, nested_gfp, node);
        }
        area->pages = pages;
        if (!area->pages) {
                remove_vm_area(area->addr);
                kfree(area);
                return NULL;
        }

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

                if (node == NUMA_NO_NODE)
                        page = alloc_page(alloc_mask);
                else
                        page = alloc_pages_node(node, alloc_mask, order);

                if (unlikely(!page)) {
                        /* Successfully allocated i pages, free them in __vunmap() */
                        area->nr_pages = i;
                        goto fail;
                }
                area->pages[i] = page;
                if (gfp_mask & __GFP_WAIT)
                        cond_resched();
        }

        if (map_vm_area(area, prot, pages))
                goto fail;
        return area->addr;

fail:
        warn_alloc_failed(gfp_mask, order,
                          "vmalloc: allocation failure, allocated %ld of %ld bytes\n",
                          (area->nr_pages*PAGE_SIZE), area->size);
        vfree(area->addr);
        return NULL;
}

vm_struct 정보가 요청하는 가상 주소 영역 만큼 page descriptor 테이블을 할당받고 order 0 페이지들을 요청 수 만큼 할당하여 연결하고  페이지 테이블에 매핑한다.

  • const gfp_t nested_gfp = (gfp_mask & GFP_RECLAIM_MASK) | __GFP_ZERO;
    • 페이지 회수와 관련된 플래그만 남기고 __GFP_ZERO를 추가한다.
  • const gfp_t alloc_mask = gfp_mask | __GFP_NOWARN;
    • gfp_mask에 __GFP_NOWARN을 추가한다.
  • nr_pages = get_vm_area_size(area) >> PAGE_SHIFT; array_size = (nr_pages * sizeof(struct page *));
    • 영역이 사용하는 페이지 수를 알아오고 만들 page descriptor들이 사용할 배열의 크기를 구한다.
  • area->nr_pages = nr_pages;
    • area에서 사용하는 페이지 수를 기록한다.
  • if (array_size > PAGE_SIZE) { pages = __vmalloc_node(array_size, 1, nested_gfp|__GFP_HIGHMEM, PAGE_KERNEL, node, area->caller); area->flags |= VM_VPAGES;
    • array_size가 1 페이지를 초과하는 경우 해당 노드의 highmem을 포함한 zone에서 array_size 만큼의 공간을 할당받는다.
    • vmalloc() 함수가 진행 중에 nest되어 호출되는 상황이다.
    • area의 플래그 정보에 VM_VPAGES를 설정하여 할당 받은 page descriptor 배열이 있다는 것을 나타낸다.
  • } else {  pages = kmalloc_node(array_size, nested_gfp, node); }
    • array_size가 1페이지 이내인 경우 kmalloc_node() 함수를 사용하여 slub object를 할당받아 page descriptor 배열을 구성하게 한다.
  • area->pages = pages;
    • area가 사용하는 page descriptor 배열을 가리키게 한다.(vm 전용 mem_map)
  • if (!area->pages) { remove_vm_area(area->addr); kfree(area); return NULL; }
    • 페이지 할당이 안된 경우 vmalloc을 포기하고 해제하고  null을 반환한다.
  • for (i = 0; i < area->nr_pages; i++) { struct page *page; if (node == NUMA_NO_NODE) page = alloc_page(alloc_mask); else page = alloc_pages_node(node, alloc_mask, order);
    • 페이지 수 만큼 루프를 돌며 1개의 물리 페이지를 할당 받는다.
  • if (unlikely(!page)) { area->nr_pages = i; goto fail; }
    • 페이지의 할당이 실패한 경우 할당 받은 페이지 수 만큼을 area->nr_pages에 기록하고 fail 레이블로 이동한다.
  • area->pages[i] = page; if (gfp_mask & __GFP_WAIT) cond_resched();
    • page descriptor 배열에 할당 받은 각 페이지를 연결한다. __GFP_WAIT 플래그를 사용한 경우 현재 태스크보다 높은 우선순위의 리스케쥴링 요청이 발생된 경우 sleep한다.
  • if (map_vm_area(area, prot, pages)) goto fail; return area->addr;
    • area의 매핑을 수행 후 그 가상 주소를 반환한다. 만일 실패하는 경우 fail 레이블로 이동한다.

 

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;

}

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

 

clear_vm_uninitialized_flag()

mm/vmalloc.c

static void clear_vm_uninitialized_flag(struct vm_struct *vm)
{
        /*
         * Before removing VM_UNINITIALIZED,
         * we should make sure that vm has proper values.
         * Pair with smp_rmb() in show_numa_info().
         */
        smp_wmb();
        vm->flags &= ~VM_UNINITIALIZED;
}

vm 설정이 완료되었음을 나타내기 위해 VM_UNINITIALIZED 플래그를 제거한다.

 

vfree()

mm/vmalloc.c

/**
 *      vfree  -  release memory allocated by vmalloc()
 *      @addr:          memory base address
 *
 *      Free the virtually continuous memory area starting at @addr, as
 *      obtained from vmalloc(), vmalloc_32() or __vmalloc(). If @addr is
 *      NULL, no operation is performed.
 *
 *      Must not be called in NMI context (strictly speaking, only if we don't
 *      have CONFIG_ARCH_HAVE_NMI_SAFE_CMPXCHG, but making the calling
 *      conventions for vfree() arch-depenedent would be a really bad idea)
 *
 *      NOTE: assumes that the object at *addr has a size >= sizeof(llist_node)
 */
void vfree(const void *addr)
{
        BUG_ON(in_nmi()); 

        kmemleak_free(addr);

        if (!addr)
                return;
        if (unlikely(in_interrupt())) {
                struct vfree_deferred *p = this_cpu_ptr(&vfree_deferred);
                if (llist_add((struct llist_node *)addr, &p->list))
                        schedule_work(&p->wq);
        } else
                __vunmap(addr, 1);
}
EXPORT_SYMBOL(vfree);

vmalloc()에 의해 할당되고 매핑된 연속된 가상 주소 메모리를 해제한다.

  • 인터럽트 핸들러를 통해 호출되는 경우 당장 매핑된 연속된 가상 주소 메모리를 해제할 수 없으므로 지연 처리를 위해 per-cpu vfree_deferred에 추가한 후 워크큐에 등록된 free_work() 함수를 스케쥴 한다.

 

다음 그림은 vfree() 함수와 관련 함수의 흐름을 보여준다.

vfree-1

 

free_work()

mm/vmalloc.c

static void free_work(struct work_struct *w)
{
        struct vfree_deferred *p = container_of(w, struct vfree_deferred, wq);
        struct llist_node *llnode = llist_del_all(&p->list);
        while (llnode) {
                void *p = llnode;
                llnode = llist_next(llnode);
                __vunmap(p, 1);
        }
}

per-cpu vfree_deferred 리스트에 있는 모든 vm(연속된 가상 주소에 매핑된 메모리)들을 해제한다.

 

참고

답글 남기기

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