본문 바로가기

Linux Kernel

[Linux Kernel] 메모리 할당

반응형

이번 글에선 디바이스 드라이버 관점에서 메모리 할당에 관하여 다룬다. 메모리 관리 기법, 주소 공간에 대해서는 다루지 않는다 주소와 메모리 공간은 아래 글을 참고하자.

 

[Linux Kernel] 주소와 메모리 공간

이 글은 LDD3을 공부하면서 정리하였고 필요한 내용을 그때그때 추가했다. 사용자 프로세스 관점에서 바라보는 주소 공간과 커널 관점에서 바라보는 것은 매우 다르기 때문에 이를 이해할 필요

hyeyoo.com

심심해서 넣어본 메모리 사진

kmalloc, kfree

/**
 * kmalloc - allocate memory
 * @size: how many bytes of memory are required.
 * @flags: the type of memory to allocate.
 *
 * kmalloc is the normal method of allocating memory
 * for objects smaller than page size in the kernel.
 *
 * The allocated object address is aligned to at least ARCH_KMALLOC_MINALIGN
 * bytes. For @size of power of two bytes, the alignment is also guaranteed
 * to be at least to the size.
 *
 * The @flags argument may be one of the GFP flags defined at
 * include/linux/gfp.h and described at
 * :ref:`Documentation/core-api/mm-api.rst <mm-api-gfp-flags>`
 *
 * The recommended usage of the @flags is described at
 * :ref:`Documentation/core-api/memory-allocation.rst <memory_allocation>`
 */
static __always_inline void *kmalloc(size_t size, gfp_t flags);

/**
 * kfree - free previously allocated memory
 * @objp: pointer returned by kmalloc.
 *
 * If @objp is NULL, no operation is performed.
 *
 * Don't free memory not originally allocated by kmalloc()
 * or you will run into trouble.
 */
void kfree(const void *objp)

kmalloc은 작은 사이즈(페이지보다)의 메모리를 할당할 때 자주 사용하는 함수로 물리적으로 연속된, size 바이트 이상의 메모리를 할당한다. size 바이트 '이상'이라는 것은 더 많은 메모리가 할당될 수 있다는 것을 의미한다. 예를 들어, 20 바이트 짜리 메모리 공간을 요청하면 32 바이트가 할당될 수도 있다. 할당된 메모리는 kfree로 호출해서 반드시 반환해야 한다.

 

kmalloc은 size 말고도 flags라는 인자를 받는데, 이 flags는 메모리를 어떻게 할당받을 건지를 정하는 플래그이다. 커널은 매우 다양한 컨텍스트에서 메모리를 할당받기 때문에 이 플래그를 사용하는데, 예를 들어서 interrupt context나 spinlock을 들고있는 등의 atomic context에서는 GFP_ATOMIC이라는 플래그를 사용해서 kmalloc이 sleep을 하지 못하도록 한다. (대신 실패할 가능성이 더 높아진다.) 하지만 process context에 있는 경우에는 보통 GFP_KERNEL 플래그를 사용하여 kmalloc이 sleep을 할 수 있게 한다. 그 외 플래그의 자세한 설명은 include/linux/slab.h, include/linux/gfp.h, Documentation/core-api/memory-allocation, Documentation/core-api/mm-api.rst에서 찾아볼 수 있다.

 

v5.12 기준 kmalloc은 작은 메모리에 대해서는  2^n 단위로 (8, 16, 32, 64, ..., ) slab을 사용하고, 큰 메모리에 대해서는 alloc_pages를 호출한다.

vmalloc

물리적으로 연속된 메모리를 할당하는 kmalloc과는 달리 vmalloc은 가상 메모리 주소 상에서만 연속적인 메모리가 할당된다. 다시 말해서 물리적으로는 떨어져있지만, 연속된 가상 주소로 맵핑한다. 물론 성능상 vmalloc보단 kmalloc으로 메모리할당하는 게 더 좋다.  대부분의 경우에선 vmalloc보다 kmalloc을 사용한다.

vmalloc, __vmalloc

vmalloc의 사용법은 kmalloc과 같으며, vmalloc은 flags를 받지 않으므로 플래그를 넘겨야 한다면 __vmalloc을 사용해야 한다.

/**
 * 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.
 *
 * Return: pointer to the allocated memory or %NULL on error
 */
