先上几个图,大家可以linux管理内存的基本方式,也比较容易理解:
下面介绍内核分配内存的API以及使用方法注意事项。
1.kmalloc
原型如下:
#include
void *kmalloc(size_t size,int flags);
该函数分配的物理内存是连续的,该函数不对分配的内存清零,size表示分配块的大小,flags控制kmalloc分配方式的行为。下面具体讲解这两个参数:
size参数:
分配范围:32/64byte < size < 128k,最小的32或者64取决于当前体系结构下的页面大小。
flags:
最常用的两个flag标志: GFP_KERNEL(可能休眠),GFP_ATOMIC(原子分配,不会休眠)
linux内核把内存分为三个区段:可用于DMA的内存,常规内存,高端内存,通常的分配都发生在常规内存区,但是通过设置下面的标志也可以请求其他区段的分配,使用方法为和上面的标记 | 起来。
这些标记包括:
__GFP_DMA:用于DMA的内存分配
__GFP_HIGHMEM:用于高端内存的分配。但是这个标记表示内存会对三个区段进行依次搜索分配,并不一定会在高端内存区分配,有趣的是书中写道:kmalloc是不能分配高端内存的。
......
例如我要分配一段不可休眠的用于DMA的内存,可以给flag赋值:GFP_ATOMIC | __GFP_DMA
2. 后备高速缓存
内核维护了一组拥有同一大小内存块的内存池,称为后备高速缓存。内核的高速缓存管理称为"slab分配器",slab分配的高速缓存的类型为kmem_cache_t,可以通过下面的函数调用:
- #inlcude<linux/slab.h>
- kmem_cache_t *kmem_cache_create(const char *name,size_t size,
- size_t offset,
- unsigned long flags,
- void (*constructor)(void *,kmem_cache_t *,
- unsigned long flags),
- void (*destructor)(void *,kmem_cache_t *,
- unsigned long falgs));
该函数创建一个高速缓存对象,这些区域大小相同,区域大小由size指定;name通常设定为将要高速缓存的结构类型的名字,指向一个常量字符串;offset表示页面中第一个对象的偏移量,默认为0;flags表示如何完成分配,是一个位掩码,取值有:
SLAB_NO_REAP:保护缓存在系统寻找时不会减少,一般不会设置
SLAB_HWCACHE_ALIGN:要求所有数据对象和高速缓存行对齐
SLAB_CACHE_DMA:要求每个数据对象从可用于DMA的内存区段中分配
constructor和destructor是可选参数,前者用于初始化新分配的对象,后者用于清除对象。
下图表示了分配好的高速缓存:
创建好一个高速缓存对象后,我们再调用下面的API从中获取内存对象:
- void *kmem_cache_alloc(kmem_cache_t *cache,int flags);
参数cache是前面创建好的高速缓存,参数flags和kmalloc相同。
释放一个内存对象:
- void kmem_cache_free(kmem_cache_t *cache,const void *obj);
释放高速缓存:
- int kmem_cache_destroy(kmem_cache_t *cache);
只有在所有对象都归还后,该操作才会成功,应该检查该函数返回状态,如果失败(返回负值)说明发生了内存泄露,有些对象未被释放。
3. 内存池
为了确保内存分配的成功,内核开发者建立了一种称为内存池的抽象。内存池其实就是某种形式的后备高速缓存,它试图始终保存空闲的内存,以便在紧急状态下使用。
内核中内存池的对象类型为:mempool_t,用下面的函数来建立内存池对象:
- #include<linux/mempoll.h>
- mempool_t *mempoll_create(int min_nr,
- mempool_alloc_t *alloc_fn,
- mempool_free_t *free_fn,
- void *pool_data);
min_nr 表示内存池应该始终保持的已分配对象的最少数目。对象的实际分配和释放由后面两个函数alloc_fn和free_fn处理,定义如下:(但是通常我们用内核提供的两个函数mempool_alloc_slab,mempool_free_slab来完成)
- typedef void *(mempool_alloc_t)(int gfp_mask,void *pool_data);
- typedef void (mempool_free_t)(void *element,void *pool_data);
而mempool_create的第四个参数pool_data即为传入分配和释放函数的第二个参数。
构造内存池的通用写法:
首先,创建内存池:
- cache = kmem_cache_create(...);
- pool = mempool_create(MY_POOL_MININUM,mempool_alloc_slab,mempool_free_slab,cache);
然后用如下函数分配和释放对象:
- void *mempool_alloc(mempool_t *pool, int gfp_mask);
- void mempool_free(void *element,mempool_t *pool);
创建mempool时,会多次调用分配函数为预先分配的对象创建内存池,之后,对mempool_alloc的调用将首先通过分配函数获得该对象。如果该分配失败,就会返回预先分配的对象(如果存在的话)。
mempool_free释放一个对象,如果预先分配的对象数目小于要求的最低数目,该对象会保留在内存池中,否则,该对象返回给系统。
还可以利用下面函数调整mempool的大小:
- int mempool_resize(mempool_t *pool, int new_min_nr,int gfp_mask);
将内存池大小调整为至少有new_min_nr个预分配对象。
如果不在需要内存池,调用:
- void mempool_destroy(mempool_t *pool);
在销毁之前,必须要将多有分配的对象返回到内存池中,否则会引起oops。
驱动程序一般避免使用mempool。
4. get_free_pages和相关函数
如果模块需要分配大块的内存,使用面向页的分配技术会好些。分配页面涉及到的函数如下:
- get_zeroed_page(unsigned int flags);
- //返回指向新页面的指针并将页面清零。
- __get_free_page(unsigned int flags);
- // 类似于上述函数,但不清零页面。
- __get_free_pages(unsigned int flags,unsigned int order);
- //分配若干(物理连续的)页面,并返回指向该内存区域第一个字节的指针,但不清零页面。
释放页面涉及到的函数如下:
- void free_page(unsigned int addr);
- void free_pages(unsigned long addr,unsigned long order);
注意:分配和释放的页面数目必须相等,不然内存映射关系会破坏,系统会出错。
5.vmalloc及其辅助函数
vmalloc返回虚拟地址空间的连续区域,尽管在物理上不一定连续,vmalloc错误返回0(NULL地址),成功时一个指向线性的,大小最少为size的线性内存区域。
函数原型如下:
- #include<linux/vmalloc.h>
- void *vmalloc(unsigned long size);
- void vfree(void *addr);
- void *ioremap(unsigned long offset, unsigned long size);
- void iounmap(void *addr);
注意:kmalloc和__get_free_pages使用的地址范围和物理地址是一一对应的,可能会有一个PAGE_OFFSET的偏移,但是不需要为该地址段修改页表。但是vmalloc和ioremap使用的地址范围完全虚拟,每次分配要通过对页表的适当设置来建立内存区域。
vmalloc不能在原子上下文中使用,因为该函数内部调用了kmallc(GFP_KERNEL),可能导致休眠。
阅读(4538) | 评论(0) | 转发(0) |