1.malloc 和free的原理(http://m.blog.csdn.net/article/details?id=39496057)
从操作系统角度来看,进程分配内存有两种方式,分别由两个系统调用完成:brk和mmap(不考虑共享内存)。
1、brk是将数据段(.data)的最高地址指针_edata往高地址推;
2、mmap是在进程的虚拟地址空间中(堆和栈中间,称为文件映射区域的地方)找一块空闲的虚拟内存。
这两种方式分配的都是虚拟内存,没有分配物理内存。在第一次访问已分配的虚拟地址空间的时候,发生缺页中断,操作系统负责分配物理内存,然后建立虚拟内存和物理内存之间的映射关系。
在标准C库中,提供了malloc/free函数分配释放内存,这两个函数底层是由brk,mmap,munmap这些系统调用实现的
情况一、malloc小于128k的内存,使用brk分配内存,将_edata往高地址推(只分配虚拟空间,不对应物理内存(因此没有初始化),第一次读/写数据时,引起内核缺页中断,内核才分配对应的物理内存,然后虚拟地址空间建立映射关系)
情况二、malloc大于128k的内存,使用mmap分配内存,在堆和栈之间找一块空闲内存分配(对应独立内存,而且初始化为0)
当最高地址空间的空闲内存超过128K(可由M_TRIM_THRESHOLD选项调节)时,执行内存紧缩操作(trim)
内存池
C/C++中直接使用系统调用malloc/free、new/delete进行内存分配和释放,有以下弊端:
内存池(memory pool)是代替直接调用malloc/free、new/delete进行内存管理的常用方法,当我们申请内存空间时,首先到我们的内存池中查找合适的内存块,而不是直接向操作系统申请,优势在于:
http://blog.csdn.net/shawngucas/article/details/6574863
LINUX内核内存管理kmalloc,vmalloc
在设备驱动程序或者内核模块中动态开辟内存,不是用malloc,而是kmalloc ,vmalloc,释放内存用的是kfree,vfree,kmalloc函数返回的是虚拟地址(线性地址). kmalloc特殊之处在于它分配的内存是物理上连续的,这对于要进行DMA的设备十分重要. 而用vmalloc分配的内存只是线性地址连续,物理地址不一定连续,不能直接用于DMA。
vmalloc函数的工作方式类似于kmalloc,只不过它分配的内存虚拟地址是连续的,而物理地址则无需连续。通过vmalloc获得的页必须一个一个地进行映射,效率不高, 因此,只在不得已(一般是为了获得大块内存)时使用。vmalloc函数返回一个指针,指向逻辑上连续的一块内存区,其大小至少为size。在发生错误 时,函数返回NULL。vmalloc可能睡眠,因此,不能从中断上下文中进行调用,也不能从其它不允许阻塞的情况下调用。要释放通过vmalloc所获 得的内存,应使用vfree函数
vmalloc和kmalloc区别大概可总结为:
1,vmalloc分配的一般为高端内存,只有当内存不够的时候才分配低端内存;kmallco从低端内存分配。
2,vmalloc分配的物理地址一般不连续,而kmalloc分配的地址连续,两者分配的虚拟地址都是连续的;
3,vmalloc分配的一般为大块内存,而kmaooc一般分配的为小块内存,(一般不超过128k);
slab分配器是Linux内存管理中非常重要和复杂的一部分,其工作是针对一些经常分配并释放的对象,如进程描述符等,这些对象的大小一般比较小,如果直接采用伙伴系统来进行分配和释放,不仅会造成大量的内碎片,而且处理速度也太慢。而slab分配器是基于对象进行管理的,相同类型的对象归为一类(如进程描述符就是一类),每当要申请这样一个对象,slab分配器就从一个slab列表中分配一个这样大小的单元出去,而当要释放时,将其重新保存在该列表中,而不是直接返回给伙伴系统。slab分配对象时,会使用最近释放的对象内存块,因此其驻留在CPU高速缓存的概率较高。
用于描述和管理cache的数据结构是struct kmem_cache
伙伴系统算法
在实际应用中,经常需要分配一组连续的页框,而频繁地申请和释放不同大小的连续页框,必然导致在已分配页框的内存块中分散了许多小块的 空闲页框这样,即使这些页框是空闲的,其他需要分配连续页框的应用也很难得到满足
为了避免出现这种情况,Linux内核中引入了伙伴系统算法(buddy system)把所有的空闲页框分组为11个 块链表,每个块链表分别包含大小为1,2,4,8,16,32,64,128,256,512和1024个连续页框的页框块最大可以申请1024个连 续页框,对应4MB大小的连续内存每个页框块的第一个页框的物理地址是该块大小的整数倍
假设要申请一个256个页框的块,先从256个页框的链表中查找空闲块,如果没有,就去512个 页框的链表中找,找到了则将页框块分为2个256个 页框的块,一个分配给应用,另外一个移到256个页框的链表中如果512个页框的链表中仍没有空闲块,继续向1024个页 框的链表查找,如果仍然没有,则返回错误
页框块在释放时,会主动将两个连续的页框块合并为一个较大的页框块
1.2.slab分 配器
slab分配器源于 Solaris 2.4 的 分配算法,工作于物理内存页框分配器之上,管理特定大小对象的缓存,进行快速而高效的内存分配
slab分配器为每种使用的内核对象建立单独的缓冲区Linux 内核已经采用了伙伴系统管理物理内存页框,因此 slab分配器直接工作于伙伴系 统之上每种缓冲区由多个 slab 组成,每个 slab就是一组连续的物理内存页框,被划分成了固定数目的对象根据对象大小的不同,缺省情况下一个 slab 最多可以由 1024个页框构成出于对齐 等其它方面的要求,slab 中分配给对象的内存可能大于用户要求的对象实际大小,这会造成一定的 内存浪费
2.常用内存分配函数
2.1.__get_free_pages
unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order)
__get_free_pages函数是最原始的内存分配方式,直接从伙伴系统中获取原始页框,返回值为第一个页框的起始地址__get_free_pages在实现上只是封装了alloc_pages函 数,从代码分析,alloc_pages函数会分配长度为1<
2.2.kmem_cache_alloc
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),
void (*dtor)(void*, struct kmem_cache *, unsigned long))
void *kmem_cache_alloc(struct kmem_cache *c, gfp_t flags)
kmem_cache_create/ kmem_cache_alloc是基于slab分配器的一种内存分配方式,适用于反复分配释放同一大小内存块的场合首先用kmem_cache_create创建一个高速缓存区域,然后用kmem_cache_alloc从 该高速缓存区域中获取新的内存块 kmem_cache_alloc一次能分配的最大内存由mm/slab.c文件中的MAX_OBJ_ORDER宏 定义,在默认的2.6.18内核版本中,该宏定义为5, 于是一次最多能申请1<<5 * 4KB也就是128KB的 连续物理内存分析内核源码发现,kmem_cache_create函数的size参数大于128KB时会调用BUG()测试结果验证了分析结果,用kmem_cache_create分 配超过128KB的内存时使内核崩溃
2.3.kmalloc
void *kmalloc(size_t size, gfp_t flags)
kmalloc是内核中最常用的一种内存分配方式,它通过调用kmem_cache_alloc函 数来实现kmalloc一次最多能申请的内存大小由include/Linux/Kmalloc_size.h的 内容来决定,在默认的2.6.18内核版本中,kmalloc一 次最多能申请大小为131702B也就是128KB字 节的连续物理内存测试结果表明,如果试图用kmalloc函数分配大于128KB的内存,编译不能通过
2.4.vmalloc
void *vmalloc(unsigned long size)
前面几种内存分配方式都是物理连续的,能保证较低的平均访问时间但是在某些场合中,对内存区的请求不是很频繁,较高的内存访问时间也 可以接受,这是就可以分配一段线性连续,物理不连续的地址,带来的好处是一次可以分配较大块的内存图3-1表 示的是vmalloc分配的内存使用的地址范围vmalloc对 一次能分配的内存大小没有明确限制出于性能考虑,应谨慎使用vmalloc函数在测试过程中, 最大能一次分配1GB的空间
Linux内核部分内存分布
2.5.dma_alloc_coherent
void *dma_alloc_coherent(struct device *dev, size_t size,
ma_addr_t *dma_handle, gfp_t gfp)
DMA是一种硬件机制,允许外围设备和主存之间直接传输IO数据,而不需要CPU的参与,使用DMA机制能大幅提高与设备通信的 吞吐量DMA操作中,涉及到CPU高速缓 存和对应的内存数据一致性的问题,必须保证两者的数据一致,在x86_64体系结构中,硬件已经很 好的解决了这个问题, dma_alloc_coherent和__get_free_pages函数实现差别不大,前者实际是调用__alloc_pages函 数来分配内存,因此一次分配内存的大小限制和后者一样__get_free_pages分配的内 存同样可以用于DMA操作测试结果证明,dma_alloc_coherent函 数一次能分配的最大内存也为4M
2.6.ioremap
void * ioremap (unsigned long offset, unsigned long size)
ioremap是一种更直接的内存“分配”方式,使用时直接指定物理起始地址和需要分配内存的大小,然后将该段 物理地址映射到内核地址空间ioremap用到的物理地址空间都是事先确定的,和上面的几种内存 分配方式并不太一样,并不是分配一段新的物理内存ioremap多用于设备驱动,可以让CPU直接访问外部设备的IO空间ioremap能映射的内存由原有的物理内存空间决定,所以没有进行测试
2.7.Boot Memory
如果要分配大量的连续物理内存,上述的分配函数都不能满足,就只能用比较特殊的方式,在Linux内 核引导阶段来预留部分内存
2.7.1.在内核引导时分配内存
void* alloc_bootmem(unsigned long size)
可以在Linux内核引导过程中绕过伙伴系统来分配大块内存使用方法是在Linux内核引导时,调用mem_init函数之前 用alloc_bootmem函数申请指定大小的内存如果需要在其他地方调用这块内存,可以将alloc_bootmem返回的内存首地址通过EXPORT_SYMBOL导 出,然后就可以使用这块内存了这种内存分配方式的缺点是,申请内存的代码必须在链接到内核中的代码里才能使用,因此必须重新编译内核,而且内存管理系统 看不到这部分内存,需要用户自行管理测试结果表明,重新编译内核后重启,能够访问引导时分配的内存块
2.7.2.通过内核引导参数预留顶部内存
在Linux内核引导时,传入参数“mem=size”保留顶部的内存区间比如系统有256MB内 存,参数“mem=248M”会预留顶部的8MB内存,进入系统后可以调用ioremap(0xF800000,0x800000)来申请这段内存
3.几种分配函数的比较
分配原理 |
最大内存 |
其他 |
|
__get_free_pages |
直接对页框进行操作 |
4MB |
适用于分配较大量的连续物理内存 |
kmem_cache_alloc |
基于slab机制实现 |
128KB |
适合需要频繁申请释放相同大小内存块时使用 |
kmalloc |
基于kmem_cache_alloc实现 |
128KB |
最常见的分配方式,需要小于页框大小的内存时可以使用 |
vmalloc |
建立非连续物理内存到虚拟地址的映射 |
|
物理不连续,适合需要大内存,但是对地址连续性没有要求的场合 |
dma_alloc_coherent |
基于__alloc_pages实现 |
4MB |
适用于DMA操 作 |
ioremap |
实现已知物理地址到虚拟地址的映射 |
|
适用于物理地址已知的场合,如设备驱动 |
alloc_bootmem |
在启动kernel时,预留一段内存,内核看不见 |
|
小于物理内存大小,内存管理要求较高 |