void *vmalloc(unsigned long size);

void *__vmalloc(unsigned long size, gfp_t gfp_mask);

vfree, vfree_atomic

free를 해주는 것도 똑같이 vfree를 사용하면 된다. 단, vfree는 atomic context에서 사용할 수 없다. atomic context에선 vfree_atomic 함수를 사용해야 하며, 이것 조차도 NMI context에서는 쓸 수 없다.

/**
 * vfree - Release memory allocated by vmalloc()
 * @addr:  Memory base address
 *
 * Free the virtually continuous memory area starting at @addr, as obtained
 * from one of the vmalloc() family of APIs.  This will usually also free the
 * physical memory underlying the virtual allocation, but that memory is
 * reference counted, so it will not be freed until the last user goes away.
 *
 * If @addr is NULL, no operation is performed.
 *
 * Context:
 * May sleep if called *not* from interrupt context.
 * Must not be called in NMI context (strictly speaking, it could be
 * if we have CONFIG_ARCH_HAVE_NMI_SAFE_CMPXCHG, but making the calling
 * conventions for vfree() arch-depenedent would be a really bad idea).
 */

void vfree(const void *addr);

/**
 * vfree_atomic - release memory allocated by vmalloc()
 * @addr:	  memory base address
 *
 * This one is just like vfree() but can be called in any atomic context
 * except NMIs.
 */
 
 void vfree_atomic(const void *addr);

lookaside cache

같은 크기의 메모리의 할당과 해제를 반복할 때lookaside cache를 사용할 수 있다. 다른 말로는 slab allocator라고도 한다.  예를 들어 task_struct 구조체를 반복적으로 사용하는 경우에는 같은 크기의 메모리만 할당과 해제를 반복하므로, 좀 더 빠르게 할당과 해제를 수행할 수 있다.

kmem_cache_create

/**
 * kmem_cache_create - Create a cache.
 * @name: A string which is used in /proc/slabinfo to identify this cache.
 * @size: The size of objects to be created in this cache.
 * @align: The required alignment for the objects.
 * @flags: SLAB flags
 * @ctor: A constructor for the objects.
 *
 * Cannot be called within a interrupt, but can be interrupted.
 * The @ctor is run when new pages are allocated by the cache.
 *
 * Return: a pointer to the cache on success, NULL on failure.
 */
struct kmem_cache *
kmem_cache_create(const char *name, unsigned int size, unsigned int align,
		slab_flags_t flags, void (*ctor)(void *));

name: 캐시의 이름

size: 구조체의 크기

align: 구조체를 align할 크기

flags: SLAB 플래그

ctor: 캐시 내에 가용한 메모리가 부족해자면 새로 메모리를 할당해야한다. 이 때 새로 할당되는 page들에 대해 ctor (constructor의 줄임말) 함수가 호출된다.

 

flags는 다음의 값들을 OR 연산하여 만들 수 있다.

SLAB_POSION: 값을 a5a5a5... 로 초기화해서 초기화하지 않은 메모리에 접근하는 것을 탐지한다.

SLAB_RED_ZONE: 버퍼 overrun을 막기 위한 'Red zone'

SLAB_HWCACHE_ALIGN: 실제 프로세서의 cache line에 딱 맞게 구조체를 align한다.

/*
 * The flags are
 *
 * %SLAB_POISON - Poison the slab with a known test pattern (a5a5a5a5)
 * to catch references to uninitialised memory.
 *
 * %SLAB_RED_ZONE - Insert `Red` zones around the allocated memory to check
 * for buffer overruns.
 *
 * %SLAB_HWCACHE_ALIGN - Align the objects in this cache to a hardware
 * cacheline.  This can be beneficial if you're counting cycles as closely
 * as davem. */

kmem_cache_alloc

캐시로부터 메모리를 할당받는다.

cachep: 앞에서 kmem_cache_create로 만든 캐시

flags: kmalloc의 flags와 같다.

/**
 * kmem_cache_alloc - Allocate an object
 * @cachep: The cache to allocate from.
 * @flags: See kmalloc().
 *
 * Allocate an object from this cache.  The flags are only relevant
 * if the cache has no available objects.
 *
 * Return: pointer to the new object or %NULL in case of error
 */
void *kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags);

kmem_cache_free

kmem_cache_alloc으로 할당받은 메모리를 반환한다.

