三种实现:buddy,slab 以及 glibc的malloc.
Buddy算法:
1. Linux使用页(struct page)来对物理内存进行管理,这个结构体约占40字节,故4G内存存放页结构体需要(40/1024)*4GB = 20MB。实际上内存还分为( ZONE_NORMAL,ZONE_DMA,ZONE_HIGHMEM三个区 )。
2. 对于页的分配、释放,Linux用 buddy 算法管理。
Buddy算法把内存中所有页面按照2^n划分,其中n=0~5,每个内存空间按1个页面、2个页面、4个页面、8个页面、16个页面、32个页面进行六次划分。所以Buddy算法一次可以分配物理上连续最大内存块大小为 4K * 32 = 128KB。
划分后形成了大小不等的存储块,称为页面块,简称页块,包含一个页面的页块称为1页块,包含2个页面的称为2页块,依次类推。每种页块按前后顺序两两结合成一对Buddy“伙伴”。系统按照Buddy关系把具有相同大小的空闲页面块组成页块组,即1页块组、2页块组……32页块组。 每个页块组用一个双向循环链表进行管理,共有6个链表,分别为1、2、4、8、16、32页块链表。分别挂到free_area[] 数组上。
位图数组: 位图数组用于标记内存页面使用情况,第0组每一位表示单个页面使用情况,1表示使用,0表示空闲,第二组每一位表示比邻的两个页面使用情况,一次类推。默认为10个数组,当一对Buddy的两个页面中有一个事空闲的,而另一个全部或部分被占用时,该位置1.两个页面块都是空闲,对应位置0.
内存分配和释放过程: 内存分配时,系统按照Buddy算法,根据请求的页面数在free_area[]对应的空闲页块组中搜索。 若请求页面数不是2的整数次幂,则按照稍大于请求数的2的整数次幂的值搜索相应的页面块组。当相应页块组中没有可使用的空闲页面块时就查询更大一些的页块组,在找到可用的页块后分配所需要的页面。当某一空闲页面被分配后,若仍有剩余的空闲页面,则根据剩余页面的大小把他们加入到相应页面组中。内存页面释放时,系统将其作为空闲页面看待,检查是否存在与这些页面相邻的其他空闲页块,若存在,则合为一个连续的空闲区按Buddy算法重新分组。
采用buddy算法,解决了外碎片问题(当有相邻空闲块时,它们会合并),这种方法适合大块内存请求,不适合小内存区请求。
Slab算法:
linux提供了buddy系统,来进行基于页的分配。buddy系统解决了内存碎片问题。若为小块内存而请求整个页面,这样对于内存来说是一种极度的浪费。因此linux采用了slab来管理小块内存的分配与释放。Slab最早是由sun的工程师提出。它的提出是基于以下因素考虑的:
1:内核函数经常倾向于反复请求相同的数据类型。比如:创建进程时,会请求一块内存来存放mm结构。
2:不同的结构使用不同的分配方法可以提高效率。同样,如果进程在撤消的时候,内核不把mm结构释放掉,而是存放到一个缓冲区里,以后若有请求mm存储空间的行为就可以直接从缓冲区中取得,而不需重新分配内存.
3:如果伙伴系统频繁分配,释放内存会影响系统的效率,以此,可以把要释放到的内存放到缓冲区中,直至超过一个阀值才把它释放至伙伴系统,这样可以在一定程度上缓减减伙伴系统的压力 .
4:为了缓减“内碎片”的产生,通常可以把小内存块按照2的倍数组织在一起,这一点和伙伴系统类似
内核中经常使用的内存分配函数就是基于slab系统。
void *kmalloc( size_t size, int flags );
void kfree( const void *objp );
linux内核中,一共有26个,13组普通高速缓存。分别是size-32、size-64...size-131072(128KB)。size-32(DMA)、size-64(DMA)...size-131072(DMA)。其中,前面13个高速缓存,是常规内存(ZONE_NORMAL)内存分配。后面13个高速缓存,是DMA内存(ZONE_DMA)内存分配。这26个高速缓存在系统初始化的时候就创建了。
当内核调用kmalloc函数申请内存的时候,其实是根据大小,向系统的普通高速缓存分配对象。因为高速缓存的大小被预定了。所以,如果调用kmalloc(52,GFP_KERNEL),实际上是从size_64的高速缓存的slab中分配对象,真正的内存开销是64字节。
总结:Slab实际上是运行于Buddy之上,这可以从Slab一次可申请物理上连续内存大小看出来,当一次申请内存大于128K时,就需要使用vmalloc() 了(分配物理上不连续的内存,通过修正页表来获得逻辑上连续的内存)。
malloc 的实现:
在glibc中,malloc分配及free释放的内存是通过结构体 struct mem_control_block {int is_available;int size;}; 内存控制块来处理的,系统维护一个链表,并且这个链表是在一块连续的内存上。[managed_memory_start, last_valid_address] 为链表管理的总的内存。
当我们连续分配[1, 2, 4, 3, 5]个空间时,实际上是分配了[1+8, 2+8, 4+8, 3+8, 5+8]这么大的内存块。
当释放4,3时,4,3在相应链上内存控制块的is_availible被设置为1。
当我们再次分配4,3时,通过对该链表(由于是连续空间,由size即可寻找下一个节点)的查找可以对上次分配的空间再次利用。