linux 内核内存分配

1 kmalloc/kzalloc/kcalloc/kfree
  kamlloc函数原型如下:
  void *kmalloc(size_t size, int flags);
  该函数用于分配小块内存,返回分配的内存的指针.
  参数说明:
  size:需要分配的大小,单位是字节
  flags :分配标志主要有下面的参数
          GFP_KERNEL 表示在内核空间分配内存
          GFP_ATOMIC 表示该分配时不能睡眠的。如果内存空间不足,函数会一直等待,知道有足够的内存
          GFP_USER   表示在用户空间页来分配内存
          GFP_HIGHUSER 和GFP_USER类似, 但是是在高端内存分配
          GFP_DMA    表示分配在能够 用于DMA 的内存
          __GFP_REPEAT 表示是分配不成功" 更尽力些尝试" 通过重复尝试 -- 但是分配可能仍然失败.
          __GFP_NOFAIL 标志告诉分配器不要失败; 它尽最大努力来满足要求。使用 __GFP_NOFAIL 是强烈不推荐的;
          __GFP_NORETRY 告知分配器立即放弃如果得不到请求的内存.
          __GFP_ZERO 将分配的内存清零




  注意:kmalloc函数是有可能休眠的,如果在空间不足的情况下,kmalloc会睡眠等待有可用空间,所以如果在中断上下文中分配,一定要指定GFP_ATOMIC 
        kmalloc分配的内存大小是有限制的,在网上的很多文章中说是不能大于128k-16的空间,不过我在linux3.4的内核测试发现实际是可以分配4M-16的空间。
        kmalloc分配的空间是物理上连续的,如果我们分配的空间较大,而系统没有那么大的连续空间时,函数可能会失败。所以用kmalloc分配较大的空间时,分配的越早越容易成功。
        kmalloc分配的空间的地址不一定是页对齐的,在用于mmap的时候需要注意。最好用后面介绍的alloc_pages来分配。
        kmalloc分配的空间最小是32字节的,另外该函数分配的空间 可能实际占用的会略大于要求的空间大小。




   kzalloc的函数原型如下:
   void *kzalloc(size_t size, gfp_t flags);
   该函数和kmalloc的功能几乎一样的,唯一不同的是kmalloc只分配不清零,而kzalloc会将分配的空间清零。该函数分配的空间也是通过kfree来释放的。




   kcalloc的函数原型如下:
   void *kcalloc(size_t n, size_t size, gfp_t flags);
   这个函数和kmalloc的功能类似,用于分配一个数组,并且将分配的空降清零。实际上这个函数最后调用的也是kmalloc来实现的。该函数分配的空间也是通过kfree来释放的。
   参数说明:
   size_t n :分配的数组元素的个数
   size_t size:每个元素的尺寸
   gfp_t flags: 分配标志
   示例代码:
   int * ptr = (int*)kcalloc(100,sizeof(int),GFP_KERNEL);
   if(ptr!=NULL)
   {
                .........
                ....使用完
                kfree(ptr);
   }
  
   kfree函数原型如下:
   void kfree(const void *ptr);
   该函数的功能是释放由kmalloc()分配出来的内存块
   下面是示例代码:
   char * ptr = kmalloc(8*1024, GFP_KERNEL);
   if(ptr!=NULL)
   {
                .........
                ....使用完
                kfree(ptr);
   }




2 alloc_pages/alloc_page/free_pages/__get_free_pages
  alloc_page函数原型如下:




  struct page * alloc_pages(gfp_t gfp_mask, unsigned int order);
  该函数用于分配内存页,失败返回NULL
  gfp_mask 分配标志,参数和kmalloc是一样的,见上面的介绍
  order 要分配的页数,分配的页数为2的order次方。例如order 为0 就是分配一页也就是4096字节。order 为1就是分配两页。order 为2就是4页
  我们在实际使用中通常都是关心分配多少字节的,在这里linux已经提供了函数把字节数转为order 。函数为int get_order(unsigned long size)
  该函数的参数为要分配的字节数,返回order数




  alloc_page是一个宏定义如下:
  #define alloc_page(gfp_mask) alloc_pages(gfp_mask, 0)
  可见alloc_page就是只分配一个页的内存。
  
  注意:alloc_pages函数是有可能休眠的,如果在空间不足的情况下,alloc_pages会睡眠等待有可用空间,所以如果在中断上下文中分配,一定要指定GFP_ATOMIC 
        alloc_pages分配的内存大小是有限制的, 最大只能分配4MB的内存空间
        alloc_pages分配的空间是物理上连续的,且是页对齐的。如果我们分配的空间较大,而系统没有那么大的连续空间时,函数可能会失败。所以用kmalloc分配较大的空间时,分配的越早越容易成功。




  free_pages的函数原型如下:
  void free_pages(unsigned long addr, unsigned int order);
  该函数用于释放alloc_pages分配的内存。




  下面是示例代码:
          struct page *page;
          unsigned char *ptr_mem;
          page = alloc_pages(GFP_KERNEL,1);
          if(page==NULL)
          {
            printk(KERN_ERR"alloc_pages return error\r\n");
                ptr_mem=NULL;
          }
          else
          {
                ptr_mem= page_address(page); 
                if(ptr_mem==NULL)
                {
                        printk(KERN_ERR"page_address return error\r\n");
                        free_pages((unsigned long)page, 1);
                }
                else
                {
                      。。。。。。。//使用内存
                        free_pages(ptr_mem, 1);
                }
          }




  __get_free_pages函数原型如下:
  unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order);
  这个函数和alloc_pages的功能完全一样,唯一的不同就是这个函数直接返回了分配的内存地址,实际上这个函数就是内部就是直接调用了alloc_pages和page_address来分配内存页的。
  这个函数分配的内存也是通过free_pages来释放的。
  下面是示例代码:
  unsigned char *ptr_mem;
  ptr_mem = (unsigned char *)__get_free_pages(GFP_KERNEL,1);




