Chinaunix首页 | 论坛 | 博客
  • 博客访问: 110572
  • 博文数量: 23
  • 博客积分: 471
  • 博客等级: 一等列兵
  • 技术积分: 251
  • 用 户 组: 普通用户
  • 注册时间: 2010-12-21 15:21
文章分类
文章存档

2017年(1)

2013年(2)

2011年(20)

分类: LINUX

2011-08-26 16:05:53

先上几个图,大家可以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,可以通过下面的函数调用:
   
  1. #inlcude<linux/slab.h>
  2. kmem_cache_t *kmem_cache_create(const char *name,size_t size,
  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,用下面的函数来建立内存池对象:

  1. #include<linux/mempoll.h>
  2. mempool_t *mempoll_create(int min_nr,
  3.                           mempool_alloc_t *alloc_fn,
  4.                           mempool_free_t *free_fn,
  5.                           void *pool_data);

min_nr 表示内存池应该始终保持的已分配对象的最少数目。对象的实际分配和释放由后面两个函数alloc_fn和free_fn处理,定义如下:(但是通常我们用内核提供的两个函数mempool_alloc_slab,mempool_free_slab来完成)

  1. typedef void *(mempool_alloc_t)(int gfp_mask,void *pool_data);
  2. typedef void (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(unsigned int flags);
  4.    // 类似于上述函数,但不清零页面。
  5. __get_free_pages(unsigned int flags,unsigned int order);
  6.    //分配若干(物理连续的)页面,并返回指向该内存区域第一个字节的指针,但不清零页面。

   释放页面涉及到的函数如下:

  1. void free_page(unsigned int addr);
  2. void free_pages(unsigned long addr,unsigned long order);

   注意:分配和释放的页面数目必须相等,不然内存映射关系会破坏,系统会出错。

  5.vmalloc及其辅助函数

    vmalloc返回虚拟地址空间的连续区域,尽管在物理上不一定连续,vmalloc错误返回0(NULL地址),成功时一个指向线性的,大小最少为size的线性内存区域。

    函数原型如下:

  1. #include<linux/vmalloc.h>

  2. void *vmalloc(unsigned long size);
  3. void vfree(void *addr);
  4. void *ioremap(unsigned long offset, unsigned long size);
  5. void iounmap(void *addr);

   注意:kmalloc和__get_free_pages使用的地址范围和物理地址是一一对应的,可能会有一个PAGE_OFFSET的偏移,但是不需要为该地址段修改页表。但是vmalloc和ioremap使用的地址范围完全虚拟,每次分配要通过对页表的适当设置来建立内存区域。

        vmalloc不能在原子上下文中使用,因为该函数内部调用了kmallc(GFP_KERNEL),可能导致休眠。

阅读(4455) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~