[Linux Kernel] kvmalloc() 함수

kvmalloc() 함수는 리눅스 커널에서 메모리를 동적으로 할당하는 여러 함수 중 하나이다. 이 함수는 kmalloc()vmalloc()의 장점을 결합하여, 필요한 경우 페이지별로 물리적 메모리를 할당하는 기능을 제공한다. 이 글에서는 kvmalloc() 함수의 사용법과 내부 작동 방식에 대해 알아본다.

kvmalloc() 함수란?

이 함수는 요청한 메모리 크기에 따라 kmalloc() 또는 vmalloc()를 선택적으로 사용하여 효율적으로 메모리를 할당한다.

  • 작은 메모리 할당: kmalloc()를 사용하여 물리적으로 연속된 메모리를 할당한다.
  • 큰 메모리 할당: vmalloc()를 사용하여 가상적으로 연속된 메모리를 할당한다.

kvmalloc() 함수

kvmalloc() 함수는 다음과 같이 define 되어 있다.

void *kvmalloc(size_t size, gfp_t flags);
  • size: 할당할 메모리의 크기 (바이트 단위)
  • flags: 메모리 할당 플래그 (GFP_KERNEL, GFP_ATOMIC 등)

사용 예제

다음은 커널 드라이버 예제인데, kvmalloc() 함수를 사용하여 1KB의 메모리를 할당하고 이를 해제하는 간단한 작업을 수행하는 코드 이다.

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>

static int __init my_module_init(void)
{
    void *ptr;
    size_t size = 1024; // 할당할 메모리 크기 (1KB)

    // kvmalloc()을 사용하여 메모리 할당
    ptr = kvmalloc(size, GFP_KERNEL);
    if (!ptr) {
        pr_err("kvmalloc failed\n");
        return -ENOMEM;
    }

    pr_info("kvmalloc succeeded\n");

    // 할당된 메모리 사용 (예: 초기화)
    memset(ptr, 0, size);

    // 할당된 메모리 해제
    kvfree(ptr);

    return 0;
}

static void __exit my_module_exit(void)
{
    pr_info("Module exit\n");
}

module_init(my_module_init);
module_exit(my_module_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Author");
MODULE_DESCRIPTION("kvmalloc example");

kvmalloc() 함수의 내부 작동 방식

kvmalloc() 선언은 다음과 같다.

#define kvmalloc(_size, _flags)			kvmalloc_node(_size, _flags, NUMA_NO_NODE)
#define kvmalloc_noprof(_size, _flags)		kvmalloc_node_noprof(_size, _flags, NUMA_NO_NODE)
#define kvzalloc(_size, _flags)			kvmalloc(_size, (_flags)|__GFP_ZERO)

#define kvzalloc_node(_size, _flags, _node)	kvmalloc_node(_size, (_flags)|__GFP_ZERO, _node)

실제 kvmalloc() 실행 함수인 kvmalloc_node() 함수이다.

/**
 * kvmalloc_node - attempt to allocate physically contiguous memory, but upon
 * failure, fall back to non-contiguous (vmalloc) allocation.
 * @size: size of the request.
 * @flags: gfp mask for the allocation - must be compatible (superset) with GFP_KERNEL.
 * @node: numa node to allocate from
 *
 * Uses kmalloc to get the memory but if the allocation fails then falls back
 * to the vmalloc allocator. Use kvfree for freeing the memory.
 *
 * Reclaim modifiers - __GFP_NORETRY and __GFP_NOFAIL are not supported. __GFP_REPEAT
 * is supported only for large (>32kB) allocations, and it should be used only if
 * kmalloc is preferable to the vmalloc fallback, due to visible performance drawbacks.
 *
 * Any use of gfp flags outside of GFP_KERNEL should be consulted with mm people.
 */
void *kvmalloc_node(size_t size, gfp_t flags, int node)
{
    gfp_t kmalloc_flags = flags;
    void *ret;

    /*
     * vmalloc uses GFP_KERNEL for some internal allocations (e.g page tables)
     * so the given set of flags has to be compatible.
     */
    WARN_ON_ONCE((flags & GFP_KERNEL) != GFP_KERNEL);

    /*
     * Make sure that larger requests are not too disruptive - no OOM
     * killer and no allocation failure warnings as we have a fallback
     */
    if (size > PAGE_SIZE) {
        kmalloc_flags |= __GFP_NOWARN;

        /*
         * We have to override __GFP_REPEAT by __GFP_NORETRY for !costly
         * requests because there is no other way to tell the allocator
         * that we want to fail rather than retry endlessly.
         */
        if (!(kmalloc_flags & __GFP_REPEAT) ||
                (size <= PAGE_SIZE << PAGE_ALLOC_COSTLY_ORDER))
            kmalloc_flags |= __GFP_NORETRY;
    }

    ret = kmalloc_node(size, kmalloc_flags, node);

    /*
     * It doesn't really make sense to fallback to vmalloc for sub page
     * requests
     */
    if (ret || size <= PAGE_SIZE)
        return ret;

    return __vmalloc_node_flags(size, node, flags);
}
EXPORT_SYMBOL(kvmalloc_node);

kvmalloc_node() 함수는 내부적으로 다음과 같은 단계를 거친다.

  1. 크기 확인: 할당할 메모리 크기를 확인한다.
  2. kmalloc() 시도: 할당할 메모리 크기가 작은 경우, kmalloc()을 사용하여 물리적으로 연속된 메모리를 할당한다.
  3. vmalloc() 사용: 할당할 메모리 크기가 큰 경우, vmalloc()을 사용하여 가상적으로 연속된 메모리를 할당한다.
  4. 메모리 반환: 성공적으로 메모리가 할당되면 포인터를 반환하고, 실패하면 NULL을 반환한다.

무분별한 큰 사이즈를 kmalloc() 을 사용하다보면, 메모리 단편화로 인한 메모리 효율이 떨어져, 이로 인한 부하가 가중되고, 메모리가 있음에도 OOM이 발생할 수 있다. kvmalloc() 함수는 kmalloc()vmalloc()의 장점을 결합하여 작은 메모리와 큰 메모리 할당을 모두 효율적으로 처리할 수 있다.

참고 사이트

답글 남기기