티스토리 뷰

4.8 kmalloc 과 vmalloc (p487)

4.8.1 kmalloc과 vmalloc 의 특징

구분 kmalloc vmalloc
공통점 커널에서 페이지 단위가 아닌 메모리를 할당  
할당 연속된 물리 메모리를 할당 가상 메모리로 연속적인 것처럼 보이게 함

4.8.2 GFP(Get Free Page) 플래그

  • 할당을 시도할 존을 지정
  • 페이지 MOBILITY와 장소 hint 관련 플래그
  • 워터마크 관련 플래그
  • 페이지 회수 관련 플래그
  • 액션 관련 플래그
  • 복합 GFP 플래그

4.8.3 kmalloc 할당

  • 슬럽을 사용할때
    • 큰 크기나 DMA 요청에 대해서는 버디 시스템으로부터 페이지를 할당 (alloc_pages)
    • 그렇지 않은 경우, kmalloc kmem 캐시를 통해 메모리 할당 (slab_alloc)

4-151 p491 include/linux/slab.h kmalloc

// size 가 상수이면서 SIZE 가 클때, 버디 시스템으로 부터 페이지 할당
    if (__builtin_constant_p(size)) {
        if (size > KMALLOC_MAX_CACHE_SIZE)
            return kmalloc_large(size, flags);
// dma 가 아닌 경우 & 사이즈 작았던 경우 slab alloc

결국 kmem_cache s 구조체를 찾아서 slab_alloc

4-152 p492 mm/slab_common.c kmalloc_order

큰페이지에 대해 __GFP_COMP 를 사용해 여러 페이지를 버디 시스템에서 할당 받는다.
flags |= __GFP_COMP;

page = alloc_kmem_pages(flags, order);
ret = page ? page_address(page) : null

include/linux/mm.h page_address

  • page 구조체로 부터 물리 메모리 페이지의 가상 주소를 획득하기 위함
#if defined(WANT_PAGE_VIRTUAL)
static inline void *page_address(const struct page *page)
{
    return page->virtual;
}
static inline void set_page_address(struct page *page, void *address)
{
    page->virtual = address;
}
#define page_address_init()  do { } while(0)
#endif

#if defined(HASHED_PAGE_VIRTUAL)
void *page_address(const struct page *page);
void set_page_address(struct page *page, void *virtual);
void page_address_init(void);
#endif

#if !defined(HASHED_PAGE_VIRTUAL) && !defined(WANT_PAGE_VIRTUAL)
#define page_address(page) lowmem_page_address(page)
#define set_page_address(page, address)  do { } while(0)
#define page_address_init()  do { } while(0)
#endif

4-153 mm/page_alloc.c alloc_kmem_pages

  • buddy 에서 페이지를 획득해온다.
static inline struct page *
alloc_pages(gfp_t gfp_mask, unsigned int order)
{
    return alloc_pages_current(gfp_mask, order);
}

mm mempolicy.c alloc_pages_current
mm page_alloc.c __alloc_pages_nodemask 

4-154 p493 include/linux/slab.h kmalloc_index

  • kmalloc 시 byte 를 보고 index 를 결정한다. ~64MB 까지 지원
  • size_index 여기의 index 가 됨
slab_common.c

static s8 size_index[24] = {
    3,    /* 8 */
    4,    /* 16 */
    5,    /* 24 */
    5,    /* 32 */
    6,    /* 40 */
    6,    /* 48 */
    6,    /* 56 */
    6,    /* 64 */
    1,    /* 72 */
    1,    /* 80 */
    1,    /* 88 */
    1,    /* 96 */
    7,    /* 104 */
    7,    /* 112 */
    7,    /* 120 */
    7,    /* 128 */
    2,    /* 136 */
    2,    /* 144 */
    2,    /* 152 */
    2,    /* 160 */
    2,    /* 168 */
    2,    /* 176 */
    2,    /* 184 */
    2    /* 192 */
};

static inline int size_index_elem(size_t bytes)
{
    return (bytes - 1) / 8;
}

4-155 p495 mm/slab-common.c kmalloc_slab

  • 192 보다 작은 경우는 테이블에서 index 획득, 그렇지 않은 경우
  • kmalloc_cache[size_index[ 192 보다 작거나 같을때 size_index_elem(size) or 192 초과일 때 fls(size - 1) ]
fls
1024 - 2^10 = 11
2048 - 2^11 = 12

4.8.4 kmallopc 으로 할당한 메모리 해제

4-156 p497 mm/slub.c kfree

4.8.5 vmalloc 초기화

연속된 가상주소 메모리를 할당 받기 위한 메커니즘 초기화

  • vmalloc : 연속된 가상 주소를 갖는 메모리 할당
  • vfree : vmalloc 으로 할당한 메모리 해제
  • vmap : 연속된 가상 주소 매핑
  • vunmap : 가상주소 매핑 해제

4-156 p497 vmalloc_init

  • vmalloc 초기화, 리스트 헤드 만들기와 필드 초기화 정도만 진행한다.
    • per-cpu vmap_block_queue
    • per-cpu vfree_deferred
    • vmlist : 기존 vmlist 에 등록된 vm_struct 를 읽어 vm_area 를 구성한다
  • vmap_block_queue 는 할당이나 free 등 메모리 매핑을 큐로 동작하는 듯하다. 오래 걸리기 떄문에 큐로 지연하는듯 (아직 잘 모름)
#define per_cpu(var, cpu)    (*per_cpu_ptr(&(var), cpu))

struct vfree_deferred {
    struct llist_head list;
    struct work_struct wq;
};

INIT_WORK(&p->wq, free_work);

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

vfree 를 queue 에 담아서 처리하기 위해서 지연시키는 용도

4.8.6 vmalloc 을 사용한 메모리 할당

mm/vmalloc.c vmalloc

void *vmalloc(unsigned long size) 

4-158 p501 mm/vmalloc.c __vmalloc_node_range

  • 요청 노드에서 연속된 가상 메모리를 할당한다. 가상 주소는 지정된 범위 내 빈 공간을 사용한다.
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)

__get_vm_area_node // 들어갈 수 있는 vm_area 획득
__vmalloc_area_node // page 구조체와 page 할당 (4-159)

4-159 p503 mm/vmalloc.c __vmalloc_area_node

  • page 구조체 테이블을 할당 받고, 싱글 페이지를 할당받아서 연결하고 페이지 테이블에 매핑한다.
// page 구조체를 담기 위한 array 사이즈가 page 보다 작다면 슬럽을, 그렇지 않다면 다시 vmalloc 을 불러 (nestesd) 메모리를 확보한다.
    if (array_size > PAGE_SIZE) {
        pages = __vmalloc_node(array_size, 1, nested_gfp|__GFP_HIGHMEM,
                PAGE_KERNEL, node, area->caller);
    } else {
        pages = kmalloc_node(array_size, nested_gfp, node);
    }

// nr_pages 만큼 돌며 싱글 페이지를 획득
for (i = 0; i < area->nr_pages; i++) {

// pgd pud pmd pte 테이블에 매핑
map_vm_area

// 실패시 vfree 호출 4-161

궁금증 : 그러면 buddy 에서 order 로 크게 페이지 획득하는건 kmalloc 에서만 쓰나?

  • gpt : alloc_pages, __get_free_pages 에서도 사용합니다. alloc_pages와 __get_free_pages는 커널 내부에서 다양한 상황에서 사용됩니다. 이들은 메모리 할당 시 더 낮은 수준에서 직접적으로 물리 메모리 페이지를 관리하는 함수로, 특정한 요구 사항이 있는 경우에 사용됩니다.

4.8.7 vmalloc() 으로 할당받은 매모리 해제

4-161 p505 mm/vmallo.c vfree

  • vm_area 를 리스트에서 제거하고, vm_area 가 갖고있던 페이지를 모두 해제
What is non-maskable interrupt (NMI)?
A NMI is a hardware interrupt that is exempt from any interrupt-masking enabled by the operating system (e.g. CentOS 8.5). In nearly every situation, it is in response to [non-recoverable hardware errors](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux_for_real_time/7/html/reference_guide/non-maskable_interrupts).

BUG_ON(in_nmi());

4.9 per-cpu 할당자

4.9.1 개요

변수를 cpu 수만큼 할당
별도의 락을 잡을 필요 없어 성능이 빠르고, 캐시 히트율이 높다

per-cpu 의 주요 특징

태스크 선점 & 인터럽트의 보호는 필요하다.
캐시 객체 & 통계 카운터에서 자주 사용된다.

유닛 : cpu 마다 per-cpu 데이터가 저장되는 공간

static 영역 : 부팅시 결정
reserved 영역 : 모듈에서 사용
dynamic : 함수를 통해 동적으로 추가

1개 유닛이 사용하는 페이지수와 바이트수는 변수로 관리된다. pcpu_unit_pages / pcpu_unit_size

  • cpu->unit 매핑
    • pcpu_unit_map[cpu] ->unit
    • pcpu_unit_offsets[unit index] -> offset (node 안에 unit 이 여러개인것 같다. unit index 는 node 와 관계없이 unique index)
  • unit->cpu 매핑
    • 역으로 unit 도 cpu 에 매핑을 생성
chunk

유닛 전체가 모인 단위

NUMA 시스템에서 1개의 chunk 난 각 노드의 메모리를 사용하여 할당됨 (first chunk)

  • first chunk : static, reserved, dynamic
  • added chunk : dynamic

그러면 노드의 chunk 는 해당 노드에 연결된 cpu 들 각각에 대해 유닛이 하나씩 있겠구나.

struct pcpu_chunk {
  void *base_addr;    /* base address of this chunk */
  ...
}

reserved 영역(모듈) 은 없을 수도 있다.

이에따라 schunk, dchunk 의 매핑이 달라진다. (p514)

댓글