일반적으로 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_KERNEL : 이 flag가 사용되면 할당이 항상 성공하도록 요구하는 것으로 만약에 메모리가 모자란 경우에는 할당자에서 프로세스를 잠들게 하고 메모리가 확보될때 프로세스가 깨워집니다.. 하지만 인터럽트 구간내에서는 프로세스가 잠들면 안되므로 인터럽트 처리구간내에서는 사용하지 않아야 합니다.
- GFP_ATOMIC : 이 flag가 사용되면 메모리가 부족할때 즉시 NULL을 반환하도록 요구하는것으로 프로세스가 잠드는 문제가 없기 때문에 인터럽트 구간내에서도 사용가능하게 됩니다. 단, 메모리 할당에 실패하는 경우에 대한 충분한 고려가 반드시 필요합니다.
- GFP_DMA : 연속된 물리적 메모리를 할당고자 요구할때 사용합니다.
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 */