/**
 * kmem_cache_free - Deallocate an object
 * @cachep: The cache the allocation was from.
 * @objp: The previously allocated object.
 *
 * Free an object which was previously allocated from this
 * cache.
 */
void kmem_cache_free(struct kmem_cache *cachep, void *objp);

kmem_cache_destroy

kmem_cache_create로 만든 캐시를 제거한다.

void kmem_cache_destroy(struct kmem_cache *);

memory pool

메모리 할당은 항상 실패할 가능성이 존재한다. kmalloc도 그렇고, kmem_cache_alloc도 그렇다. 하지만 상황에 따라서는  메모리 할당을 실패하면 안 되는 경우가 있을 수 있다. 그럴 때는 항상 일정한 양의 가용 메모리를 유지하는 memory pool을 사용한다.

mempool_create

/**
 * mempool_create - create a memory pool
 * @min_nr:    the minimum number of elements guaranteed to be
 *             allocated for this pool.
 * @alloc_fn:  user-defined element-allocation function.
 * @free_fn:   user-defined element-freeing function.
 * @pool_data: optional private data available to the user-defined functions.
 *
 * this function creates and allocates a guaranteed size, preallocated
 * memory pool. The pool can be used from the mempool_alloc() and mempool_free()
 * functions. This function might sleep. Both the alloc_fn() and the free_fn()
 * functions might sleep - as long as the mempool_alloc() function is not called
 * from IRQ contexts.
 *
 * Return: pointer to the created memory pool object or %NULL on error.
 */
mempool_t *mempool_create(int min_nr, mempool_alloc_t *alloc_fn,
				mempool_free_t *free_fn, void *pool_data);

mempool은 min_nr개만큼을 항상 할당할 수 있도록 여유 공간을 유지한다. alloc_fn, free_fn은 mempool 상의 메모리를 할당 / 해제할 때마다 호출되며, alloc_fn과 free_fn의 프로토타입은 다음과 같다. pool_data는 alloc_fn과 free_fn에 인자로 들어간다.

typedef void *(mempool_alloc_t)(int gfp_mask, void *pool_data);
typedef void *(mempool_free_t)(void *element, void *pool_data);

mempool은 slab allocator처럼 독립적으로 작동하지 않는다. mempool을 사용하려면 할당/해제시 사용할 alloc_fn, free_fn을 정의하고, memory pool에서 사용할 자료구조도 pool_data로 정의해야 한다.

 

하지만 mempool을 사용하는 대부분의 사람은 메모리 할당자를 직접 만들려고 하지는 않을 것이다. 따라서 우리는 다음과 같은 방법으로 slab allocator 위에서 동작하는 mempool을 만들 수 있다.

 

mempool_create(MIN_NR, mempool_alloc_slab, mempool_free_slab, my_kmem_cache);

이때 mempool_alloc_slab, mempool_free_slab은 mempool.h에 정의되어있으며, my_kmem_cache는 앞에서 다룬 kmem_cache_create로 생성해야 한다.

mempool_alloc

주어진 mempool에서 메모리를 할당하며,  gfp_mask를 받는다.

/**
 * mempool_alloc - allocate an element from a specific memory pool
 * @pool:      pointer to the memory pool which was allocated via
 *             mempool_create().
 * @gfp_mask:  the usual allocation bitmask.
 *
 * this function only sleeps if the alloc_fn() function sleeps or
 * returns NULL. Note that due to preallocation, this function
 * *never* fails when called from process contexts. (it might
 * fail if called from an IRQ context.)
 * Note: using __GFP_ZERO is not supported.
 *
 * Return: pointer to the allocated element or %NULL on error.
 */
void *mempool_alloc(mempool_t *pool, gfp_t gfp_mask);

mempool_free

mempool_alloc에서 할당한 메모리를 해제한다.

/**
 * mempool_free - return an element to the pool.
 * @element:   pool element pointer.
 * @pool:      pointer to the memory pool which was allocated via
 *             mempool_create().
 *
 * this function only sleeps if the free_fn() function sleeps.
 */
void mempool_free(void *element, mempool_t *pool);

mempool_resize

mempool_create에서 지정한 min_nr을 갱신할 때 사용한다.