3 vmalloc/vfree
  从上面个的介绍可以看到,无论是kmalloc还是alloc_pages分配的内存空间有限制,不能超过4MB。这对于现在越来越复杂的系统来说,可能是远远不够的。如果要分配大于4MB的内存,我们要用到vmalloc来分配内存了。
  vmalloc函数原型如下:
  void *vmalloc(unsigned long size);
  函数参数:size 是要分配的内存的尺寸,以字节为单位的。
  函数返回值为分派的虚拟地址的指针,如果失败返回NULL。




  注意事项:
  1 vmalloc可以分配的最大内存容量远大于kmalloc或alloc_pages,对于最大的分配限制,似乎没有一个统一的限制,取决于系统,我们可以通过"cat /proc/meminfo"来查看,在内容有VmallocTotal 项,就标示了最大的可分配内存。例如我的板上就是“VmallocTotal : 557056KB"
  2 vmalloc分配的内存只保证虚拟地址空间是连续的,但是在物理上不是连续的。所以vmalloc分配的内存不能用于DMA或者mmap。
  3 vmalloc同样会睡眠,所以不能在中断上下文或者其他不允许休眠的地方使用。
  
  下面是示例代码:
  ptr= vmalloc(100*1024*1024);//分配100MB
  .......//使用分配的内存
  vfree(ptr);




4 分配大于4MB的连续的物理内存的方法:
  在我们的驱动中,往往我们可能需要分配一段很大的连续内存用于硬件解码、图形加速之类的,这往往需要大于4MB的内存了。那么我们前面介绍的函数就无法满足要求了。
  下面我们介绍下如何分配大于4MB的连续内存。
  4.1 通过alloc_bootmem来分配内存。
  alloc_bootmem是在linux系统初始化的时候分配一块内存,这块内存将会被保留下来,在驱动中我们就可以直接使用这块内存了。
  
  alloc_bootmem函数原型如下:
  alloc_bootmem实际上是一个宏定义如下:
  #define alloc_bootmem(x) __alloc_bootmem(x, SMP_CACHE_BYTES, BOOTMEM_LOW_LIMIT)
  另外还有一个alloc_bootmem_align 定义如下:
  #define alloc_bootmem_align(x, align) __alloc_bootmem(x, align, BOOTMEM_LOW_LIMIT)
  和lloc_bootmem_pages 定义如下:
  #define alloc_bootmem_pages(x) __alloc_bootmem(x, PAGE_SIZE, BOOTMEM_LOW_LIMIT)
  这三个宏都是调用了__alloc_bootmem在初始化的时候分配一块连续内存。
  alloc_bootmem(x)是分配一块内存,x是要分配的内存的大小,字节为单位。
  alloc_bootmem_align(x, align)  在alloc_bootmem基础上增加了一个参数指定对齐方式
  alloc_bootmem_pages(x)是直接指定分配的内存是按页对齐的。
  这个函数返回的是虚拟地址,在驱动中可以直接使用。




  下面是示例代码:
  首先在setup_arch总调用alloc_bootmem分配内存。
  这个函数通常在/arch/xxx/kernel/setup.c这个文件中定义的,xxx表cpu的架构,例如arm,mips之类的。
  下面是代码:
  void * g_ReverseMem;
  EXPORT_SYMBOL(g_ReverseMem);//导出符号,在驱动中就可以直接使用了
  。。。。。。。  
  void __init setup_arch(char **cmdline_p)
  {
      ............
      g_ReverseMem = alloc_bootmem(50*1024*1024);//50MB的内存
      ............
  }




  在驱动中我们通过
  extern void * g_ReverseMem;
  就可以直接使用这块内存了。
  需要注意的是alloc_bootmem返回的虚拟地址的指针,如果在DMA中需要的是物理地址,那么我们就需要通过virt_to_phys来转换为物理地址来使用。
  
  释放:
  我们可以通过free_bootmem释放alloc_bootmem分配的内存
  free_bootmem函数原型如下:
  void free_bootmem(unsigned long addr, unsigned long size);
  下面是示例代码:
  free_bootmem(g_ReverseMem,50*1024*1024);




  4.2 通过memblock_reserve在linux系统初始化的时候,保留一块内存,供驱动来使用。
  memblock_reserve函数原型如下:
  int memblock_reserve(phys_addr_t base, phys_addr_t size);
  函数参数:base要保留的物理地址的起始地址
            size要保留的内存大小,单位是字节
  下面是示例代码:
  首先在xxx__reserve函数中调用memblock_reserve保留一块内存。
  xxx__reserve函数通常在arch/cpuxxx/mach-xxx/xxx.c文件下,cpuxxx是CPU的架构例如arm mips之类的,xxx是平台名,例如sunxi之类的。
  xxx__reserve将会赋值给machine_desc的结构的.reserve方法
  下面是示例代码
  在arch/cpuxxx/mach-xxx/xxx.c
  void __init xxx_reserve(void)
  {
                。。。。。。。
                memblock_reserve(RESERVE_MEMBASE, 0x3200000);//注意,这里的RESERVE_MEMBASE一定要是物理地址
                。。。。。。。
  }
  MACHINE_START(xxx, "xxx")
  ........
  .reserve = xxx_reserve,
  ........
  MACHINE_END




  在驱动中使用这段内存
  unsigned char * ptr= phys_to_virt( RESERVE_MEMBASE );
  
  注意事项,memblock_reserve(RESERVE_MEMBASE, 0x3200000);保留的这段内存一定要确保没有用作其他的地方,否则可能造成系统死掉。




