Chinaunix首页 | 论坛 | 博客
  • 博客访问: 121433
  • 博文数量: 41
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 22
  • 用 户 组: 普通用户
  • 注册时间: 2013-01-25 16:01
文章分类
文章存档

2015年(6)

2014年(22)

2013年(13)

我的朋友

分类: LINUX

2015-01-09 10:11:13

原文地址:linux的内存开辟 作者:steven_miao

内存开辟
谨以此文纪念过往的岁月。
内存的分配和管理在linux的内核中是一个巨头。在此仅记录个人理解,如有错误请指正。
1.kmalloc
在内存开辟中kmalloc的使用概率很高,在通常的内存开辟中均会使用该函数来开辟内存。但是分配的区域仍然保持原有的数据,一般需要清零。
函数原型:
void *kmalloc(size_t size ,int flags);
参数size很好理解,即是分配多大的内存,以字节为单位。flags也很明白,即是分配的标志,来控制kmalloc的行为。
flags一般常用的标志有GFP_KERNEL,GFP_ATOMIC,__GFP_DMA。简单说一下,GFP_KERNEL指由内核进程来执行的。使用这个flag时,函数会被阻塞,因为kmalloc允许当空闲页比较少的时候,会睡眠等待空闲页。而GFP_ATOMIC则不然,他会立即调用内核预留的一些空闲页,如果所有的预留都没有了,就会立即返回错误。
该标志一般用于中断处理中。kmalloc一般最下处理32或64字节,最大一般不超过128K。
kmalloc的源码如下
static __always_inline void *kmalloc(size_t size, gfp_t flags)
{
 if (__builtin_constant_p(size)) {   --检测size是否为常量
  if (size > PAGE_SIZE)             --如果开辟的空间大于1页的话,则会采用(void *)__get_free_pages(flags | __GFP_COMP, get_order(size));的办法开辟
   return kmalloc_large(size, flags);
  if (!(flags & SLUB_DMA)) {           --如果flags并没有被指定为__GFP_DMA,即是在DMA区开辟的话,则采用从已经创建了对象的高速缓存中分配
   struct kmem_cache *s = kmalloc_slab(size);   --这个则是查询适合大小的内存对象
   if (!s)
    return ZERO_SIZE_PTR;
   return kmem_cache_alloc(s, flags); --分配对应的内存对象
  }
 }
 return __kmalloc(size, flags);    --如果不是常量的话
}
void *__kmalloc(size_t size, gfp_t flags)  --  这个函数与上面的类似可以说几乎是一样的,不知道这样设计的含义
{
 struct kmem_cache *s;
 if (unlikely(size > PAGE_SIZE))
  return kmalloc_large(size, flags);
 s = get_slab(size, flags);
 if (unlikely(ZERO_OR_NULL_PTR(s)))  -- unlikely这个东西有没有是一样的,只不过说明大多数情况下条件是为否的。这个是用于编译加速的。这个是告诉编译器,这个条件大多数是否
  return s;
 return slab_alloc(s, flags, -1, _RET_IP_);  --kmem_cache_alloc的函数直接会调用这个函数。
}  
至于__get_free_pages和如何从后备高速缓存中开辟对象,这个在理解内存管理的时候再去理解。到此可以说对于小页的ARCH(即PAGE_SIZE=4K),kmalloc的size小于PAGE_SIZE时,会采用从已经创建好了的高速缓存中分配内存对象。如果size不是2^n的话,会多开辟内存,有时最多开辟两倍的内存,比如size=2^n+1时,最后开辟的内存会是2^(n+1),不过这种情况出现在size大于256时。为什么这么说呢?看下面的源码就可以理解了在kmalloc_slab函数中会调用kmalloc_index来查询size所对应的高速缓存对象。至于为什么会出现这种情况,以后在看内存预分配高速缓冲时再说。源码如下:
static __always_inline int kmalloc_index(size_t size)
{
 if (size <= KMALLOC_MIN_SIZE)
  return KMALLOC_SHIFT_LOW;
#if KMALLOC_MIN_SIZE <= 64     KMALLOC_MIN_SIZE在arm平台下为 8
 if (size > 64 && size <= 96)
  return 1;
 if (size > 128 && size <= 192)
  return 2;
#endif
 if (size <=          8) return 3;  
 if (size <=         16) return 4;
 if (size <=         32) return 5;
 if (size <=         64) return 6;
 if (size <=        128) return 7;     
 if (size <=        256) return 8;
 if (size <=        512) return 9;
 if (size <=       1024) return 10;
 if (size <=   2 * 1024) return 11;
 if (size <=   4 * 1024) return 12;
 --下面是为了支持大页的
 if (size <=   8 * 1024) return 13;
 if (size <=  16 * 1024) return 14;
 if (size <=  32 * 1024) return 15;
 if (size <=  64 * 1024) return 16;
 if (size <= 128 * 1024) return 17;
 if (size <= 256 * 1024) return 18;
 if (size <= 512 * 1024) return 19;
 if (size <= 1024 * 1024) return 20;
 if (size <=  2 * 1024 * 1024) return 21;
 return -1;
}
这这里主要是理解kmalloc时,size的大小与真实内核分配内存间的关系,以及对于不同的开辟不同的内存大小则采用不同的办法去开辟。对于大于PAGE_SIZE的size采用__get_free_pages开辟,而小于PAGE_SIZE的则采用从已经预创建好的高速缓存对象中开辟。
2.vmalloc
vmalloc分配的内存在虚拟空间是连续的,而在物理空间可能是不连续的。适用于开辟大块连续的,仅仅在软件中存在,用于缓冲的内存。vmalloc需要查询内核虚拟空间、的空闲节点,在空闲区域内创建页表。其所消耗的资源远比kmalloc多。所以不适合适用vmalloc去开辟小的内存,这种情况下,效率会很低。至于vmalloc如何去开辟内存空间以及创建新的页表,还是来看源码的实现。在这里不怎么考虑标志符的影响,先考虑一般情况下的vmalloc的实现 vmalloc函数实现在vmalloc.c中vmalloc->__vmalloc_node,__vmalloc_node除去检测的代码,其主要功能就是调用下面的两个函数。
static void *__vmalloc_node(unsigned long size, gfp_t gfp_mask, pgprot_t prot,int node, void *caller)
{
 struct vm_struct *area;
 area = __get_vm_area_node(size, VM_ALLOC, VMALLOC_START, VMALLOC_END,node, gfp_mask, caller);
 
--- #define VMALLOC_OFFSET  (8*1024*1024)
---  #define VMALLOC_START  (((unsigned long)high_memory + VMALLOC_OFFSET) & ~(VMALLOC_OFFSET-1))   查询的虚拟地址主要是从高地址开始查询,不过
     arm平台high_memory为0。
---  VMALLOC_END  0xE0000000
 return __vmalloc_area_node(area, gfp_mask, prot, node, caller);
}
__get_vm_area_node主要是查询内核虚拟空间中的空闲节点,从start到end之间查找一个空闲的vm块
static struct vm_struct *__get_vm_area_node(unsigned long size,unsigned long flags, unsigned long start, unsigned long end,int node, gfp_t gfp_mask, void *caller)
{
 static struct vmap_area *va;
 struct vm_struct *area;
 struct vm_struct *tmp, **p;
 unsigned long align = 1;
 size = PAGE_ALIGN(size);
 area = kmalloc_node(sizeof(*area), gfp_mask & GFP_RECLAIM_MASK, node);   --开辟内存
 --需要多开辟一段信息页
 size += PAGE_SIZE;
 va = alloc_vmap_area(size, align, start, end, node, gfp_mask);  --根据从内核虚拟空间中查询出一段大小大于size的虚拟内存空间。这个是怎么实现的会在理解虚拟内存的时候去理解。这儿就略过。
  --将对虚拟空间块的描述赋值 vm_struct
 area->flags = flags;
 area->addr = (void *)va->va_start;
 area->size = size;
 area->pages = NULL;
 area->nr_pages = 0;
 area->phys_addr = 0;
 area->caller = caller;
 va->private = area;
 va->flags |= VM_VM_AREA;
 write_lock(&vmlist_lock);
 for (p = &vmlist; (tmp = *p) != NULL; p = &tmp->next) {
  if (tmp->addr >= area->addr)
   break;
 }
 area->next = *p;    --将该vm块添加到链表
 *p = area;
 write_unlock(&vmlist_lock);
 return area;
}
__get_vm_area_node从内核空间查询到大小满足size,一般是大于size的,并且地址连续的一个vm块,并且对vm_struct进行赋值。而在__vmalloc_area_node中则是将上面查找的vm_struct的内存进行创建新页。在上面函数中只是查询到一段连续的内存,并没有将内存分页,创建新的页链表
static void *__vmalloc_area_node(struct vm_struct *area, gfp_t gfp_mask,pgprot_t prot, int node, void *caller)
{
 struct page **pages;
 unsigned int nr_pages, array_size, i;
 nr_pages = (area->size - PAGE_SIZE) >> PAGE_SHIFT;
 array_size = (nr_pages * sizeof(struct page *));
 area->nr_pages = nr_pages;
 /* Please note that the recursion is strictly bounded. */
 if (array_size > PAGE_SIZE) {       --如果所需要存储页的信息超过了4K,则需要对页信息指针进行二级处理。因为在查询内核空间时,仅多开辟一页的内存用于存储页信息,如果页信息超过了一页,则需要重新去内核虚拟内存中开辟一个新页来查询,这其实是一个嵌套,使用二级页目录。
  pages = __vmalloc_node(array_size, gfp_mask | __GFP_ZERO,PAGE_KERNEL, node, caller);
  area->flags |= VM_VPAGES;
 } else {
  pages = kmalloc_node(array_size,(gfp_mask & GFP_RECLAIM_MASK) | __GFP_ZERO,node);       --否则则直接开辟空间,在node节点内。
 }
 area->pages = pages;
 area->caller = caller;
 if (!area->pages) {
  remove_vm_area(area->addr);
  kfree(area);
  return NULL;
 }
 for (i = 0; i < area->nr_pages; i++) {
  struct page *page;
  if (node < 0)
   page = alloc_page(gfp_mask);
  else
   page = alloc_pages_node(node, gfp_mask, 0);  --这儿就是在node节点中创建新页。不过这儿的node应该不是0
  area->pages[i] = page;
 }
 return area->addr;
}
到此vmalloc就开辟出新的虚拟并连续的内存。vmalloc其实是在内核虚拟空间中找出满足size的内存节点,然后在该节点中创建新的页表。这也是为什么在开辟小的内存时采用vmalloc是不划算的。而且vmalloc对于物理内存而言并没有什么意义,因为在物理内存中,vmalloc开辟的内存并不连续。其主要是对软件有意义。
3.__get_free_pages
在上面也提到__get_free_pages,该函数会在kmalloc开辟空间大于PAGE_SIZE时调用。
__get_free_pages ->alloc_pages ->alloc_pages_node->__alloc_pages->__alloc_pages_internal 经过七拐八拐的最终会分配新页这个函数,这个函数是zone区分配器的核心。
__alloc_pages_internal这个函数以后再了解内存管理时,再好好理解,现在很难。
4.kmem_cache_create与kmem_cache_alloc
这两个函数是组合起来一起用,kmem_cache_create用于创建一个高速缓冲对象,而kmem_cache_alloc用于分配高速缓冲对象内存。这个一般用于需要频繁开辟和注销同一种结构的对象,这个也是用于加速。
5.dma_alloc_coherent
这个函数分配缓冲区并且重新映射。分配的内存可以用于DMA的传输。
6. alloc_bootmem
有时候某些内核模块需要开辟较大的内存如几M的内存空间,如果在内存管理建立后去开辟容易流于失败,通常会在引导程序时提前开辟,这样会跳过内核的内存管理,对内存管理而言,这样的空间是不可见的。这样会减少留给操作系统的RAM。这段内存也需要自己去管理。这个我喜欢!!
7. ioremap
ioremap这个函数不能是一个真正的内存分配,不过他也是一个内存分配,为什么这么说呢?因为ioremap的内存可以将内存管理系统看不见的硬件内存映射到虚拟内存中,说他是内存分配,因为他是分配纯虚拟空间。说他不是,因为他不分配真实的物理空间,他是一种映射。
例如:现在物理内存128M,但是在传替给内核参数时却是 “mem = 124M”,那对用linux系统而言,真实的内存就是124M那还有的4M就无法使用了。这个时候就可以采用ioremap的办法,将在流离在外的4M给弄来,ioremap(0x7C00000,0x400000);这样就可以将那4M给映射到内存中,不过这个还是需要自己去管理的。
 
以上就是一些内存分配的常用的机制。至于具体的实现办法以后再学习内存管理机制的时候,在去看看。

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