리눅스 커널 메모리 할당과 GFP Flags (kmalloc, GFP_KERNEL, GFP_ATOMIC)

반응형

 

메모리 할당이란?

프로그램을 실행하면서 처리할 데이터, 변수, 그리고 각종 연산에 필요한 정보들은 메모리에 저장되는데, 이 때 시스템에서 해당 데이터가 들어갈 적절한 공간(메모리 영역)을 찾아 할당해준다. 리눅스 커널에서는 이러한 메모리 할당 작업을 효율적으로 관리하기 위해 다양한 메모리 할당 함수와 메커니즘을 제공하는데,  kmalloc, vmalloc, alloc_pages와 같은 함수들이 이러한 역할을 수행한다.

 

1. 메모리 할당이 필요한 이유

  • 자원 효율성: 메모리 공간은 한정되어 있기 때문에, 시스템 자원을 효율적으로 사용하려면 필요한 만큼만 메모리를 할당해야 한다. 할당된 메모리는 프로그램이 실행되는 동안 사용되며, 프로그램이 종료되면 다시 시스템에 반환되어 다른 프로그램이 사용할 수 있도록 해줘야 한다. 그렇지 않으면 메모리 누수(memory leak)가 발생해 시스템 성능이 저하될 수 있다.
  • 안정적인 프로그램 실행 및 성능 최적: 메모리를 할당함으로써 프로그램이 데이터를 안전하게 저장하고 관리할 수 있다. 만약 메모리를 할당하지 않고 데이터를 저장하려고 한다면, 시스템의 다른 중요한 데이터가 손상되거나 프로그램이 오작동할 수 있다. 또한, 필요한 만큼만 메모리를 할당하고, 사용이 끝난 메모리는 바로 해제함으로써 시스템 자원의 낭비를 막을 수 있다. 예를 들어, 너무 많은 메모리를 한 번에 할당하면 시스템 속도가 느려질 수 있기 때문에, 적절한 크기의 메모리를 할당하는 것이 성능 유지에 중요한 역할을 한다.
  • 동적인 데이터 처리: 프로그램이 실행 중일 때 데이터 크기가 변할 수 있는데, 이때 상황에 맞는 메모리를 즉시 할당하고 해제하는 것이 필요하다. 사용자 입력에 따라 처리해야 할 데이터의 크기가 달라질 때마다 메모리를 할당하는 동적 메모리 할당(dynamic memory allocation)을 예로 들 수 있다.

 

2. 리눅스 커널의 메모리 할당 함수

리눅스 커널은 메모리 할당을 위한 여러 가지 API를 제공한다.

  • kmalloc: 작고 연속적인 메모리를 할당할 때 사용한다. ex) 작은 데이터 구조체를 저장할 때 적합
  • vmalloc: 큰 메모리 블록을 할당할 때 사용한다. 물리적으로 연속적일 필요는 없지만, 가상 주소 공간에서는 연속적으로 보인다.
  • alloc_pages: 페이지 단위로 메모리를 직접 할당할 때 사용한다. 주로 저수준의 메모리 관리에 사용된다.
  • kzalloc: kmalloc과 유사하지만 할당된 메모리를 자동으로 0으로 초기화한다. 안전하게 메모리를 사용할 수 있도록 도와준다.

대부분의 메모리 할당 API는 메모리를 어떻게 할당할지를 표현하기 위해 GFP 플래그를 사용한다. GFP는 "get free pages"의 약자로, 메모리 할당 함수의 기본 기능을 의미한다. 대부분의 경우 아래와 같이 GFP 플래그를 이용해 메모리를 할당할 수 있으며, 함수 호출 시 두 번째 인자로 전달한다.

 

kzalloc(<size>, GFP_KERNEL);

 

3. GFP 플래그 - Get Free Page flags

