2015-02-22 李海沿
不知道为什么,最近做梦总是梦见以前的事,以前的场景,可能是28号回学校的缘故吧!好了,不扯废话了,前面我针对gpio按键这个实验学习了中断,信号量,定时器等内核实现,下面我们,使用以前的字符设备模板来写一个Linux内存空间内存分配的实验。
一、KMALLOC
kmalloc 是一个功能强大且高速(除非被阻塞)的工具,所分配到的内存在物理内存中连续且保持原有的数据(不清零)。原型:
#include <linux/slab.h> void *kmalloc(size_t size, int flags); |
参数详解:
size 参数
内核管理系统的物理内存,物理内存只能按页面进行分配。kmalloc 和典型的用户空间 malloc 在实际上有很大的差别,内核使用特殊的基于页的分配技术,以最佳的方式利用系统 RAM。Linux 处理内存分配的方法:创建一系列内存对象集合,每个集合内的内存块大小是固定。处理分配请求时,就直接在包含有足够大内存块的集合中传递一个整块给请求 者。
必须注意的是:内核只能分配一些预定义的、固定大小的字节数组。kmalloc 能够处理的最小内存块是 32 或 64 字节(体系结构依赖),而内存块大小的上限随着体系和内核配置而变化。考虑到移植性,不应分配大于 128 KB的内存。若需多于几个 KB的内存块,最好使用其他方法。
flags 参数
内存分配最终总是调用 __get_free_pages 来进行实际的分配,这就是 GFP_ 前缀的由来。
所有标志都定义在 <linux/gfp.h> ,有符号代表常常使用的标志组合。
主要的标志常被称为分配优先级,包括:
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 初始化。它们主要用在文件系统和虚拟内存代码。那里允许分配休眠,但不应发生递归的文件系统调。
Linux 内核把内存分为 3 个区段: 可用于DMA的内存(位于一个特别的地址范围的内存, 外设可以在这里进行 DMA 存取)、常规内存和高端内存(为了访问(相对)大量的内存而存在的一种机制)。目的是使每中计算机平台都必须知道如何将自己特定的内存范围归类到这三个区 段中,而不是所有RAM都一样。 当要分配一个满足kmalloc要求的新页时, 内核会建立一个内存区段的列表以供搜索。若指定了 __GFP_DMA, 只有可用于DMA的内存区段被搜索;若没有指定特别的标志, 常规和 可用于DMA的内存区段都被搜索; 若 设置了 __GFP_HIGHMEM,所有的 3 个区段都被搜索(注意:kmalloc 不能分配高端内存)。 内存区段背后的机制在 mm/page_alloc.c 中实现, 且区段的初始化时平台相关的, 通常在 arch 目录树的 mm/init.c中。 |
有的标志用双下划线做前缀,他们可与上面标志"或"起来使用,以控制分配方式:
__GFP_DMA
要求分配可用于DMA的内存。
__GFP_HIGHMEM
分配的内存可以位于高端内存.
__GFP_COLD
通常,分配器试图返回"缓存热(cache warm)"页面(可在处理器缓存中找到的页面)。 而这个标志请求一个尚未使用的"冷"页面。对于用作 DMA 读取的页面分配,可使用此标志。因为此时页面在处理器缓存中没多大帮助。
__GFP_NOWARN
当一个分配无法满足,阻止内核发出警告(使用 printk )。
__GFP_HIGH
高优先级请求,允许为紧急状况消耗被内核保留的最后一些内存页。
__GFP_REPEAT
__GFP_NOFAIL
__GFP_NORETRY
告诉分配器当满足一个分配有困难时,如何动作。__GFP_REPEAT 表示努力再尝试一次,仍然可能失败; __GFP_NOFAIL 告诉分配器尽最大努力来满足要求,始终不返回失败,不推荐使用; __GFP_NORETRY 告知分配器如果无法满足请求,立即返回。
前面知识点摘自: http://www.douban.com/note/56607778/
二、后备高速缓存 (lookaside cache)
内核中普通对象进行初始化所需的时间超过了对其进行分配和释放所需的时间,因此不应该将内存释放回一个全局的内存池,而是将内存保持为针对特定目而初始化的状态。例如,如果内存被分配给了一个互斥锁,那么只需在为互斥锁首次分配内存时执行一次互斥锁初始化函数(mutex_init)即可。后续的内存分配不需要执行这个初始化函数,因为从上次释放和调用析构之后,它已经处于所需的状态中了。
linux2.6中USB和SCSI驱动程序使用了这种高速缓存,是为一些反复使用的块增加某些特殊的内存池。后背高速缓存管理也叫slab分配器,相关函数和类型在<linux/slab.h>中申明。
slab分配器实现高速缓存具有kmem_cache_t类型。
kmem_cache_t * kmem_cache_create( const char *name, size_t size, size_t align, unsigned long flags; void (*constructor)(void*, kmem_cache_t *, unsigned long), void (*destructor)(void*, kmem_cache_t *, unsigned long)); |
用于创建一个新的高速缓存对象。
constructor用于初始化新分配的对象,destructor用于清除对象。
一旦某个对象的高速缓存被创建以后,就可以调用kmem_cache_alloc从中分配内存对象。
void * kmem_cache_alloc(kmem_cache_t *cache,int flags);
释放内存对象使用kmem_cache_free
void kmem_cache_free(kmem_cache_t *cache,const void *obj);
在内存空间都被释放后,模块被卸载前,驱动程序应当释放他的高速缓存。
int kmem_cache_destory(kmem_cache_t *cache);
要检查其返回状态,如果失败,表明莫块中发生了内存泄露。
基于slab的高速缓存scullc
kmem_cache_t *scullc_cache; scullc_cache=kmem_cache_creat("scullc",scullc_quantum,0,SLAB_HWCACHE_ALIGN,NULL,NULL); if(!scullc_cache) { scullc_cleanup(); return -ENOMEM; } if(!dpte->data[s_pos]) { dptr->data[s_pos]=kmem_cache_alloc(scullc_cache,GFP_KERNEL); if(!dptr->data[s_pos]) goto nomem; memset(dptr->data[s_pos],0,scullc_quantum); } for(i=0;i<qset;i++) { if(dptr->data[i]) kmem_cache_free(scullc_cache,dptr->data[i]); } if(scullc_cache) kmem_cache_destory(scullc_cache); |
三、内存池
内核中有些地方的内存分配是不允许失败的,为确保能分配成功,内核建立一种称为内存池的抽象,他试图始终保持空闲状态,以便紧急情况使用。
mempool_t * mempool_creat(int min_nr, mempool_alloc_t *alloc_fn, //对象分分配 mempool_alloc_slab mempool_free_t *free_fn, //释放 mempool_free_slab void *pool_data); |
可以用如下代码来构造内存池
cache=kmem_cache_creat(...); //创建一个高速缓存 pool=mempool_creat(MY_POOL_MINIMUM,mempool_alloc_slab,mempool_free_slab,cache);//建立内存池对象 void *mempool_alloc(mempool_t *poll,int gfp_mask);//分配对象 void *mempool_free(void *element,mempool_t *poll);//释放对象 void mempool_destroy(mempool_t *poll);//销毁内存池 |
注意:mempool会分配一些内存块,空闲且不会被用到,造成内存的大量浪费。所以一般情况不要用内存池。
四、实例分析
1.定义内存操作的指针
2.在init函数中,分别使用kmalloc,get_zeroed_page,vmalloc申请物理内存,整页内存,虚拟内存
3.申请内存之后就是读写内存
4.当我们不用内存时,在exit函数中释放内存,防止造成内存泄露
5.编译测试
如图所示,当我们insmod时,打印出了申请的内存的地址,并且成功的读出了我们写入的数据
附上驱动代码:
1 /*在内存中申请1k 大小的内存做为简单的一个设备来访问*/ 2 #include<linux/cdev.h> 3 #include<linux/module.h> 4 #include<linux/types.h> 5 #include<linux/fs.h> 6 #include<linux/errno.h> 7 #include<linux/mm.h> 8 #include<linux/sched.h> 9 #include<linux/init.h> 10 #include<asm/io.h> 11 #include<asm/system.h> 12 #include<asm/uaccess.h> 13 #include<linux/device.h> 14 #include <linux/vmalloc.h> 15 16 #define Driver_NAME "kmalloc" 17 #define DEVICE_NAME "kmalloc" 18 19 static int major = 0; 20 21 //auto to create device node 22 static struct class *drv_class = NULL; 23 static struct class_device *drv_class_dev = NULL; 24 25 //定义内存分配的指针 26 char *buf1 = NULL; 27 char *buf2 = NULL; 28 char *buf3 = NULL; 29 30 static int key_open(struct inode *inode, struct file *file) 31 { 32 printk("<0>function open!\n\n"); 33 return 0; 34 } 35 36 static int key_read(struct file *filp, char __user *buff, size_t count, loff_t *offp) 37 { 38 return 0; 39 } 40 41 static ssize_t key_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos) 42 { 43 printk("<0>function write!\n\n"); 44 return 1; 45 } 46 47 static int key_release(struct inode *inode, struct file *filp) 48 { 49 printk("<0>function write!\n\n"); 50 return 0; 51 } 52 53 static int key_ioctl(struct inode *inode,struct file *flip,unsigned int command,unsigned long arg) 54 { 55 printk("<0>function ioctl!\n\n"); 56 return 0; 57 } 58 59 static struct file_operations key_fops = { 60 .owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */ 61 .open = key_open, 62 .read = key_read, 63 .write = key_write, 64 .release= key_release, 65 .ioctl = key_ioctl, 66 }; 67 68 69 static int __init key_irq_init(void) 70 { 71 printk("<0>\nHello,this is %s module!\n\n",Driver_NAME); 72 //register and mknod 73 major = register_chrdev(0,Driver_NAME,&key_fops); 74 drv_class = class_create(THIS_MODULE,Driver_NAME); 75 drv_class_dev = device_create(drv_class,NULL,MKDEV(major,0),NULL,DEVICE_NAME); /*/dev/key_query*/ 76 77 //kmalloc分配内存实验1 78 // buf1 申请一个100字节的物理内存,若无内存,则休眠 79 buf1 = (unsigned char *)kmalloc(100,GFP_KERNEL); 80 if(buf1 == NULL){ 81 printk("<0>buf1 kmalloc error !\n\n"); 82 return -1; 83 } 84 printk("<0>buf1 kmalloc mem addr = %x \n\n",buf1); 85 memset(buf1,0,100); //将所分配到的内存清理 86 strcpy(buf1,"<<------buf1 Kmalloc Mem OK!------>>");//向内存中写入数据 87 printk("<0>BUF1: %s\n\n",buf1); //从内存中读出数据 88 89 //get_zeroed_page申请分配整页内存,并且初始化为0 90 buf2 = (unsigned char *)get_zeroed_page(GFP_KERNEL); 91 if(buf2 == NULL){ 92 printk("<0>buf2 get_zeroed_page error !\n\n"); 93 return -1; 94 } 95 printk("<0>buf2 get_zeroed_page addr = %x \n\n",buf2); 96 strcpy(buf2,"<<------buf2 get_zeroed_page OK!------>>");//向内存中写入数据 97 printk("<0>BUF2: %s\n\n",buf2); 98 99 //Vmalloc 申请1000000个字节(1M)的空间 虚拟地址分配(零碎的物理地址空间) 100 buf3 = (unsigned char *)vmalloc(1000000); 101 if(buf3 == NULL){ 102 printk("<0>buf3 vmalloc error !\n\n"); 103 return -1; 104 } 105 printk("<0>buf3 vmalloc addr = %x \n\n",buf3); 106 strcpy(buf3,"<<------buf3 vmalloc OK!------>>");//向内存中写入数据 107 printk("<0>BUF3: %s\n\n",buf3); 108 109 return 0; 110 } 111 112 static void __exit key_irq_exit(void) 113 { 114 printk("<0>\nGoodbye,%s!\n\n",Driver_NAME); 115 116 unregister_chrdev(major,Driver_NAME); 117 device_unregister(drv_class_dev); 118 class_destroy(drv_class); 119 120 //释放前面申请的内存 121 printk("<0>buf1 kfree addr = %x \n\n",buf1); 122 kfree(buf1); 123 printk("<0>buf2 free_page addr = %x \n\n",buf2); 124 free_page((unsigned long)buf2); 125 printk("<0>buf3 vfree addr = %x \n\n",buf3); 126 vfree(buf3); 127 printk("<0><<------Module Exit!------>>\n\n"); 128 129 } 130 131 132 /* 这两行指定驱动程序的初始化函数和卸载函数 */ 133 module_init(key_irq_init); 134 module_exit(key_irq_exit); 135 136 /* 描述驱动程序的一些信息,不是必须的 */ 137 MODULE_AUTHOR("Lover雪儿"); 138 MODULE_VERSION("0.1.0"); 139 MODULE_DESCRIPTION("IMX257 key Driver"); 140 MODULE_LICENSE("GPL");