5 SLAB内存管理
  当我们频繁分配和释放内存,往往会产生内存的碎片。linux引入了SLAB。slab分配器把这些需要分配的小块内存区作为对象。每一类对象分配一个cache,cache有一个或多个slab组成,slab由一个或多个物理页面组成。需要分配对象的时候从slab中空闲的对象取,用完了再放回slab中,而不是释放给物理页分配器,这样下次用的时候就不用重新初始化了,实现了对象的复用。从而解决了内存碎片的问题。
  下面介绍下相关的函数:
  struct kmem_cache *kmem_cache_create (const char *name, size_t size, size_t align, unsigned long flags,  void (*ctor)(void*, struct kmem_cache *, unsigned long));
  参数说明:name:用于/proc/slabinfo文件中确认此高速缓冲的字符串
       size:要创建的cache所对应对象的大小
       align:对象对齐偏移量
            flags:对应slab的标志。常用的标志有 
                  SLAB_RED_ZONE :在对象头、尾插入标志,用来支持对缓冲区溢出的检查。
                  SLAB_POISON 使用一种己知模式填充 slab,允许对缓存中的对象进行监视(对象属对象所有,不过可以在外部进行修改)。
                  SLAB_HWCACHE_ALIGN 指定缓存对象必须与硬件缓存行对齐。
            ctor:构建对象构造函数。这个函数将会在分配空间的时候由系统回调,我们可以在这个函数中对分配的内存做一些初始化之类的动作,如果没有什么特殊的处理,这个参数可以直接设置为NULL。
  函数成功时返回指向cache的指针,失败时返回NULL.
  注意:调用这个函数是不会分配内存的,只有在调用后面介绍的kmem_cache_alloc才会分配内存,并且回调ctor。




  void kmem_cache_destroy(struct kmem_cache * cache);
  参数说明:cache :kmem_cache_create 创建的struct kmem_cache结构的指针
  这个函数是和kmem_cache_create 成对使用的,用来销毁kmem_cache_create 创建的高速缓存。
  注意在调用这个函数的时候一定要确保kmem_cache_alloc分配的内存被释放了。




  void *kmem_cache_alloc( struct kmem_cache *cachep, gfp_t flags );
  参数说明:cachep :kmem_cache_create 创建的struct kmem_cache结构的指针
            flags :分配标志,GFP_KERNEL、GFP_ATOMIC等等,详细的参考前面kmalloc的参数的介绍。
  这个函数从缓存中分配一个对象,返回内存的指针。这个函数分配的内存可能会大于kmem_cache_create中指定的size_t size,也就是说这个函数可能会分配一大块内存,那么就会分配多个对象,而之后调用kmem_cache_alloc的时候,就直接从之前分配的对象中分配一个对象返回。
  注意这个函数有可能会睡眠,如果在中断上下文中调用,一定要设置GFP_ATOMIC 。




  void kmem_cache_free( struct kmem_cache *cachep, void *objp );
  参数说明:cachep:kmem_cache_create 创建的struct kmem_cache结构的指针
            objp : kmem_cache_alloc分配的内存的指针
  
  下面是示例代码:
    void slab_ctor(void *cachep)
    {  
            printk("slab_ctor is called! \n");  
            memset(cachep,0,100); 
    }  
    struct kmem_cache *test_cachep = NULL; 
    unsigned char * object1 ;
    unsigned char * object2 ;
    test_cachep = kmem_cache_create("slab_test", 100, 0, SLAB_HWCACHE_ALIGN, slab_ctor);  
    if(!test_cachep)  
        return -ENOMEM;  
        
     
    printk("start kmem_cache_alloc!\n");   
    object1 = kmem_cache_alloc(test_cachep, GFP_KERNEL);      
    if(object1==NULL)  
        return -ENOMEM;  
     
    object2 = kmem_cache_alloc(test_cachep, GFP_KERNEL);  
    if(object2==NULL)  
        return -ENOMEM;  
    ...........
    //使用内存




    .........
    //释放
    if(object1) kmem_cache_free(test_cachep, object1);  
    if(object2) kmem_cache_free(test_cachep, object2); 




    if(test_cachep)  
        kmem_cache_destroy(test_cachep);  