GFP(Get Free Pages) 플래그는 메모리 할당 시 어떤 방식으로 메모리를 확보할지 지정하는 옵션이다. 다양한 GFP 플래그가 있으며, 각 플래그는 메모리 할당의 행동 방식을 결정한다.

  • GFP_KERNEL: 가장 일반적으로 사용되는 플래그로, 커널 데이터 구조체나 캐시 등을 할당할 때 사용한다.
  • GFP_NOWAIT: 메모리를 즉시 할당하려고 시도하며, 실패할 경우 즉시 반환된다. 주로 인터럽트 핸들러와 같은 긴급 상황에서 사용된다.
  • GFP_ATOMIC: 절대적으로 메모리를 할당해야 하는 상황에서 사용한다. 매우 높은 우선순위를 가지며, 메모리 부족 시 할당에 실패할 가능성이 크다.
  • GFP_USER: 사용자 공간에서 호출되는 메모리 할당에 사용한다. 커널이 직접 접근할 수 있는 메모리가 필요할 때 사용한다.

 

4. 메모리 할당 및 해제

#include <linux/slab.h>

size_t size = 1024; // 할당할 메모리 크기
void *ptr = kzalloc(size, GFP_KERNEL);
if (!ptr) {
    // 할당 실패 시 처리
}

kzalloc은 메모리를 할당하고 자동으로 0으로 초기화해주기 때문에 안전하게 사용할 수 있다. 만약 더 큰 메모리가 필요하다면 vmalloc을 사용하면 된다.

 

kfree(ptr);

할당된 메모리는 더 이상 필요하지 않을 때 반드시 해제해주어야 하며, 메모리를 해제하지 않을 경우 메모리 누수가 발생할 수 있다.

  • kfree: kmalloc이나 kzalloc으로 할당한 메모리를 해제할 때 사용
  • vfree: vmalloc으로 할당한 메모리를 해제할 때 사용
  • kmem_cache_free: 슬랩 캐시 할당을 해제할 때 사용

 

5. GFP Flags 직접 확인하기

메모리 할당 함수를 사용할 때 지정하는 플래그를 직접 확인해보았다. 필자의 경우 linux/include/linux/gfp.h 에 GFP 관련 플래그가 정의되어 있다.

 

 /**
  * DOC: Useful GFP flag combinations
  *
  * Useful GFP flag combinations
  * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  *
  * Useful GFP flag combinations that are commonly used. It is recommended
  * that subsystems start with one of these combinations and then set/clear
  * %__GFP_FOO flags as necessary.
  *
  * %GFP_ATOMIC users can not sleep and need the allocation to succeed. A lower
  * watermark is applied to allow access to "atomic reserves"
  *
  * %GFP_KERNEL is typical for kernel-internal allocations. The caller requires
  * %ZONE_NORMAL or a lower zone for direct access but can direct reclaim.
  *
  * %GFP_KERNEL_ACCOUNT is the same as GFP_KERNEL, except the allocation is
  * accounted to kmemcg.
  *
  * %GFP_NOWAIT is for kernel allocations that should not stall for direct
  * reclaim, start physical IO or use any filesystem callback.
  *
  * %GFP_NOIO will use direct reclaim to discard clean pages or slab pages
  * that do not require the starting of any physical IO.
  * Please try to avoid using this flag directly and instead use
  * memalloc_noio_{save,restore} to mark the whole scope which cannot
  * perform any IO with a short explanation why. All allocation requests
  * will inherit GFP_NOIO implicitly.
  *
  * %GFP_NOFS will use direct reclaim but will not use any filesystem interfaces.
  * Please try to avoid using this flag directly and instead use
  * memalloc_nofs_{save,restore} to mark the whole scope which cannot/shouldn't
  * recurse into the FS layer with a short explanation why. All allocation
  * requests will inherit GFP_NOFS implicitly.
  *
  * %GFP_USER is for userspace allocations that also need to be directly
  * accessibly by the kernel or hardware. It is typically used by hardware
  * for buffers that are mapped to userspace (e.g. graphics) that hardware
  * still must DMA to. cpuset limits are enforced for these allocations.
  *
  * %GFP_DMA exists for historical reasons and should be avoided where possible.
  * The flags indicates that the caller requires that the lowest zone be
  * used (%ZONE_DMA or 16M on x86-64). Ideally, this would be removed but
  * it would require careful auditing as some users really require it and
  * others use the flag to avoid lowmem reserves in %ZONE_DMA and treat the
  * lowest zone as a type of emergency reserve.
  *
  * %GFP_DMA32 is similar to %GFP_DMA except that the caller requires a 32-bit
  * address.
  *
  * %GFP_HIGHUSER is for userspace allocations that may be mapped to userspace,
  * do not need to be directly accessible by the kernel but that cannot
  * move once in use. An example may be a hardware allocation that maps
  * data directly into userspace but has no addressing limitations.
  *
  * %GFP_HIGHUSER_MOVABLE is for userspace allocations that the kernel does not
  * need direct access to but can use kmap() when access is required. They
  * are expected to be movable via page reclaim or page migration. Typically,
  * pages on the LRU would also be allocated with %GFP_HIGHUSER_MOVABLE.
 * %GFP_TRANSHUGE and %GFP_TRANSHUGE_LIGHT are used for THP allocations. They
 * are compound allocations that will generally fail quickly if memory is not
 * available and will not wake kswapd/kcompactd on failure. The _LIGHT
 * version does not attempt reclaim/compaction at all and is by default used
 * in page fault path, while the non-light is used by khugepaged.
 */