/**
 * mempool_resize - resize an existing memory pool
 * @pool:       pointer to the memory pool which was allocated via
 *              mempool_create().
 * @new_min_nr: the new minimum number of elements guaranteed to be
 *              allocated for this pool.
 *
 * This function shrinks/grows the pool. In the case of growing,
 * it cannot be guaranteed that the pool will be grown to the new
 * size immediately, but new mempool_free() calls will refill it.
 * This function may sleep.
 *
 * Note, the caller must guarantee that no mempool_destroy is called
 * while this function is running. mempool_alloc() & mempool_free()
 * might be called (eg. from IRQ contexts) while this function executes.
 *
 * Return: %0 on success, negative error code otherwise.
 */
int mempool_resize(mempool_t *pool, int new_min_nr);

mempool_destroy

mempoool을 제거한다.

/**
 * mempool_destroy - deallocate a memory pool
 * @pool:      pointer to the memory pool which was allocated via
 *             mempool_create().
 *
 * Free all reserved elements in @pool and @pool itself.  This function
 * only sleeps if the free_fn() function sleeps.
 */
void mempool_destroy(mempool_t *pool);

페이지 단위의 메모리 할당

지금까지 살펴본 것들은 페이지보다는 작은, 바이트 단위의 메모리 할당 함수들이다. 커널에서는 페이지 단위로도 메모리를 할당받을 수 있는 함수들을 제공한다. 대부분의 함수는 alloc_pages 함수를 사용하나, 아직은 이에 대해 다루지 않겠다.

__get_free_pages, __get_free_page,  get_zeroed_page

/*
 * Common helper functions. Never use with __GFP_HIGHMEM because the returned
 * address cannot represent highmem pages. Use alloc_pages and then kmap if
 * you need to access high mem.
 */
unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order);

unsigned long get_zeroed_page(gfp_t gfp_mask)
{
	return __get_free_pages(gfp_mask | __GFP_ZERO, 0);
}

#define __get_free_page(gfp_mask) \
		__get_free_pages((gfp_mask), 0)

get_free_pages는 2^(order)개의 page를 할당받는 함수이다. 할당된 페이지는 물리적으로 연속적인 페이지들이며, 실패시 0을 리턴한다. __get_free_page는 페이지 하나를 할당할 때 사용하고, get_zeroed_page는 0으로 초기화된 페이지를 할당할 때 사용한다. 근데 구현부를 보면 알겠지만 결국은 모두 내부적으로 __get_free_pages를 호출한다.

get_order

order를 계산하는 함수이다. order를 하드코딩하는 것보다는 항상 이 함수를 사용하는 게 좋다. 이 함수는 size 바이트를 포함하는 가장 작은 order 값을 반환한다. 위에서 봤듯 살펴봤듯 페이지는 2^(order)개 단위로 할당한다.

 

예를 들어서 PAGE_SIZE = 4096이고, size가 4097이라면, 적어도 2^1개의 페이지가 필요하므로 order는 1이다.

/**
 * get_order - Determine the allocation order of a memory size
 * @size: The size for which to get the order
 *
 * Determine the allocation order of a particular sized block of memory.  This
 * is on a logarithmic scale, where:
 *
 *	0 -> 2^0 * PAGE_SIZE and below
 *	1 -> 2^1 * PAGE_SIZE to 2^0 * PAGE_SIZE + 1
 *	2 -> 2^2 * PAGE_SIZE to 2^1 * PAGE_SIZE + 1
 *	3 -> 2^3 * PAGE_SIZE to 2^2 * PAGE_SIZE + 1
 *	4 -> 2^4 * PAGE_SIZE to 2^3 * PAGE_SIZE + 1
 *	...
 *
 * The order returned is used to find the smallest allocation granule required
 * to hold an object of the specified size.
 *
 * The result is undefined if the size is 0.
 */
static __always_inline __attribute_const__ int get_order(unsigned long size);

free_pages, free_page

할당받은 페이지를 free할 때 사용하는 함수이다.

void free_pages(unsigned long addr, unsigned int order);
#define free_page(addr) free_pages((addr), 0)

참고 문서

 

Linux Device Drivers book - Bootlin

A must-have book for people creating device drivers for the Linux kernel! Now available in a single PDF file. Linux Device Drivers from Jonathan Corbet, Alessandro Rubini and Greg Kroah-Hartmann, is the book anyone interested in writing Linux device driver

bootlin.com