이번 글에선 디바이스 드라이버 관점에서 메모리 할당에 관하여 다룬다. 메모리 관리 기법, 주소 공간에 대해서는 다루지 않는다.
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)