6 内存池
  在驱动开发中,有的时候是不允许内存分配失败的,因此在linux2.6之后引入了内存池。
  内存池其实就是某种形式的后备高速缓存,它试图始终保存空闲的内存,以便在紧急状态下使用。
  下面先介绍下相关的函数:




  mempool_t *mempool_create(int min_nr, mempool_alloc_t *alloc_fn, mempool_free_t *free_fn, void *pool_data);
  参数说明:min_nr 参数是内存池应当一直保留的最小数量的分配对象,在调用mempool_create将创建min_nr个内存块先预留下来。
  实际的分配和释放对象由 alloc_fn 和 free_fn 处理, 这两个参数是函数的指针,如没有什么特殊要求,可以直接用linux定义好的mempool_alloc_slab、mempool_free_slab函数来处理。
  pool_data 实际传递给alloc_fn 和 free_fn 的参数
  这个函数将创建一个内存池,并返回mempool_t 的指针。








  void *mempool_alloc(mempool_t *pool, int gfp_mask);
  从内存池中分配内存,返回内存块的指针。
  参数说明:pool :mempool_create创建的内存池的指针
            gfp_mask  :分配标志,和kmem_cache_alloc类似,这里不详细介绍了。
  
  void mempool_free(void *element, mempool_t *pool);
  释放mempool_alloc分配的内存
  参数说明: element:mempool_alloc分配的内存指针
      pool : mempool_create创建的内存池的指针




  
  void mempool_destroy(mempool_t *pool);
  销毁已经创建的内存池




  下面是示例代码:
    struct kmem_cache *test_cachep = NULL;  
    unsigned char *object1 ;
    unsigned char *object2 ;  
    mempool_t * my_pool = NULL; 




    test_cachep = kmem_cache_create("slab_test", 100, 0, SLAB_HWCACHE_ALIGN, slab_ctor);  
    if(!test_cachep)  
        return -ENOMEM;  
        
    my_pool = mempool_create(10, mempool_alloc_slab, mempool_free_slab, test_cachep);  
    if(!my_pool)  
        return -ENOMEM; 
    printk("start mempool_alloc!\n");   
    object1 = mempool_alloc(my_pool, GFP_KERNEL);    
    if(!object1)  
        return -ENOMEM;  
    
    object2 = mempool_alloc(my_pool, GFP_KERNEL); 
    if(!object2)  
        return -ENOMEM;  
    ...................
    //使用内存
    ..................
    //释放
    mempool_free(object1,my_pool);
    mempool_free(object2,my_pool);
    if(my_pool){  
        mempool_destroy(my_pool);  
    
    }   
    if(test_cachep)  
        kmem_cache_destroy(test_cachep);  
  说明:在调用  mempool_create的时候,系统会先创建10个test_cachep 的缓冲区。
        在调用mempool_alloc系统首先会在系统公共的高速缓存中分配空间,如果分配不成功,才会从mempool_create创建的10个test_cachep 中分配内存。
        也就是说mempool_create首先会预留一部分内存,只有在系统内存不足,无法分配的情况下,才会从预留的内存中分配,这样就保证了在内存很紧张的时候还是可以分配。但是如果预留的10个内存也分配完了,那就还是会返回失败的(返回NULL)。




7 虚拟地址和物理地址的转换:
  void *phys_to_virt(phys_addr_t x);//物理地址装换为虚拟地址
  phys_addr_t virt_to_phys(const volatile void *x);//虚拟地址转换为物理地址
  
  
  
  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值