实际的物理内存仅当进程真实访问新获取的虚拟地址时,才会由“请页机制”产生“缺页异常”,从而进入分配内存的程序“缺页异常”是虚拟内存机制的基本保证-----由它告诉内核为进程分配物理页,并建立页表。这样,虚拟地址才真实地映射了物理地址对于申请的内存必须释放,否则将寻致系统错误。
在内核中主要有以下几种内核分配函数,其主要区别和使用:
|--用户空间(动态分配)
--malloc /free按字节分配内存
--new/delete
--vmalloc/free分配的内存按页对齐
|--内核空间
-- kmalloc/kfree:分配的内存物理上连续,只能在低端内存分配;
-- vmalloc/vfree:分配的内存在内核空间中连续,物理上无需连续;
-- get_zeroed_page/free_page:分配一个页面,并且该页面内容被清零,只能在低端内存分配;-- __get_free_pages/free_pages:分配指定页数的低端内存,只能从高端内存分配;
--alloc_pages/__free_pages:分配指定页数的内存,可以从高端内存,也可以从低端内存分配。
内核管理系统的物理内存,物理内存只能按页面进行分配。kmalloc和典型的用户空间malloc在实际上有很大的差别,内核使用特殊的基于页的分配技术,以最佳的方式利用系统RAM。Linux处理内存分配的方法:创建一系列内存对象集合,每个集合内的内存块大小是固定。处理分配请求时,就直接在包含有足够大内存块的集合中传递一个整块给请求者。
必须注意的是:内核只能分配一些预定义的、固定大小的字节数组。kmalloc能够处理的最小内存块是32或64字节(体系结构依赖),而内存块大小的上限随着体系和内核配置而变化。考虑到移植性,不应分配大于128 KB的内存。若需多于几个KB的内存块,最好使用其他方法。
内存分配最终总是调用__get_free_pages来进行实际的分配,这就是GFP_前缀的由来。所有标志都定义在,有符号代表常常使用的标志组合。
内核内存分配标志:
分配优先级的标志:
–GFP_KERNEL:表示该次内存分配由内核态进程调用,返是内核内存的正常分配,如果空闲空间不够,该次分配将使得进程进入睡眠,等待空闲页出现。
最常用的标志,意思是这个分配代表运行在内核空间的进程进行。内核正常分配内存。当空闲内存较少时,可能进入休眠来等待一个页面。当前进程休眠时,内核会采取适当的动作来获取空闲页。所以使用GFP_KERNEL来分配内存的函数必须是可重入,且不能在原子上下文中运行。
–GFP_ATOMIC:用于分配请求不是来自于进程上下文,而是来自于中断、任务队列处理、内核定时器等中断上下文的情况,此时不能进入睡眠。
内核通常会为原子性的分配预留一些空闲页。当当前进程不能被置为睡眠时,应使用GFP_ATOMIC,这样kmalloc甚至能够使用最后一个空闲页。如果连这最后一个空闲页也不存在,则分配返回失败。常用来从中断处理和进程上下文之外的其他代码中分配内存,从不睡眠。
- GFP_USER:用来为用户空间分配内存页,可能睡眠。
- GFP_HIGHUSER:类似GFP_USER,如果有高端内存,就从高端内存分配。
- GFP_NOIO/GFP_NOFS:功能类似GFP_KERNEL,但是为内核分配内存的工作增加了限制。具有GFP_NOFS的分配不允许执行任何文件系统调用,而GFP_NOIO禁止任何I/O初始化。它们主要用在文件系统和虚拟内存代码。那里允许分配休眠,但不应发生递归的文件系统调。
控制分配方式的标志:(有的标志用双下划线做前缀,他们可与上面标志“或”起来使用)
–__GFP_DMA:用于分配用于DMA(Direct memoryaccess-直接内存访问)功能的内存区(通常物理地址在16M以下)。
–__GFP_HIGHMEM:用于分配的内存可以位于高端内存的情况(通常在物理内存896M以上)
–__GFP_COLD:通常,分配器试图返回“缓存热(cache warm)”页面(可在处理器缓存中找到的页面)。而这个标志请求一个尚未使用的“冷”页面。对于用作DMA读取的页面分配,可使用此标志。因为此时页面在处理器缓存中没多大帮助。
–__GFP_NOWARN:当一个分配无法满足,阻止内核发出警告(使用printk )。
–__GFP_HIGH:高优先级请求,允许为紧急状况消耗被内核保留的最后一些内存页。
–__GFP_REPEAT/__GFP_NOFAIL/__GFP_NORETRY:告诉分配器当满足一个分配有困难时,如何动作。__GFP_REPEAT表示努力再尝试一次,仍然可能失败;
__GFP_NOFAIL告诉分配器尽最大努力来满足要求,始终不返回失败,不推荐使用;
__GFP_NORETRY告知分配器如果无法满足请求,立即返回。
内核内存的各个函数的异同如下表:
A、kmalloc/kfree函数:
该函数分配的内存物理上连续,从ZONE_NORMAL区域返回连续内存的内存分配函数,只能在低端内存分配。
函数原型如下:
void *kmalloc(int count, int flags);
count是要分配的字节数,flags是一个模式说明符。支持的所有标志列在include/linux./gfp.h文件中(gfp是get free page的缩写),如下为常用标志。
(1) GFP_KERNEL,被进程上下文用来分配内存。如果指定了该标志,kmalloc()将被允许睡眠,以等待其他页被释放。
(2) GFP_ATOMIC,被中断上下文用来获取内存。在这种模式下,kmalloc()不允许进行睡眠等待,以获得空闲页,因此GFP_ATOMIC分配成功的可能性比用GFP_KERNEL低。
使用kmalloc申请的内存需要kfree来释放,其函数原型如下:
void kfree(const void *objp);
其中参数objp:由kmalloc返回的内核虚拟地址。
由于kmalloc()返回的内存保留了以前的内容,将它暴露给用户空间可到会导致安全问题,因此我们可以使用kzalloc()获得被填充为0的内存。
B、get_zeroed_page/free_page函数:
该函数分配一个页面,并且该页面内容被清零,只能在低端内存分配。函数原型如下:
unsigned long get_zeroed_page(gfp_t gfp_mask);
参数说明:gfp_mask:分配标志,用于控制kmalloc行为。
返回值:指向分配到的已经被清零的内存页面第一个字节的指针,失败返回NULL。
void free_pages(unsigned long addr);
参数addr:由get_zeroed_page返回的内核虚拟地址。
下面通过一个实例来看看内核空间的动态内存申请方式,使用kmalloc和get_zeroed_page函数:
//Day01/KernelspaceDemo02.c
/*This demo is for how to use kmalloc and get_zeroed_page function in the kernel space*/
#include
#include
#include
#include
static char *buf_k=NULL;/*the data for kmalloc function*/
static char *buf_g=NULL;/*the data for get_zeroed_page function*/
/*initialize module function */
static int __init kernelspacedemo02_init(void){
printk(KERN_ALERT "Entry kernelspacedemo02_init...\n");
/*allocate memory from kernel space by kmalloc */
buf_k=(char *)kmalloc(100,GFP_KERNEL);
if(IS_ERR(buf_k)){
printk(KERN_ALERT "Failure to allocate memory from kernel 100 Bytes ...\n");
goto failure_alloc_buf_k;
}
memset(buf_k,0,100);
strcpy(buf_k, "Congratulate ! Success to allocate memory from kernel 100 Bytes ...\n");
printk(KERN_ALERT "Buffure of buf_k:%s\n",buf_k);
/*allocate memory from kernel space by get_zeroed_page */
buf_g=(char *)get_zeroed_page(GFP_KERNEL);
if(IS_ERR(buf_g)){
printk(KERN_ALERT "Failure to allocate one page memory from kernel...\n");
goto failure_alloc_buf_g;
}
strcpy(buf_g, "Congratulate ! Success to allocate one page memory from kernel...\n");
printk(KERN_ALERT "Buffure of buf_g:%s\n",buf_g);
return 0;
failure_alloc_buf_g:
kfree(buf_k);
buf_k=NULL;
failure_alloc_buf_k:
return -ENOMEM;
}
/*clean up module function */
static void __exit kernelspacedemo02_exit(void){
printk(KERN_ALERT "Entry kernelspacedemo02_exit...\n");
/*free the memory allocate by kmalloc and get_zeroed_page function */
free_page((unsigned long )buf_g);
buf_g=NULL;
kfree(buf_k);
buf_k=NULL;
}
/*The declaration of initialization and cleaning up module functions*/
module_init(kernelspacedemo02_init);
module_exit(kernelspacedemo02_exit);
/*The information of this module*/
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("JACK_ZHANG");
MODULE_DESCRIPTION("Use kmalloc and get_zeroed_page function to allocate memory in the kernel space");
MODULE_VERSION("0.0.1");
MODULE_ALIAS("KernelSpaceKmalloc");
编译,在开发板上运行,可以得到如下结果:
C、__get_free_pages/free_pages函数:
该函数分配指定页数的低端内存,只能从高端内存分配。函数原型如下:
unsigned long __get_free_pages(gfp_t gfp_mask,unsigned int order);
参数说明:
gfp_mask:分配标志,用于控制__get_free_pages行为
order:请求或释放的页数的2的幂,例如:操作1页该值为0,操作16页该值为4。
如果该值太大会寻致分配失败,该值允许的最大值依赖于体系结构。
返回:指向分配到的连续内存区第一个字节的指针,失败返回NULL。
free_pages的函数原型如下:
void free_pages(unsigned long addr, unsigned intorder);
参数说明:
addr:由__get_free_pages返回的内核虚拟地址
order: __get_free_pages分配内存时使用的值
int get_order(unsigned long size);
size:需要计算order值的大小(按字节计算);
返回: __ get_free_pages等函数需要的order值。
D、alloc_pages宏/__free_pages函数:
该宏可以分配指定页数的内存,可以从高端内存,也可以从低端内存分配。
函数如下:
struct page *alloc_pages_node(gfp_t gfp_mask,unsigned int order);
gfp_mask:分配标志,用于控制__get_free_pages行为。
order:请求或释放的页数的2的幂,例如:操作1页该值为0,操作16页该值为4。
如果该值太大会寻致分配失败,该值允许的最大值依赖于体系结构
返回:指向分配到的物理页面中的第一个页的structpage指针,失败返回NULL。
E、vmalloc/vfree函数:
分配的内存在内核空间中连续,物理上无需连续。vmalloc由于不需要物理上也连续,所以性能很差。一般只有在必须申请大块内存时才使用,例如动态插入模块时。
如果需要分配大的内存缓冲区,而且也不要求内存在物理上有联系,可以用vmalloc()代替kmalloc():
void *vmalloc(unsigned long size);
size:待分配的内存大小,自动按页对齐
返回:分配到的内核虚拟地址,失败返回NULL
vmalloc()需要比kmalloc()更大的分配空间,但是它更慢,而且不能从中断上下文调用。另外,不能用vmalloc()返回的物理上不连 续的内存执行DMA。
在设备打开时,高性能的网络驱动程序通常会使用vmalloc()来分配较大的描述符环行缓冲区。
Vfree函数原型:
void vfree(const void *addr);
addr:由vmalloc返回的内核虚拟地址。
下面的示例在内核模块中使用了kmalloc,__get_free_pages,以及vmalloc函数,请注意区分其在实际使用的区别:
//Day01/KernelspaceDemo03.c
/*This demo is for how to use kmalloc vmalloc and __get_free_pages function in the kernel address space*/
#include
#include
#include
#include
#include
static unsigned char *kernelkmalloc =NULL;/*the data for kmalloc function*/
static unsigned char *kernelpagemem=NULL;/*the data for __get_free_pages function*/
static unsigned char *kernelvmalloc=NULL;/*the data for vmalloc function*/
static int order =1;
/*initialize module function */
static int __init kernelspacedemo03_init(void){
int ret=-ENOMEM;
printk(KERN_ALERT "Entry kernelspacedemo03_init...\n");
/*allocate memory from kernel space by kmalloc function*/
kernelkmalloc=(unsigned char *)kmalloc(100,GFP_KERNEL);
if(IS_ERR(kernelkmalloc)){
printk(KERN_ALERT "Failure to allocate memory use kmalloc from kernel ...\n");
goto failure_kernelkmalloc;
}
printk("Congratulate ! Success to allocate memory use kmalloc from kernel ...\n");
printk(KERN_ALERT "The space address is : 0x%lx \n",(unsigned long )kernelkmalloc);
/*allocate memory from kernel space by __get_free_page function */
kernelpagemem=(unsigned char *) __get_free_pages(GFP_KERNEL,order);
if(IS_ERR(kernelpagemem)){
printk(KERN_ALERT "Failure to get pages from kernel...\n");
goto failure_kernelpagemem;
}
printk(KERN_ALERT"Congratulate ! Success to allocate free pages memory from kernel...\n");
printk(KERN_ALERT "And this pages address:0x%lx\n",(unsigned long)kernelpagemem);
/*allocate memory from kernel space by vmalloc function */
kernelvmalloc=(unsigned char *) vmalloc (1024*1024);
if(IS_ERR(kernelvmalloc)){
printk(KERN_ALERT "Failure to get memory by vmalloc function from kernel...\n");
goto failure_kernelvmalloc;
}
printk(KERN_ALERT"Congratulate ! Success to allocate memory from kernel...\n");
printk(KERN_ALERT "And this pages address:0x%lx\n",(unsigned long)kernelvmalloc);
return 0;
failure_kernelvmalloc:
free_pages((unsigned long)kernelpagemem,order);
kernelpagemem=NULL;
failure_kernelpagemem:
kfree(kernelkmalloc);
kernelkmalloc=NULL;
failure_kernelkmalloc:
return ret;
}
/*clean up module function */
static void __exit kernelspacedemo03_exit(void){
printk(KERN_ALERT "Entry kernelspacedemo03_exit...\n");
/*free the memory allocate by kmalloc/ __get_free_pages/vmalloc function */
vfree(kernelvmalloc);
kernelvmalloc=NULL;
free_pages((unsigned long)kernelpagemem,order);
kernelpagemem=NULL;
kfree(kernelkmalloc);
kernelkmalloc=NULL;
}
/*The declaration of initialization and cleaning up module functions*/
module_init(kernelspacedemo03_init);
module_exit(kernelspacedemo03_exit);
/*The information of this module*/
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("JACK_ZHANG");
MODULE_DESCRIPTION("Use kmalloc/ get_free_pages/vmalloc function to allocate memory in the kernel space");
MODULE_VERSION("0.0.1");
MODULE_ALIAS("KernelAddressSpace");
该范例编译后,开发板上运行效果如图:
特别申明:
在内核中特别注意,对于申请的内存使用完毕必须释放,否则将导致系统错误。
在内核中常使用的内存申请大致代码范例如下,实际中依据需要修改。
范例:
//Day01/KernelspaceDemo01.c
#include
#include
#include
#include
#include
#define PAGE_NUM 4
unsigned char * kernelkmalloc = NULL;
unsigned char * kernelkpagemen = NULL;
unsigned char * kernelvmalloc = NULL;
static int kernelspace_init (void){
/*错误编号*/
int ret= -ENOMEM;
/*kmalloc的第二个参数flags:
* GFP_KERNEL:分配内存,分配过程中可能导致睡眠,没有分配到就睡眠等待
* GFP_ATOMIC:分配过程中不会导致睡眠,没有分配到就直接返回
* __GFP_DMA:申请到的内存通常位于0——16M区间
* __GFP_HIGHMEM :申请高端内存(896M以上的物理地址)
*/
kernelkmalloc = (unsigned char *)kmalloc(100,GFP_KERNEL);
/*判断内存是否申请分配成功。如果成功该宏返回的是分配成功的地址,出错则回的错误编号*/
if(IS_ERR(kernelkmalloc)){
printk("Kmalloc failed !\n");
ret = PTR_ERR(kernelkmalloc);
goto failure_kmalloc;/*使用goto语句做出错处理直接返回*/
}
printk("kmalloc space :0X%lx \n",(unsigned long )kernelkmalloc);
/*__get_free_page的第二个参数order:
*请求或释放的页数的2的幂(比如是4,表示分配16页的空间)
*一般先定义宏,程序逻辑上容易理解
*/
kernelkpagemen = (unsigned char *)__get_free_pages(GFP_KERNEL,PAGE_NUM);
/*在内核空间程序,如果第一次申请成功、第二次失败,返回的时候须要释放第一次分配的内存
*如果在应用空间,进程结束,所申请和打开的资源,系统会自动回收和关闭
*这一点在内核编程中尤其重要,否则会发生内存泄漏等问题
*/
if(IS_ERR(kernelkpagemen)){
printk("Kmalloc failed !\n");
ret = PTR_ERR(kernelkmalloc);
goto failure_get_free_page ;/*使用goto语句做出错处理直接返回*/
}
printk("get_free_page :0X%lx \n",(unsigned long )kernelkpagemen);
kernelvmalloc = (unsigned char *)vmalloc(1024*1024);
if(IS_ERR(kernelvmalloc)){
printk("vmalloc failed !\n");
ret = PTR_ERR(kernelvmalloc);
goto failure_vmalloc ;/*使用goto语句做出错处理直接返回*/
}
printk("vmalloc address :0X%lx \n",(unsigned long )kernelvmalloc);
return 0;
/*出错处理*/
failure_vmalloc:
free_pages((unsigned long)kernelkpagemen,PAGE_NUM);
failure_get_free_page:
kfree(kernelkmalloc);/*若执行到此处,也会执行下面的return ret*/
failure_kmalloc:
return ret;
}
/*模块卸载的时候,需要释放所申请的内存*/
static void kernelspace_exit (void){
vfree(kernelvmalloc);
free_pages((unsigned long)kernelkpagemen,PAGE_NUM);
kfree(kernelkmalloc);
}
MODULE_LICENSE ("Dual BSD/GPL");
module_init(kernelspace_init);
module_exit(kernelspace_exit);
而基于客户端的代码则如下:
/*This demo is for how to use malloc function in the userspace*/
#include
#include
#include
static char *dat =NULL;
static long allcnt =50*1024*1024;
int main( int argc,char **argv){
char ch;
printf("Starting demostration:\n");
printf("Press \'a\' key to allocate space...\n");
ch =getchar();
if('a'!=ch){
printf("You press error key !\n");
exit(-1);
}
/*allocate memory*/
dat=malloc(allcnt);
if(NULL==dat){
printf("Failure to allocate memory size:%ld...\n",allcnt);
exit(-2);
}
printf("Success to allocate memory size:%ld...\n",allcnt);
/*Using memory allocate*/
printf("Press \'u\' key to use memory ...\n");
ch=getchar();
if('u'!= ch){
printf("You press error key !\n");
free(dat);
exit(-3);
}
printf("Using memory allocated...\n");
memset(dat,0,allcnt);
/*Free memory and exit*/
printf("Press any key to exit...\n");
getchar();
free(dat);
dat=NULL;
return 0;
}
编译后运行效果如下:
此外内核还提供了一些更复杂的内存分配技术,包括后备缓冲区(look aside buffer)、slab和mempool等。