본문 바로가기

Linux

Linux의 kmalloc과 vmalloc에 대해서

출처 : http://embedded21.egloos.com/530514


Linux에서 kernel 영역에서의 동적 memory allocation인 kmalloc과 vmalloc에 대해서 알아보도록 하자.

Kernel은 physical memory를 page의 단위로 관리 한다. page의 크기는 architecture에 의해서 좌우 된다. 보통 32-bit x86 기반의 machine에서는 page의 크기가 4096 bytes 단위로 관리 된다. Physical memory 내의 각각의 page는 Linux kernel에서 다음의 구조체로 관리된다.


struct page {
    unsigned long flags;        // page status
    atomic_t _count;             // ref. cnt.
    ...
    void *virtual;                   // explained later on..
};



32-bit x86 system에서는 기본 kernel configuration이 4GB address space를 user process들을 위해서 3GB의 virtual memory space로 쪼개고 1GB를 kernel을 위해서 쪼갠다. 즉, kernel이 조작할 수 있는 최대 메모리 량은 1GB이다. 그러나 실제로는 1GB 보다 작은 896 MB의 메모리가 최대이다. 왜냐하면 128MB는 kernel data structure들에 의해서 사용 되기 때문이다. 이것을 kernel configuration에 의해서 변경이 가능하다만, 만약 user process들을 위한 virtual address space를 줄인다면 application들이 memroy를 확보하는데 어려움을 겪는 상황이 발생할 것이다.



                                            <Default address space split on a 32-bit PC system>
 


1. kmalloc
kmalloc은 Linux kernel space에서 가장 기본적으로 흔하게 사용되는 dynamic memory allocation의 방법이다. MMU에 의해서 virtual address로 translate된 physical address를 return한다. 즉, virtual address와 physical address는 1:1 mapping이다.

위 경우에서, kmalloc은 'Zone normal'에서 연속적인 메모리 영역을 할당한다. 

 kmalloc flag
     GFP_KERNEL
         : kmalloc의 메모리를 할당할 때까지 sleep으로 들어갈 수 있게 허용된다.
    
    GFP_ATOMIC
         : interrupt context code에서 사용된다. 
           interrupt context에서는 sleep and wait이 지원되지 않으므로, 
           즉, GFP_KERNEL보다 GFP_ATOMIC의 성공 확률이 더 떨어진다.
    

2. kzalloc() 
kmalloc은 이전에 사용되었던 곳으로부터의 내용들을 보유하기 때문에 만약 이 영역이 user space로 공개된다면, security risk가 발생할 수 있다. 그래서 zero로 초기화 한 kmalloc을 하고 싶다면, kzalloc()을 사용하는 것이 좋다.


3. vmalloc()
큰 memory buffer를 잡고 싶고, 물리적으로 메모리 영역이 연속적일 필요가 없다면, kmalloc 대신 vmalloc을 사용한다. vmalloc은 불 연속적으로 존재하는 page들을 가지고 새로운 page table을 생성하여 memory를 관리한다. 이렇듯 page table을 관리 해 줘야 하는 overhead가 발생하기 때문에 vmalloc은 kmalloc보다 느리다. 또한 vmalloc은 내부 적으로 GFP_KERNEL을 사용한 kmalloc을 사용하기 때문에 (다른 page 영역에 대해서 수 차례 kmalloc을 호출하게 된다) interrupt context에서는 호출될 수 없다 (interrupt context에서는 sleep을 허용하지 않기 때문이다). 그리고 불연속 공간에 대한 처리를 수행하므로 당연히 DMA 역시 사용할 수 없다.

고 성능의 network driver는 보통 device가 open 상태일 때, 큰 descriptor ring을 사용하기 위해서 vmalloc을 사용한다. kernel은 더 많은 세련된 memory allocation techniques들을 제공한다. 차후 살펴볼 이러한 것들은 look aside buffer들, slab들, 그리고 mempools들을 포함하고 있다.


void *vmalloc(unsigned long size)
{
       return __vmalloc(size, GFP_KERNEL | __GFP_HIGHMEM, PAGE_KERNEL);
}

void *__vmalloc(unsigned long size, gfp_t gfp_mask, pgprot_t prot)
{
 return __vmalloc_node(size, gfp_mask, prot, -1);
}

static void *__vmalloc_node(unsigned long size, gfp_t gfp_mask, pgprot_t prot,
       int node)
{
 struct vm_struct *area;

 size = PAGE_ALIGN(size);
 if (!size || (size >> PAGE_SHIFT) > num_physpages)
  return NULL;

 area = get_vm_area_node(size, VM_ALLOC, node, gfp_mask); <-- 어느 address space에서 memory를 얻어올지 결정
 if (!area)
  return NULL;

 return __vmalloc_area_node(area, gfp_mask, prot, node); <-- 해당 영역을 관리하는 page들에 대해서 할당 flag를 설정한다.
}


아래는 page로서 memory space를 관리하는 방식의 memory allocation의 상세 코드를 보여준다.

코드를 살펴보면, 알 수 있듯이 불연속 공간을 할당하기 때문에, access 시 마다, 공간에 대한 처리가 필요하며 (이로서 느려짐), 더불어 DMA 등은 당연히 사용할 수가 없다. 하지만, kernel space에 fragmentation이 발생 하여 kmalloc을 사용할 수 없는 경우에도, vmalloc은 (전체 가용 size가 현재 할당할 size보다 크다면) 메모리 할당이 가능하다.

void *__vmalloc_area_node(struct vm_struct *area, gfp_t gfp_mask,
    pgprot_t prot, int node)
{
 struct page **pages;
 unsigned int nr_pages, array_size, i;

 nr_pages = (area->size - PAGE_SIZE) >> 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, gfp_mask | __GFP_ZERO,
     PAGE_KERNEL, node);
  area->flags |= VM_VPAGES;
 } else {
  pages = kmalloc_node(array_size,
    (gfp_mask & GFP_RECLAIM_MASK) | __GFP_ZERO,
    node);
 }
 area->pages = pages;
 if (!area->pages) {
  remove_vm_area(area->addr);
  kfree(area);
  return NULL;
 }

 for (i = 0; i < area->nr_pages; i++) {
  if (node < 0)
   area->pages[i] = alloc_page(gfp_mask);
  else
   area->pages[i] = alloc_pages_node(node, gfp_mask, 0);
  if (unlikely(!area->pages[i])) {
   /* Successfully allocated i pages, free them in __vunmap() */
   area->nr_pages = i;
   goto fail;
  }
 }

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

fail:
 vfree(area->addr);
 return NULL;
}