#define GFP_ATOMIC  (__GFP_HIGH|__GFP_ATOMIC|__GFP_KSWAPD_RECLAIM)
#define GFP_KERNEL  (__GFP_RECLAIM | __GFP_IO | __GFP_FS)
#define GFP_KERNEL_ACCOUNT (GFP_KERNEL | __GFP_ACCOUNT)
#define GFP_NOWAIT  (__GFP_KSWAPD_RECLAIM)
#define GFP_NOIO    (__GFP_RECLAIM)
#define GFP_NOFS    (__GFP_RECLAIM | __GFP_IO)
#define GFP_USER    (__GFP_RECLAIM | __GFP_IO | __GFP_FS | __GFP_HARDWALL)
#define GFP_DMA     __GFP_DMA
#define GFP_DMA32   __GFP_DMA32
#define GFP_HIGHUSER    (GFP_USER | __GFP_HIGHMEM)
#define GFP_HIGHUSER_MOVABLE    (GFP_HIGHUSER | __GFP_MOVABLE)
#define GFP_TRANSHUGE_LIGHT ((GFP_HIGHUSER_MOVABLE | __GFP_COMP | \
             __GFP_NOMEMALLOC | __GFP_NOWARN) & ~__GFP_RECLAIM)
#define GFP_TRANSHUGE   (GFP_TRANSHUGE_LIGHT | __GFP_DIRECT_RECLAIM)

/* Convert GFP flags to their corresponding migrate type */
#define GFP_MOVABLE_MASK (__GFP_RECLAIMABLE|__GFP_MOVABLE)
#define GFP_MOVABLE_SHIFT 3

 

위에서 선언한 매크로를 GFP  플래그로 지정해 malloc() 함수를 호출하면, 커널은 GFP 플래그 옵션에 맞게 동적 메모리를 할당한다.

 

참고: https://www.kernel.org/doc/html/next/core-api/memory-allocation.html#memory-allocatio

 

Memory Allocation Guide — The Linux Kernel documentation

Memory Allocation Guide Linux provides a variety of APIs for memory allocation. You can allocate small chunks using kmalloc or kmem_cache_alloc families, large virtually contiguous areas using vmalloc and its derivatives, or you can directly request pages

www.kernel.org

 

반응형