이번 글에선 디바이스 드라이버 관점에서 메모리 할당에 관하여 다룬다. 메모리 관리 기법, 주소 공간에 대해서는 다루지 않는다 주소와 메모리 공간은 아래 글을 참고하자.
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)
참고 문서
'Kernel' 카테고리의 다른 글
[Linux Kernel] 고민과 공부 방향 정리 (5) | 2021.05.12 |
---|---|
[Linux Kernel] 첫 의미있는 기여 경험 (mm, slub) (2) | 2021.05.12 |
[Linux Kernel] 시간과 타이머 (1) | 2021.05.04 |
[Linux Kernel] 인터럽트와 후반부 처리의 개념 (0) | 2021.04.28 |
[Linux Kernel] proc 파일시스템과 seq_file 인터페이스 (1) | 2021.04.18 |
댓글