일반적으로 C언어의 표준 라이브러리 함수인 malloc, free함수는 커널에서 사용하지 않습니다. 프로세스가 동작하는 사용자프로세스에서는 glibc에 의해서 할당루틴을 제공받아서 malloc, free가 제공됩니다. 그리고 특별히 할당과 해제에 있어서 심각한 고려사항들이 발생하지 않습니다. 그러나 커널의 경우는 상황이 다르며 다음과 같은 상황에 대해서 면밀한 고려가 필요합니다.
- 커널에서는 물리적인 메모리를 직접 접근할수도 있고 MMU(메모리 관리장치)를 통한 접근도 고려해야 합니다.
- 메모리의 할당과 해제는 빈번하게 발생하며 이로 인하여 메모리의 단편화가 발생할수 있습니다. 이것을 방지하기 위해서 커널에서는 PAGE_SIZE와 PAGE_SHIFT라는 값으로 관리됩니다. (보통 PAGE_SHIFT는 12를 사용하며 1<<12 즉, 4KBytes가 PAGE_SIZE로 정의하여 사용합니다. 이것은 하드웨어적인 제약에 의해서 결정됩니다.)
- 응용프로그램에서는 malloc, free함수에 의해서 가상메모리를 할당받기 때문에 실패할 가능성이 거의 없습니다. 그렇지만 커널에서는 요구되는 메모리크기를 할당하는데 부족하거나 단편화로 인하여 적절한 메모리의 단편화 제거동작이 필요할수 있습니다. 이에 따라서 실패하였을때 할당이 성공할때까지 대기하면서 해당 메모리를 확보하도록 동작하던지 아니면 실패에 따른 복귀를 하던지 메모리의 요구성격에 따라서 고려되어야 합니다.
- 가상메모리기법에 의해서 실제 접근하고자 하는 메모리가 물리적 메모리가 아닌 보조저장장치에 있을수가 있는데 이러한 경우에 대한 추가적인 처리를 고려하여야 합니다.
- DMA같은 연속된 물리적 메모리 주소가 필요한 경우 이를 고려한 메모리관리루틴이 필요합니다. (요즘에는 가상 DMA를 이용하는 경우도 있다고 하는데 흔치는 않은것 같습니다.)
- Interrupt상황에서 메모리를 할당해야 하는 경우 일반적으로 메모리가 부족할때 해당 Interrupt구간에서 프로세스를 잠들게 하면 안되는 경우가 있으며 이를 고려하여야 합니다.
리눅스 커널은 기본적으로 __get_free_pages, free_page함수를 제공하여 PAGE_SIZE의 승수에 해당하는 메모리를 할당받거나 해제하는것이 기본 할당자로 제공됩니다. 이 함수는 커널의 할당특성을 만족시키기 위해서 flag(gfp_mask)를 추가로 인자로 넘겨받습니다. 이 함수는 실제로 승수인자를 MAX_ORDER값으로 제한받기 때문에 PAGE_SIZE * (1 << MAX_ORDER) 보다 큰 메모리는 할당받을수 없습니다. (어차피 승수가 커지면 단편화로 인하여 실제로 메모리가 더 있음에도 불구하고 실패할 확률은 높아집니다. 보통 MAX_ORDER는 11을 사용합니다.)
- GFP_ATOMIC : Allocation will not sleep. May use emergency pools. For example, use this inside interrupt handlers.
- sleep되어서는 안되는 경우에 사용되며 인터럽트 핸들러 등에서 사용합니다.
- 이 flag가 사용되면 메모리가 부족할때 즉시 NULL을 반환하도록 요구하는것으로 프로세스가 잠드는 문제가 없기 때문에 인터럽트 구간내에서도 사용가능하게 됩니다. 단, 메모리 할당에 실패하는 경우에 대한 충분한 고려가 반드시 필요합니다.
- 슬립되지 않아야 하고 “atomic reserves”으로의 접근이 허락된 low 워터마크가 적용되게 요청합니다.
- "(GFP_HIGH|GFP_ATOMIC|__GFP_KSWAPD_RECLAIM)"
- GFP_DMA : Allocation suitable for DMA. Should only be used for kmalloc caches. Otherwise, use a slab created with SLAB_DMA.
- DMA를 위한 영역의 메모리를 할당합니다.
- 연속된 물리적 메모리를 할당고자 요구할때 사용합니다.
- "(__GFP_DMA)"
- GFP_HIGHUSER : Allocate pages from high memory.
- userspace 할당을 위해 GFP_USER에 highmem 사용을 요청합니다.
- "(GFP_USER | __GFP_HIGHMEM)"
- GFP_HIGHUSER_MOVABLE
- userspace 할당을 위해 GFP_USER에 highmem 및 movable migrate 타입 사용을 요청합니다.
- "(GFP_HIGHUSER | __GFP_MOVABLE)"
- GFP_USER : Allocate memory on behalf of user. May sleep.
- userspace 할당을 위해 커널 및 하드웨어에 의해 직접 접근이 가능하도록 요청합니다.
- 현재 태스크에 지정된 cpuset 메모리 할당 정책을 사용하게 요청합니다.
- sleep될 수 있습니다.
- "(__GFP_RECLAIM | __GFP_IO | __GFP_FS | __GFP_HARDWALL)"
- GFP_KERNEL : Allocate normal kernel ram. May sleep.
- kernel의 요청에 의한 메모리 할당이며 sleep될 수 있습니다.
- 이 flag가 사용되면 할당이 항상 성공하도록 요구하는 것으로 만약에 메모리가 모자란 경우에는 할당자에서 프로세스를 잠들게 하고 메모리가 확보될때 프로세스가 깨워집니다.. 하지만 인터럽트 구간내에서는 프로세스가 잠들면 안되므로 인터럽트 처리구간내에서는 사용하지 않아야 합니다.
- 커널 내부 알고리즘이 이용하는 할당을 위해 사용되며, direct-reclaim이나 kswapd를 통한 reclaim이 가능하고 io 및 fs의 이용이 가능한 상태로 ZONE_NORMAL 또는 lower zone을 사용하도록 요청합니다.
- "(__GFP_RECLAIM | __GFP_IO | __GFP_FS)"
- GFP_KERNEL_ACCOUNT
- kmemcg(메모리 Control Group)의 사용량 통제를 받는것을 제외하고 GFP_KERNEL과 동일합니다.
- "(GFP_KERNEL | __GFP_ACCOUNT)"
- GFP_NOFS : Do not make any fs calls while trying to get memory.
- 메모리 할당을 하는 동안 Filesystem 관련 처리가 수행되지 않도록 합니다.
- direct reclaim을 이용 시 io 처리는 가능하나 file system 인터페이스를 이용하지 못하게 합니다.
- "(__GFP_RECLAIM | __GFP_IO)"
- GFP_NOIO : Do not do any I/O at all while trying to get memory.
- 메모리 할당을 하는 동안 I/O 처리가 수행되지 않도록 합니다.
- direct reclaim을 이용 시 클린 페이지 또는 slab 페이지들을 버릴 수 없도록 합니다.
- "(__GFP_RECLAIM)"
- GFP_NOWAIT : Allocation will not sleep.
- 커널 할당을 위해 kswapd를 사용한 reclaim이 가능하도록 요청합니다.
- "(__GFP_KSWAPD_RECLAIM)"
- GFP_TEMPORARY
- "(__GFP_RECLAIM | __GFP_IO | __GFP_FS | __GFP_RECLAIMABLE)"
- GFP_TRANSHUGE
- THP(Transparent Huge Page) 할당을 위해 사용되며 메모리 부족시 빠르게 실패하게 합니다.
- 실패한 경우에도 kswapd를 깨우지 않게 합니다.
- "((GFP_HIGHUSER_MOVABLE | GFP_COMP | __GFP_NOMEMALLOC | __GFP_NORETRY | __GFP_NOWARN) & ~GFP_RECLAIM)"
- GFP_ZONEMASK
- GFP 플래그를 사용하지 하지 않을 때 일반적으로 ZONE_NORMAL을 의미합니다.
- "(GFP_DMA|GFP_HIGHMEM|GFP_DMA32|GFP_MOVABLE)"
- __GFP_ACCOUNT
- kmemcg(메모리 Control Group)의 사용량 통제를 받지 않도록 요청합니다.
- __GFP_ATOMIC
- 페이지 회수나 슬립이 허용되지 않고 높은 우선 순위로 처리되도록 요청합니다.
- __GFP_COLD : Request cache-cold pages instead of trying to return cache-warm pages.
- 메모리 단편화에 영향을 줄이기 위해서 hot 페이지 대신 cold 페이지에서 할당합니다.
- __GFP_COMP
- 메타 데이터 또는 연속된 복합 페이지를 구성하도록 요청합니다.
- __GFP_DIRECT_RECLAIM
- 페이지 할당 요청 시 free 페이지가 부족한 경우 direct reclaim(호출자가 직접 회수)을 들어갈 수 있도록 요청합니다.
- __GFP_DMA
- __GFP_DMA32
- __GFP_FS
- 메모리 할당을 하는 동안 File System calls 가능하도록 요청합니다.
- __GFP_HARDWALL
- 현재 태스크에 지정된 cpuset 메모리 할당 정책을 사용하게 요청합니다.
- __GFP_HIGH : This allocation has high priority and may use emergency pools.
- __GFP_HIGHMEM
- ZONE_HIGHMEM 영역에 할당 요청합니다.
- __GFP_IO
- 메모리 할당을 하는 동안 어떠한 I/O 처리도 가능하도록 요청합니다.
- __GFP_KSWAPD_RECLAIM
- low 워터마크에 접근하는 경우 kswapd를 깨워서 high 워터마크에 오를때까지 페이지를 회수하도록 요청합니다.
- __GFP_MEMALLOC
- 모든 메모리로의 접근을 허가하도록 요청합니다.
- 프로세스 종료나 스와핑의 사용 예와 같이 매우 짧은 시간내 메모리 할당이 요구될 때 필요합니다.
- __GFP_MOVABLE
- ZONE_MOVABLE이 허락되는 경우 이 영역에 할당 요청합니다.
- __GFP_NOFAIL : Indicate that this allocation is in no way allowed to fail (think twice before using).
- __GFP_NOMEMALLOC
- 비상용 reserves 영역을 이용하지 못하게 엄격히 금지하도록 요청합니다.
- __GFP_NORETRY : If memory is not immediately available, then give up at once.
- 메모리 할당이 실패되면 다시 시도하지 않습니다.
- __GFP_NOTRACK
- kmemcheck를 사용한 디버그 트래킹을 허용하지 않도록 요청합니다.
- __GFP_NOTRACK_FALSE_POSITIVE
- kmemcheck를 사용한 false positive(가짜 긍정) 디버그 트래킹을 허용하지 않도록 요청합니다.
- __GFP_NOWARN : If allocation fails, don't issue any warnings.
- 메모리 할당이 실패할 때 어떠한 경고도 처리하지 않습니다.
- __GFP_OTHERNODE
- __GFP_RECLAIM
- "(_GFP_DIRECT_RECLAIM|_GFP_KSWAPD_RECLAIM)"
- __GFP_RECLAIMABLE
- __GFP_REPEAT : If allocation fails initially, try once more before failing.
- 메모리 할당이 처음 실패하는 경우 한 번은 재시도하도록 합니다.
- __GFP_THISNODE : Allocate node-local memory only.
- __GFP_WRITE
- dirty(쓰기용 파일 캐시) 페이지 할당을 요청합니다.
- __GFP_ZERO
- 할당된 영역을 0으로 초기화 하도록 요청한다.
unsigned long s_page;
unsigned int s_order;
s_order = MAX_ORDER;
s_order = get_order((4 << 10) * (1 << s_order));
if(s_order > ((unsigned int)(MAX_ORDER))) {
printk("<0>too big order ! (%u/%u)\n", s_order, (unsigned int)(MAX_ORDER));
}
/* unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order) */
s_page = __get_free_pages(GFP_KERNEL, s_order);
if(s_page != 0ul) {
printk("<0>__get_free_pages success. (order=%u/%u)\n", s_order, (unsigned int)(MAX_ORDER));
free_page(s_page);
}
else {
printk("<0>__get_free_pages failed ! (order=%u/%u)\n", s_order, (unsigned int)(MAX_ORDER));
}
kmalloc, kfree는 malloc, free함수와 매우 유사한 함수이지만 근본적으로 커널메모리의 특성에 따른 메모리할당을 구현하기 위해서 __get_free_pages함수처럼 flag(gfp_mask)를 추가인자로 사용합니다. __get_free_pages함수는 승수를 인자로 받지만 kmalloc함수는 size인자를 직접 받기 때문에 매우 편리하며 비교적 커널내에서 가장 많이 사용되는 할당함수라고 보시면 됩니다. 그러나 kmalloc 역시 MAX_ORDER에 따른 할당크기에 제약이 있다는 점에 주의해야 합니다.
void *s_page;
/* void *kmalloc(size_t size, gfp_t flags) */
s_page = kmalloc((size_t)1234u, GFP_KERNEL);
if(s_page != ((void *)0)) {
printk("<0>kmalloc success.\n");
/* void kfree(const void *objp) */
kfree((const void *)s_page);
}
else {
printk("<0>kmalloc failed !\n");
}
vmalloc, vfree는 size인자외에 특별한 인자를 사용하지 않으며 malloc, free함수와 가장 유사성을 띈 함수라고 할수 있습니다. __get_free_pages, kmalloc함수는 할당크기에 제약이 존재하지만 vmalloc은 가상메모리 공간을 할당하기 때문에 크기에 대한 물리적으로 허용하는 이상 크기제약은 없습니다. 하지만 인터럽트구간내에서 사용할수 없으며 가상메모리관리루틴이 수행되기 때문에 __get_free_pages, kmalloc에 비하여 상대적으로 느리고 연속적인 물리적 메모리를 기대할수 없다는 단점이 있습니다.
void *s_vpage;
/* void *vmalloc(unsigned long size) */
s_vpage = vmalloc(1234ul);
if(s_vpage != ((void *)0)) {
printk("<0>vmalloc success.\n");
/* void vfree(const void *addr) */
vfree((const void *)s_vpage);
}
else {
printk("<0>vmalloc failed !\n");
}
시스템이 순간적으로 대용량의 데이터를 처리할때 메모리가 부족해지며 가상메모리가 사용되면서 시스템의 성능저하가 발생할수 있습니다. 시스템이 원활하게 동작하도록 하려면 일부처리루틴에서는 일정량이 메모리를 미리 확보하여 메모리가 부족할때 이를 사용하는 방식도 필요성이 대두되었습니다. 그래서 고안된것이 바로 Memory pool관리 API입니다.
위의 할당자들에 대한 종합적인 예제는 다음과 같습니다.
#include <linux/module.h>
#include <linux/init.h>
#include <linux/mempool.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#if !defined(mzdriver_mempool_element_t)
typedef struct mzdriver_mempool_element_ts {
unsigned char __dummy;
unsigned char __shadow_area;
}__mzdriver_mempool_element_t;
# define mzdriver_mempool_element_t __mzdriver_mempool_element_t
#endif
static void *mzdriver_mempool_alloc_handler(gfp_t s_gfp_mask, void *s_pool_data)
{
void *s_result;
/* void *kmalloc(size_t size, gfp_t flags) */
s_result = (void *)kmalloc(sizeof(mzdriver_mempool_element_t), s_gfp_mask);
if(unlikely(s_result == ((void *)0))) {
printk("<0>mempool_alloc failed !\n");
return((void *)0);
}
printk("<0>mempool_alloc success.\n");
return(s_result);
}
static void mzdriver_mempool_free_handler(void *s_element, void *s_pool_data)
{
if(s_element == ((void *)0)) {
printk("<0>mempool_free EINVAL !\n");
return;
}
/* void kfree(const void *objp) */
kfree((const void *)s_element);
printk("<0>mempool_free success.\n");
}
static int __init mzdriver_init(void)
{
printk("<0>Insert mzdriver module.\n");
do { /* __get_free_pages, free_page */
unsigned long s_page;
unsigned int s_order;
s_order = MAX_ORDER;
s_order = get_order((4 << 10) * (1 << s_order));
if(s_order > ((unsigned int)(MAX_ORDER))) {
printk("<0>too big order ! (%u/%u)\n", s_order, (unsigned int)(MAX_ORDER));
}
/* unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order) */
s_page = __get_free_pages(GFP_KERNEL, s_order);
if(s_page != 0ul) {
printk("<0>__get_free_pages success. (order=%u/%u)\n", s_order, (unsigned int)(MAX_ORDER));
free_page(s_page);
}
else {
printk("<0>__get_free_pages failed ! (order=%u/%u)\n", s_order, (unsigned int)(MAX_ORDER));
}
}while(0);
do { /* mempool */
void *s_pool_data;
mempool_t *s_mempool;
int s_count, s_max_pool;
void *s_pool[ 3 ];
s_pool_data = (void *)0;
s_max_pool = sizeof(s_pool) / sizeof(void *);
/* mempool_t *mempool_create(int min_nr, mempool_alloc_t *alloc_fn, mempool_free_t *free_fn, void *pool_data) */
s_mempool = mempool_create(0, mzdriver_mempool_alloc_handler, mzdriver_mempool_free_handler, s_pool_data);
if(s_mempool != ((mempool_t *)0)) {
printk("<0>mempool_create success.\n");
for(s_count = 0;s_count < s_max_pool;s_count++) {
/* void * mempool_alloc(mempool_t *pool, gfp_t gfp_mask) */
s_pool[s_count] = mempool_alloc(s_mempool, GFP_KERNEL);
if(s_pool[s_count] != ((void *)0)) {
printk("<0>mempool_alloc[%d] success.\n", s_count);
}
else {
printk("<0>mempool_alloc[%d] failed !\n", s_count);
}
}
printk("<0>mempool_alloc end.\n");
for(s_count = 0;s_count < s_max_pool;s_count++) {
if(s_pool[s_count] != ((void *)0)) {
/* void mempool_free(void *element, mempool_t *pool) */
mempool_free(s_pool[s_count], s_mempool);
printk("<0>mempool_free[%d] success.\n", s_count);
}
else {
printk("<0>mempool_free[%d] ignored !\n", s_count);
}
}
/* void mempool_destroy(mempool_t *pool) */
mempool_destroy(s_mempool);
}
else {
printk("<0>mempool_create failed !\n");
}
}while(0);
do { /* vmalloc, vfree */
void *s_vpage;
/* void *vmalloc(unsigned long size) */
s_vpage = vmalloc(1234ul);
if(s_vpage != ((void *)0)) {
printk("<0>vmalloc success.\n");
/* void vfree(const void *addr) */
vfree((const void *)s_vpage);
}
else {
printk("<0>vmalloc failed !\n");
}
}while(0);
return 0;
}
static void __exit mzdriver_exit(void)
{
printk("<0>Remove mzdriver module.\n");
}
module_init(mzdriver_init);
module_exit(mzdriver_exit);
/* End of source */