http://blog.chinaunix.net/uid-20807528-id-2390867.html
内存管理
下面介绍内核分配内存的API以及使用方法注意事项。
1.kmalloc
原型如下:
#include<linux/slab.h>
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,可以通过下面的函数调用:
1. #inlcude<linux/slab.h>
2. kmem_cache_t *kmem_cache_create(const char *name,size_tsize,
3. size_t offset,
4. unsigned long flags,
5. void (*constructor)(void*,kmem_cache_t *,
6. unsigned long flags),
7. void (*destructor)(void*,kmem_cache_t *,
8. unsigned long falgs));
该函数创建一个高速缓存对象,这些区域大小相同,区域大小由size指定;name通常设定为将要高速缓存的结构类型的名字,指向一个常量字符串;offset表示页面中第一个对象的偏移量,默认为0;flags表示如何完成分配,是一个位掩码,取值有:
SLAB_NO_REAP:保护缓存在系统寻找时不会减少,一般不会设置
SLAB_HWCACHE_ALIGN:要求所有数据对象和高速缓存行对齐
SLAB_CACHE_DMA:要求每个数据对象从可用于DMA的内存区段中分配
constructor和destructor是可选参数,前者用于初始化新分配的对象,后者用于清除对象。
下图表示了分配好的高速缓存:
创建好一个高速缓存对象后,我们再调用下面的API从中获取内存对象:
1. void *kmem_cache_alloc(kmem_cache_t *cache,int flags);
参数cache是前面创建好的高速缓存,参数flags和kmalloc相同。
释放一个内存对象:
1. void kmem_cache_free(kmem_cache_t *cache,const void *obj);
释放高速缓存:
1. 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);
typedefvoid (mempool_free_t)(void*element,void *pool_data);
而mempool_create的第四个参数pool_data即为传入分配和释放函数的第二个参数。
构造内存池的通用写法:
首先,创建内存池:
1. cache = kmem_cache_create(...);
2. pool =mempool_create(MY_POOL_MININUM,mempool_alloc_slab,mempool_free_slab,cache);
然后用如下函数分配和释放对象:
1. void *mempool_alloc(mempool_t *pool,int gfp_mask);
2. void mempool_free(void*element,mempool_t *pool);
创建mempool时,会多次调用分配函数为预先分配的对象创建内存池,之后,对mempool_alloc的调用将首先通过分配函数获得该对象。如果该分配失败,就会返回预先分配的对象(如果存在的话)。
mempool_free释放一个对象,如果预先分配的对象数目小于要求的最低数目,该对象会保留在内存池中,否则,该对象返回给系统。
还可以利用下面函数调整mempool的大小:
1. int mempool_resize(mempool_t *pool,int new_min_nr,int gfp_mask);
将内存池大小调整为至少有new_min_nr个预分配对象。
如果不在需要内存池,调用:
1. void mempool_destroy(mempool_t *pool);
在销毁之前,必须要将多有分配的对象返回到内存池中,否则会引起oops。
驱动程序一般避免使用mempool。
4.get_free_pages和相关函数
如果模块需要分配大块的内存,使用面向页的分配技术会好些。分配页面涉及到的函数如下:
1. get_zeroed_page(unsigned int flags);
2. //返回指向新页面的指针并将页面清零。
3. __get_free_page(unsignedint flags);
4. //类似于上述函数,但不清零页面。
5. __get_free_pages(unsignedint flags,unsignedint order);
6. //分配若干(物理连续的)页面,并返回指向该内存区域第一个字节的指针,但不清零页面。
释放页面涉及到的函数如下:
1. void free_page(unsigned int addr);
2. void free_pages(unsignedlong addr,unsigned long order);
注意:分配和释放的页面数目必须相等,不然内存映射关系会破坏,系统会出错。
5.vmalloc及其辅助函数
vmalloc返回虚拟地址空间的连续区域,尽管在物理上不一定连续,vmalloc错误返回0(NULL地址),成功时一个指向线性的,大小最少为size的线性内存区域。
函数原型如下:
1. #include<linux/vmalloc.h>
2.
3. void *vmalloc(unsigned long size);
4. void vfree(void *addr);
5. void *ioremap(unsigned long offset,unsigned long size);
6. void iounmap(void *addr);
注意:kmalloc和__get_free_pages使用的地址范围和物理地址是一一对应的,可能会有一个PAGE_OFFSET的偏移,但是不需要为该地址段修改页表。但是vmalloc和ioremap使用的地址范围完全虚拟,每次分配要通过对页表的适当设置来建立内存区域。
vmalloc不能在原子上下文中使用,因为该函数内部调用了kmallc(GFP_KERNEL),可能导致休眠。
内存池
http://blog.chinaunix.net/uid-25968088-id-3336362.html
内存池(MemeryPool)技术是在真正使用内存之前,先申请分配一定数量的、大小相等(一般情况下)的内存块留作备用。当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够再继续申请新的内存。这样做的一个显著优点是尽量避免了内存碎片,使得内存分配效率得到提升。
不仅在用户态应用程序中被广泛使用,同时在Linux内核也被广泛使用,在内核中有不少地方内存分配不允许失败。作为一个在这些情况下确保分配的方式,内核开发者创建了一个已知为内存池(或者是 "mempool" )的抽象,内核中内存池真实地只是相当于后备缓存,它尽力一直保持一个空闲内存列表给紧急时使用,而在通常情况下有内存需求时还是从公共的内存中直接分配,这样的做法虽然有点霸占内存的嫌疑,但是可以从根本上保证关键应用在内存紧张时申请内存仍然能够成功。
下面看下内核内存池的源码,内核内存池的源码在<mm/mempool.c>中,实现上非常简洁,描述内存池的结构mempool_t在头文件<linux/mempool.h>中定义,结构描述如下:
1. typedef struct mempool_s {
2. spinlock_t lock; /*保护内存池的自旋锁*/
3. intmin_nr; /*内存池中最少可分配的元素数目*/
4. intcurr_nr; /*尚余可分配的元素数目*/
5. void **elements; /*指向元素池的指针*/
6. void *pool_data; /*内存源,即池中元素真实的分配处*/
7. mempool_alloc_t *alloc; /*分配元素的方法*/
8. mempool_free_t *free; /*回收元素的方法*/
9. wait_queue_head_t wait; /*被阻塞的等待队列*/
10. }mempool_t;
内存池的创建函数mempool_create的函数原型如下:
1. mempool_t *mempool_create(int min_nr, mempool_alloc_t *alloc_fn,
2. mempool_free_t*free_fn,void *pool_data)
3. {
4. return mempool_create_node(min_nr,alloc_fn,free_fn,pool_data,-1);
5. }
函数原型指定内存池可以容纳元素的个数、申请元素的方法、释放元素的方法,以及一个可选的内存源(通常是一个cache),内存池对象创建完成后会自动调用alloc方法从pool_data上分配min_nr个元素用来填充内存池。
内存池的释放函数mempool_destory函数的原型很简单,应该也能猜到是依次将元素对象从池中移除,再释放给pool_data,最后释放池对象,如下:
1. void mempool_destroy(mempool_t*pool)
2. {
3. while(pool->curr_nr) {
4. void *element = remove_element(pool);
5. pool->free(element, pool->pool_data);
6. }
7. kfree(pool->elements);
8. kfree(pool);
9. }
值得注意的是内存池分配和回收对象的函数:mempool_alloc和mempool_free。mempool_alloc的作用是从指定的内存池中申请/获取一个对象,函数原型如下:
1. void * mempool_alloc(mempool_t *pool, gfp_t gfp_mask){
2. ......
3. element= pool->alloc(gfp_temp, pool->pool_data);
4. if(likely(element != NULL))
5. returnelement;
6.
7. spin_lock_irqsave(&pool->lock, flags);
8. if(likely(pool->curr_nr)) {
9. element =remove_element(pool);/*从内存池中提取一个对象*/
10. spin_unlock_irqrestore(&pool->lock, flags);
11. /* paired with rmb inmempool_free(), read comment there */
12. smp_wmb();
13. returnelement;
14. }
15. ......
16.
17. }
函 数先是从pool_data中申请元素对象,当从pool_data无法成功申请到时,才会从池中提取对象使用,因此可以发现内核内存池mempool其 实是一种后备池,在内存紧张的情况下才会真正从池中获取,这样也就能保证在极端情况下申请对象的成功率,单也不一定总是会成功,因为内存池的大小毕竟是有 限的,如果内存池中的对象也用完了,那么进程就只能进入睡眠,也就是被加入到pool->wait的等待队列,等待内存池中有可用的对象时被唤醒, 重新尝试从池中申请元素:
1. init_wait(&wait);
2. prepare_to_wait(&pool->wait, &wait, TASK_UNINTERRUPTIBLE);
3. spin_unlock_irqrestore(&pool->lock, flags);
4. io_schedule_timeout(5*HZ);
5. finish_wait(&pool->wait, &wait);
池回收对象的函数mempool_free的原型如下:
1. void mempool_free(void *element, mempool_t *pool)
2. {
3. if(pool->curr_nr < pool->min_nr) {
4. spin_lock_irqsave(&pool->lock,flags);
5. if(pool->curr_nr < pool->min_nr) {
6. add_element(pool,element);
7. spin_unlock_irqrestore(&pool->lock,flags);
8. wake_up(&pool->wait);
9. return;
10. }
11. spin_unlock_irqrestore(&pool->lock,flags);
12. }
13. pool->free(element,pool->pool_data);
14. }
其实原则跟mempool_alloc是对应的,释放对象时先看池中的可用元素是否充足(pool->curr_nr == pool->min_nr),如果不是则将元素对象释放回池中,否则将元素对象还给pool->pool_data。
此外mempool也提供或者说指定了几对alloc/free函数,及在mempool_create创建池时必须指定的alloc和free函数,分别适用于不同大小或者类型的元素的内存池,具体如下:
1. void *mempool_alloc_slab(gfp_t gfp_mask,void *pool_data)
2. {
3. struct kmem_cache *mem =pool_data;
4. return kmem_cache_alloc(mem, gfp_mask);
5. }
6. void mempool_free_slab(void*element, void *pool_data)
7. {
8. struct kmem_cache *mem =pool_data;
9. kmem_cache_free(mem, element);
10. }
11.
12. void *mempool_kmalloc(gfp_t gfp_mask,void *pool_data)
13. {
14. size_t size = (size_t)pool_data;
15. return kmalloc(size, gfp_mask);
16. }
17. void mempool_kfree(void*element, void *pool_data)
18. {
19. kfree(element);
20. }
21.
22. void *mempool_alloc_pages(gfp_t gfp_mask,void *pool_data)
23. {
24. intorder = (int)(long)pool_data;
25. return alloc_pages(gfp_mask,order);
26. }
27. void mempool_free_pages(void*element, void *pool_data)
28. {
29. intorder = (int)(long)pool_data;
30. __free_pages(element, order);
31. }
总体上来讲mempool的实现很简约,但是不简单,而且非常轻便易用,这也是内核奥妙之所在。