1.通用缓冲区
在内核中初始化开销不大的数据结构可以合用一个通用的缓冲区。通用缓冲区最小为32B,然后依次为64B,128B,。。。直至128KB(即32个页面),但是,对通用缓冲区的管理采用的是Slab方式。从通用缓冲区中分配和释放缓冲区的函数:
void *kmalloc (size_t size, int flags);
void kfree(const void *ptr);
因此,当一个数据结构的使用不频繁,或其大小不足一个页面时,就没有必要为其分配专用缓冲区,而应该调用kmalloc()进行分配。如果数据结构的大小接近一个页面,则通过__get_free_pages()为它分配一个空间。
事实上,在内核中,尤其是驱动程序中,有大量的数据结构仅仅是一次性使用,而且只有几十个字节。所以用kmalloc给他们分配内存就足够了。Linux2.0以前的版本中一般都调用kamlloc给内核数据结构分配内存,所以,调用该函数的一个优点是让用户开发的驱动能保持向后兼容(在新版本中同样可以用以前的方法编译)。
kfree()释放由kmalloc()分配出来的内存块。如果释放的内存不是kmalloc()分配的,或者之前就被释放过的,再调用kfree()就会出现严重的后果。kfree和kmalloc要配对使用,以避免内存泄露和其他bug。但kfree(NULL)是安全的。
2.内核空间非连续内存的分配
我们说,任何时候,CPU访问的都是虚拟内存,那么,我们在编写驱动程序,或者编写模块时,Linux给我们分配什么样的内存?它处于4GB空间的什么位置?这就是下面要讨论的非连续内存。
首先,非连续内存处于3GB到4GB之间,也就是处于内核空间。
PAGE_OFFSET为3G,high_memory为保存物理地址最高值的变量,VMALLOC_START为非连续取得起始地址。
在物理地址的末尾与第一个内存区之间查入了一个8MB区间,这好似一个安全区,目的是为了“捕获”对非连续区的非法访问。处于同样的理由,在其他非连续的内存区之间也插入了4KB大小的安全区。每个非连续内存区的大小都是4KB的倍数。
描述非连续区的数据结构为struct vm_struct :
struct vm_struct {
unsigned long flags;
void *addr; //每个内存取的起始地址
unsigned long size; //内存区的大小加上4KB(安全区的大小)
struct vm_struct *next; //非连续区组成一个单链表
}
函数get_vm_area()创建一个新的非连续区结构,其中调用vmalloc()和vfree()函数分别为vm_struct 结构分配和时方所需内存。
vmalloc()函数给内核分配一个非连续的内存区,其原型为:
void *vmalloc(unsigned long size)
函数首先把size参数取整为页面大小(4KB)的一个倍数,也就是按页的大小进行对齐,然后进行有效性的检查如果有大小合适的可用内存,就调用get_vm_area()过的一个内存区的结构。最后调用函数vmalloc_area_pages()真正进行非连续内存取的分配,该函数实际上建立了非连续内存到物理页面的映射。
vmalloc分配的物理地址无需连续(分配内存在VMALLOC_START到4GB之间这段非连续内存区映射到物理内存也可能不是连续的),而kmalloc必须确保页在物理上是连续的(当然虚地址也要是连续的,3GB到high_memory之间,内核空间和物理内存区一一映射)。
尽管只有某些时候才需要物理上连续的内存块,但是,很多内和代码都调用kmalloc(),因为vmalloc()函数为了把物理上不连续的页面转换为虚拟地址空间上连续的页,必须专门建立页标项。而且vmalloc()获得的页必须一个一个的映射(因为他们在物理地质上并不连续),着就会导致比直接内存映射大的多的缓冲区刷新。所以vmalloc只有在必要时才会使用(典型的是为了获得大的内存快的时候,例如,当模块被动态的插入到内核中时,就把模块装载到由vmalloc分配的内存上)。
由上面学到的函数做个小小的练习:
unsigned long pagemem;
unsigned char* kmallocmem;
unsigned char* vmallocmem;
MODULE_LICENSE("GPL");
static int __init init_mmshow(void)
{
pagemem = __get_free_page(GFP_KERNEL);
if(!pagemem)
goto fail3;
printk(KERN_INFO"pagemem = 0x%lx\n", pagemem);
kmallocmem = kmalloc(100, GFP_KERNEL);
if(!kmallocmem)
goto fail2;
printk(KERN_INFO"kmallocmem = 0x%p\n", kmallocmem);
vmallocmem = vmalloc(1000000);
if(!vmallocmem)
goto fail1;
printk(KERN_INFO"vmallocmem = 0x%p\n", vmallocmem);
return 0;
fail1:
kfree(kmallocmem);
fail2:
free_page(pagemem);
fail3:
return -1;
}
static void __exit clean_mmshow(void)
{
vfree(vmallocmem);
kfree(kmallocmem);
free_page(pagemem);
}
module_init(init_mmshow);
module_exit(clean_mmshow);