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를 페이지 단위로 정렬하고 그 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

 

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

 

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(연속된 가상 주소에 매핑된 메모리)들을 해제한다.

 

참고

답글 남기기

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