| Linux中内存的分配和回收.内存回收 一。体系启动时的内存操作 二。伙伴算法 三。slab分配器 四。slob分配器 五。slub分配器 六。kmalloc和kfree 七。vmalloc和vfree 八。glibc中的malloc和free 九。参考资料 一。[[Anchor(NBE1)]]体系启动时的内存操作 1。pg0的位置和尺寸 当体系刚刚启动时,在分页成效未掀开前,线性地址和物理地址是一一对应的。刚封闭分页成效时,pg0的内存地址是在编译内核时定义好的,见arch\i386\kernel\vmlinux.lsd.S,大小为4096字节,启始地址紧跟内核在内存中物理地址。由于内核保护模式代码启始位置为0x,所以pg0地址=0x+内核保护模式代码尺寸。 对pg0的操作主要是封闭分页机制时填写页面描绘表消息,上一节在第一次页寻址设置中已经详细先容。 2。内存位图的内存操作 内存位图是体系设置区域和页面管理前的内存使用形态表。 2.1内存位图的位置和尺寸 位图contig_page_data.bdata->node_bootmem_map的起始地址跟在init_pg_tables_end的背面。大小等于整个物理页面数除以32,即每一位代表一个页面。 首先我们看文件arch/i386/kernel/setup.c中的setup_memory函数 min_low_pfn=PFN_UP(init_pg_tables_end); 然后看异样文件中的函数setup_bootmem_allocator bootmap_size=init_bootmem(min_low_pfn,max_low_pfn); 在init_bootmem中min_low_pfn是init_pg_tables_end的页框号 最终在函数init_bootmem_core中我们看到 bdata->node_bootmem_map=phys_to_virt(PFN_PHYS(mapstart)); 其中mapstart就是min_low_pfn;bdata就是NODE_DATA(0),也就是contig_page_data.bdata 所以contig_page_data.bdata->node_bootmem_map就是init_pg_tables_end指向地址 我们后面已经先容init_pg_tables_end的实际位置和内核大小有关 init_pg_tables_end=内核保护模式代码启始地址(0x)+内核保护模式代码尺寸+pg0的1024个4字节页面描绘符号+保证第一次分页设置的页表尺寸(一般还需要若干1024个4字节的页面描绘符号,由内核尺寸决定)+描绘1G内存的位图尺寸128K字节+描绘1G内存的页表空间(1024*4096字节)+间隔空间(4*4096字节) 可以看出如果内核尺寸在4M左右,描绘1G内存也需要大量页表空间(4M),这样第一次分页设置实际上就至多需要3个页表的页描绘符。 2.2内存位图的开释 在设置了区域和页面管理以后,内存位图就不需要了。开释内存位图的内存时,使用了函数free_pages_bootmem。 首先见启动过程中的mem_init()函数,这是在设置了区域和页面管理后执行的。我们看其中的free_all_bootmem()调用,在free_all_bootmem_core中将bdata->node_bootmem_map的对应页面结构逐个调用free_pages_bootmem。 在free_pages_bootmem中首先铲除了页面结构的PG_reserved标记,然后设置页面使用记数count为1,最后调用free_page(下面会详细描绘)。 3。pte的内存分配 设置页表时,pte的内存分配使用了函数alloc_bootmem_low_pages,由于此时区域页面管理还未开始使用,使用的都是内存位图搜索。 在one_page_table_init中使用alloc_bootmem_low_pages为页表分配内存,大小为一个页面4096字节。 见mm/bootmem.c中的alloc_bootmem_low函数,最后还是使用了alloc_bootmem_core,其中分配大小为PAGE_SIZE(实际上就是一个页面),对齐为PAGE_SIZE,启始搜寻地址为0,界限为ARCH_LOW_ADDRESS_LIMIT。 首先如果设置了启始搜寻地址,则从启始搜寻地址开始向上找。使用find_next_zero_bit查找bdata->node_bootmem_map从搜寻地址开始的第一个未分配页面(test_bit(i,bdata->node_bootmem_map),i为页面位),然后检讨接下来的连续请求页面是否都为空余页面。在one_page_table_init中只需要请求一个页面。 找到请求页面并调整对齐以后,将页面转换为虚拟地址,即加上0xC0000000。并使用test_and_set_bit(i,bdata->node_bootmem_map)在内存位图中标记内存已经使用。最后返回虚拟地址。 4。页面消息结构page的内存分配 在区域管理中设置页面消息page时,由函数alloc_node_mem_map使用了调用alloc_bootmem_node实现,联想手机配件。由于此时区域页面管理还未开始使用,使用的也是内存位图搜索。 见mm/bootmem.c中的alloc_bootmem_node函数,最后也是使用了alloc_bootmem_core,其中对齐使用SMP_CACHE_BYTES,启始搜寻地址为MAX_DMA_ADDRESS,界限为0。和pte页表分配不同的是,这里的尺寸不再是一个页面。 start=pgdat->node_start_pfn&~(MAX_ORDER_NR_PAGES-1);这里的pgdat->node_start_pfn就是early_node_map[i].start_pfn,在add_active_range(0,0,max_low_pfn)内里定义,实际上就是0。 end=pgdat->node_start_pfn+pgdat->node_spanned_pages;pgdat->node_spanned_pages是体系中总共的页框数目 end=ALIGN(end,MAX_ORDER_NR_PAGES); size=(end-start)*sizeof(structpage);需要的空间就是体系中页框数和页面结构的乘积,也就是为体系中存在的每一个页表建立一个页表结构 页表结构的内存搜索从MAX_DMA_ADDRESS以上寻找。0xC0000000+0x=0xC,即16M物理地址以上找空余内存。其它操作和分配pte页面内存一样。 分配后获得的页面结构内存指针放在pgdat->node_mem_map和mem_map中。 5。free_pages详解 free_pages如果只开释一页就调用free_hot_page,否则调用free_pages_ok,这里只先容后者 free_one_page(page,zone,order);要开释的页面指针page、页面所在区域指针zone和连续页面大小order page_idx=page_to_pfn(page)&((1<<MAX_ORDER)-1);取页框号的低11位while(order<MAX_ORDER-1){buddy=page_find_buddy(page,page_idx,order);使用buddy算法归并页面,但归并的连续页面不能大于MAX_ORDER-1,下面会详细先容伙伴算法 if(!page_is_buddy(page,buddy,order)) break; list_del(&buddy->lru);将伙伴页面从空余空间列表中删除area=zone->free_area+order;找到当前order的空余页面消息指针area->nr_free--;空余页面消息减1rmv_page_order(buddy);设置伙伴页面属性 combined_idx=find_combined_index(page_idx,order);归并后索引 page=page+(combined_idx-page_idx);指向归并后的页面指针 page_idx=combined_idx;page_idx是给page_find_buddy寻找伙伴的参数 order++;幂数加一 } set_page_order(page,order);设置页面伙伴消息 list_add(&page->lru,&zone->free_area[order].free_list);将页面插足到空余空间列表zone->free_area[order].nr_free++;对应空余空间记数加一二。[[Anchor(NBE2)]]伙伴算法 1。算法原理 BuddySystem是一种经典的内存管理算法。在Unix和Linux操作体系中都有用到。其作用是减少存储空间中的空洞、减少碎片、增加利用率。避免外碎片的方法有两种: a.利用分页单元把一组非连续的空闲页框映照到非连续的线性地址区间。 b.开发适当的技术来记录现存的空闲连续页框块的情状,以尽量避免为餍足对小块的要求恳求而把大块的空闲块进行分割。 基于下面三种原因,内核选择第二种避免方法: a.在某些情状下,连续的页框确实必要。 b.假使连续页框的分配不是很必要,它在保持内核页表不变方面所起的作用也是不容忽视的。假如修改页表,则导致平均访存次数增加,从而频繁刷新TLB。 c.通过4M的页可以访问大块连续的物理内存,绝对付4K页的使用,TLB未命中率降低,加快平均访存速度。 buddy算法将整个空闲页框分组为10个块链表,每个块链表分别包含1,2,4,8,16,32,64,128,256,512个连续的页框,每个块的第一个页框的物理地址是该块大小的整数倍。如,大小为16个页框的块,其起始地址是16*2^12的倍数。 例,假设要要求恳求一个128个页框的块,算法先检讨128个页框的链表是否有空闲块,如果没有则查256个页框的链表,有则将256个页框的块分裂两份,一份使用,一份拔出128个页框的链表。如果还没有,就查512个页框的链表,有的话就分裂为128,128,256,一个128使用,剩余两个拔出对应链表。如果在512还没查到,则返回出错信号。 回收过程相反,内核试图把大小为b的空闲伙伴归并为一个大小为2b的孑立块,餍足以下条件的两个块称为伙伴: a.两个块具有相同的大小,记做b。 b.它们的物理地址是连续的。 c.第一个块的第一个页框的物理地址是2*b*2^12的倍数。 该算法迭代,如果成功归并所开释的块,会试图归并2b的块来形成更大的块。 2。buddy算法在linux中的应用 Linux内核对各个zone都有一个buddysystem。 structzone中的unsignedlongfree_pages留存的是空闲块的大小。看看内存回收。 特别强调的是structfree_areafree_area[MAX_ORDER],留存着zone中的空闲块。数组中的每一个元素都有个双链表结构。 structfree_area{structlist_headfree_list; unsignedlongnr_free; };比如说free_area中第K个元素留存着大小为2的k次方大小的块的链表结构。数组中留存的是表头结构,即指向第一个2的k次方大小块的第一个页面。那块的剩余的页面怎么办?不消管,由于都是按块来操作的,只需要知道块的第一个页面即可,最后一个页面就是第一个页面加上2的k次方。同属于一个链表的块与块之间由每一个块的第一个页面的structpage中的list_headlru来彼此链接。 分配一个大小为2的m次方的页面块,首先看freearea的第m个元素,如果其nr_free大于0,则从这个链表中取出来一个块来餍足要求,如果不大于0,则看数组中m+1个元素,那要上去。如果找到能够分配的,那么就将块的第一局限大小为2的m次方的块分出去,剩下的继续留存在buddysystem中。 而每一个zone结构里都有一个structpglist_data*zone_pgdat域,这个域的成员structpage*node_mem_map指向这个zone的第一个page在mem_map的位置,mem_map是一个structpage指针,对应体系中整个的物理内存页。 页面分配代码使用free_area数组来寻找和开释页面,此机制负责整个缓冲管理。另外此代码与处理器使用的页面大小和物理分页机制无关。 free_area中的每个元素都包含页面块的消息。数组中第一个元素描叙1个页面,第二个表示2个页面大小的块而接下来表示4个页面大小的块,总之都是2的次幂倍大小。list域表示一个队列头,它包含指向mem_map指针中page数据结构的指针。整个的空闲页面都在此队列中。map域是指向某个特定页面尺寸的页面组分配情状位图的指针。当页面的第N块空闲时,位图的第N位被置位。 2.1页面分配 Linux使用Buddy算法来有效的分配与回收页面块。页面分配代码每次分配包含一个或者多个物理页面的内存块。页面以2的次幂的内存块来分配。这意味着它可以分配1个、2个和4个页面的块。只要体系中有足够的空闲页面来餍足这个要求(nr_free_pages>min_free_page),内存分配代码将在free_area中寻找一个与要求恳求大小相同的空闲块。free_area中的每个元素留存着一个反映这样大小的已分配与空闲页面的位图。例如,free_area数组中第二个元素指向一个反映大小为四个页面的内存块分配情状的内存映象。 分配算法首先搜寻餍足要求恳求大小的页面。它从free_area数据结构的list域着手沿链来搜索空闲页面。如果没有这样要求恳求大小的空闲页面,则它搜索两倍于要求恳求大小的内存块。这个过程一直将连续到free_area被搜索完或找到餍足要求的内存块为止。如果找到的页面块大于要求恳求的块则对其进行分割以使其大小与要求恳求块匹配。由于块大小都是2的次幂所以分割过程十分简单。空闲块被连进相应的队列而这个页面块被分配给调用者。 2.2页面回收 将大的页面块打碎进行分配将增加体系中零碎空闲页面块的数目。页面回收代码在适那机缘下要将这些页面结合起来形成单一大页面块。真相上页面块大小决定了页面重新组合的难易水平。 当页面块被开释时,代码将检讨是否有相同大小的相邻或者buddy内存块存在。如果有,则将它们结合起来形成一个大小为正本两倍的新空闲块。每次结合完之后,代码还要检讨是否可以继续归并成更大的页面。最佳情状是体系的空闲页面块将和许可分配的最大内存一样大。 在下面图中,如果开释页面框号1,它将和空闲页面框号0结合作为大小为2个页面的空闲块排入free_area的第一个元素中。 三。[[Anchor(NBE3)]]slab分配器 所谓尺有所长,寸有所短。以页为最小单位分配内存对付内核管理体系物理内存来说的确比较容易,但内核自身最常使用的内存却往往是很小(远远小于一页)的内存块——比如寄存文件描绘符、进程描绘符、虚拟内存区域描绘符等行为所需的内存都不够一页。这些用来寄存描绘符的内存相比页面而言,就好比是面包屑与面包。一个整页中可以聚集多个这种这些小块内存;而且这些小块内存块也和面包屑一样频繁地生成/销毁。 为了餍足内核对这种小内存块的需要,Linux体系采用了一种被称为slab分配器的技术。Slab分配器的实现相当复杂,但原理不难,其核心思想就是“存储池”的运用。内存片段(小块内存)被看作对象,当被使用完后,并不直接开释而是被缓存到“存储池”里,留做下次使用,这无疑避免了频繁创立与销毁对象所带来的额外负载。 Linuxslab分配器使用了这种思想和其他一些思想来构建一个在空间和时间上都具有高效性的内存分配器。 1。slab的特质 下图给出了slab结构的高层组织结构。在最高层是cache_chain,这是一个slab缓存的链接列表。这对付best-fit算法非常有用,可以用来查找最适合所需要的分配大小的缓存(遍历列表)。cache_chain的每个元素都是一个kmem_cache结构的援用(称为一个cache)。它定义了一个要管理的给定大小的对象池。 每个缓存都包含了一个slabs列表,这是一段连续的内存块(通常都是页面)。 存在3品种型的slab: slabs_full:完全分配的slab slabs_partial:局限分配的slab slabs_empty:空slab,或者没有对象被分配 注意,slabs_empty列表中的slab是进行回收(reaping)的主要备选对象。正是通过此过程,slab所使用的内存被返回给操作体系供其他用户使用。 slab列表中的每个slab都是一个连续的内存块(一个或多个连续页),它们被划分成一个个对象。这些对象是从特定缓存中进行分配和开释的基本元素。slab是slab分配器进行操作的最小分配单位,因此如果需要对slab进行扩展,这也就是所扩展的最小值。通常来说,每个slab被分配为多个对象。 由于对象是从slab中进行分配和开释的,因此单个slab可以在slab列表之间进行移动。例如,当一个slab中的整个对象都被使用完时,就从slabs_partial列表中移动到slabs_full列表中。当一个slab中有对象被开释后,就从slabs_full列表中移动到slabs_partial列表中。当整个对象都被开释之后,就从slabs_partial列表移动到slabs_empty列表中。 与传统的内存管理模式相比,slab缓存分配器提供了很多优点。 首先,内核通常依赖于对小对象的分配,它们会在体系生命周期内进行无数次分配。slab缓存分配器通过对类似大小的对象进行缓存而提供这种成效,从而避免了常见的碎片问题。 slab分配器还支持通用对象的初始化,从而避免了为同一目而对一个对象反复进行初始化。 最后,slab分配器还可以支持硬件缓存对齐和着色,这许可不同缓存中的对象占用相同的缓存行,从而进步缓存的利用率并获得更好的本能机能。 2。slab在Linux中的实现 下面是在Linux中创立新slab缓存、向缓存中增加内存、销毁缓存的应用程序接口(API),以及slab中对对象进行分配和开释操作的函数实现先容。 2.1slab缓存结构 structstructkmem_cache*my_cachep; kmem_cache结构包含了每个中央处理器单元的数据、一组可调整的(可以通过proc文件体系访问)参数、统计消息和管理slab缓存所必需的元素。 2.2kmem_cache_create 内核函数kmem_cache_create用来创立一个新缓存。这通常是在内核初始化时执行的,或者在首次加载内核模块时执行 structkmem_cache*kmem_cache_create(constchar*name,size_tsize,size_talign,unsignedlongflags, void(*ctor)(void*,structkmem_cache*,unsignedlong), void(*dtor)(void*,structkmem_cache*,unsignedlong)); name参数定义了缓存名称,proc文件体系(在/proc/slabinfo中)使用它标识这个缓存。size参数指定了为这个缓存创立的对象的大小,align参数定义了每个对象必需的对齐。flags参数指定了为缓存启用的选项。 SLAB_RED_ZONE在对象头、尾拔出标志,用来支持对缓冲区溢出的检讨。SLAB_POISON使用一种己知模式填充slab,许可对缓存中的对象进行监视(对象属对象整个,不过可以在内部进行修改)。SLAB_HWCACHE_ALIGN指定缓存对象必需与硬件缓存行对齐。
内存芯片编号识别(图十 ctor和dtor参数定义了一个可选的对象构造器和析构器。构造器和析构器是用户提供的回调函数。当从缓存中分配新对象时,可以通过构造器进行初始化。在创立缓存之后,kmem_cache_create函数会返回对它的援用。注意这个函数并没有向缓存分配任何内存。相反,在试图从缓存(最初为空)分配对象时,refill操作将内存分配给它。当整个对象都被使用掉时,也可以通过相同的操作向缓存增加内存。 2.3kmem_cache_destroy 内核函数kmem_cache_destroy用来销毁缓存。这个调用是由内核模块在被卸载时执行的。在调用这个函数时,缓存必需为空。 voidkmem_cache_destroy(structkmem_cache*cachep); 2.4kmem_cache_alloc 要从一个命名的缓存中分配一个对象,可以使用kmem_cache_alloc函数。调用者提供了从中分配对象的缓存以及一组标志: voidkmem_cache_alloc(structkmem_cache*cachep,gfp_tflags); 这个函数从缓存中返回一个对象。注意如果缓存目前为空,那么这个函数就会调用cache_alloc_refill向缓存中增加内存。kmem_cache_alloc的flags选项与kmalloc的flags选项相同。表2给出了标志选项的局限列表。 GFP_USER为用户分配内存(这个调用可能会睡眠)。GFP_KERNEL从内核RAM中分配内存(这个调用可能会睡眠)。GFP_ATOMIC使该调用强制处于非睡眠形态(对中断处理程序非常有用)。GFP_HIGHUSER从高端内存中分配内存。 2.5kmem_cache_zalloc 内核函数kmem_cache_zalloc与kmem_cache_alloc类似,只不过它对对象执行memset操作,用来在将对象返回调用者之前对其进行铲除操作。 2.6kmem_cache_free 要将一个对象开释回slab,可以使用kmem_cache_free。调用者提供了缓存援用和要开释的对象。 voidkmem_cache_free(structkmem_cache*cachep,void*objp); 四。[[Anchor(NBE4)]]slob分配器 slob是一个绝对简单一些的分配器,主要使用在小型的嵌入式体系。在选择了CONFIG_EMBEDDED后,就可以选用CONFIG_SLOB选项,使用SLOB分配器中。 slob是一个经典的K&R/UNIX堆分配器,其具有一个slab模仿层,和被slab替代的linux正本的kmalloc分配器比较相似,比slab更有空间效率,尺寸更小,但是依然存在碎片和难于扩展(对整个操作都简单地上锁)的问题,只适用于小体系。slob获得的是已经对齐的对象。slob在x86上的粒度是8字节,甚至可以减少到4字节。slob堆是一个单向列表,连接了从get_free_page获得的页面,从堆上按照first-fit的原则依照需求增长和分配。 从kmalloc获得的块都是8字节对齐并包含一个8字节的头。如果kmalloc被要求一个PAGE_SIZE或更大的对象,其将会调用直接get_free_pages以获得一个页面对齐的块,同时维护一个这些页面和级数的链接列表。这些对象会在kfree中被检讨页面对齐。 在slob最下层通过简单调用slab的构造和析构效仿slab。返回8字节对齐的对象,除非设置了SLAB_HWCACHE_ALIGN标记,在这种情状下,低级的分配器将块分片,以创立正确的对齐方式。 五。[[Anchor(NBE5)]]slub分配器 2.6.22中的SLAB内存管理代码将被SLUB代替。SLAB是经典的管理内核的内存的代码,Glib开发者受此启发,在Glib2.10中增加了GSlice内存管理方式,带来了本能机能上的飞跃。但是slab维护了大量的对象队列,这些队列虽然可以很快地被分配,但是过于复杂,而且维护所占用的空间会随着体系节点的增加而急剧增长。 slub就是作为slab的可替代选项出现的。slub是一种不使用队列的分配器。slub取消了大量的队列和相关维护费用,获得了极大的本能机能和伸缩性进步,并在总体上简化了slab结构,使用了基于每CPU的缓存,同时保留了slab的用户接口,而且slub还提供了强大的诊断和调试才力。 在slub分配器中,slab只是一组页面,页面中一律地填充了给定尺寸的对象。slab本身不包含元数据metadata,比较特殊的是空余的对象被组织成简单相连的list.。当分配要求恳求出现时,最开头的空余对象先被定位,并从list中移出,返回给要求恳求者。 由于缺少每个slab中原先包含的元数据,寻找第一个空余对象就是首先需要考虑的问题。答案就是slub分配器将相关消息留存到体系内存图表中,也就是在页面结构中标记出了产生slab的页面。增加页面结构尺寸不是个好注意,真相上slub分配器在页面结构中增加了三个union成员: shortunsignedintinuse;slab中分配对象的数目 shortunsignedintoffset;下一个空余对象的偏移 void*freelist;指向slab中第一个空余对象 当slab刚被分配器创立时是没有立刻分配对象的。当对象被分配以后,就以"partial"slab形式被留存在kmem_cache结构的list中。对付每个NUMA节点都包含一个"partial"的list。 在多CPU环境中,每个CPU都包含一组活跃的slab,以避免cacheline的净化。体系提供了一个特殊的线程(通过workqueue)来监视每个CPU上slab的利用。如果这个per-CPU的slab没有被使用,就将其放回partial的list并提供给其它处理器。 如果slab中整个的对象都被分出去了,分配器可以简单地将slab遗忘。当这个slab中有一个对象被开释,分配器通过内存映照表重新定位相关的slab,并将其放回适当的"partial"list。如果给定slab中整个对象都被开释了(通过跟踪使用计数器),想知道内存回收 回收内存条 回收内存卡。整个slab都将被放回页面分配器以被重新利用。 slub的一个主要好处就是可以归并具有相似尺寸和参数的对象的slab。这样在体系中只需要存在更少的slab缓存(具测试可以减少50%左右),而且slab分配的位置会更合理,slab内存中的碎片会更少。 下面是slub的一些特质: a。对象队列的管理 在slab中一个必需关怀的重点就是大量对象队列的复杂管理。在slub中没有这些队列。取而代之的是我们为每个对应的CPU分配选择slab,并且直接从slab中使用对象,而不是从队列中出队。 b。对象队列的存储费用 slab对象队列是每CPU每节点存在的。内部的缓存队列也需要一组队列,包含了每个节点上每个处理器的队列。这在非常大的体系中,队列和对象的数目将造成这样的队列象指数一样增长。例如在我们的体系中,有1k的节点/处理器,这样就需要上G字节用于联系存储对象和队列的关联,这还不包括在那些队列中本身包含的对象。这样子上去恐怕整个体系的内存都要被这些队列消耗光了。 c。slab中meta数据的费用 slab机制使用了每个slab开头作为meta数据。这意味着数据无法自然地从slab块的开头进行对齐。而slub机制留存整个meta数据在相应的页面结构中。这样slab中的对象可以自然对齐。例如一个128字节的对象可以按照128字节畛域对齐,并可以很相宜地放入4K的页面,这在slab是无法做到的。 d。slab使用了一个超复杂的缓存回收器 在单处理器体系slub不需要缓存回收器。在SMP体系中对应CPU的slab必需被放回partial列表,这样的操作是很简捷的并且不需要对象列表的遍历。而slab是每CPU失效的,在缓存回收光阴,共享的和相异的对象队列会造成严重的分裂。 e。slab需要复杂的NUMA计谋层支持 slub将NUMA计谋放到页面分配器中处理。这意味着分配本身是简洁的(slub拔出在页面层)。slab应用计谋将在slab机制中分配的slab对象区隔离,这样一系列对象一个节点接一个节点到来,并频繁援用内存计谋是可能导致本能机能问题的。slub只是从一个节点拿到一个充沛对象的slab,然后切换到另一个。 f。扩充partial列表的尺寸 在slab中每个节点都有partial列表。这意味着随着时间推移,大量的partialslab将聚集在这些列表上的。而分配器惟有在特定的节点上才可以复用。slub使用了一个全局的partialslab池子,通过这个池子来消费slab,以减少碎片。 g。可微调的 slab机制对每个slab缓存的微调是很诡异的。可以详细地操作队列尺寸,填充队列仍然需要使用spinlock来检讨slab。slub提供了一个全局参数min_slab_order来微调。增加这个值可以减少锁的费用。这个值越大,在perCPU和partial列表之间产生的页面移动就越小,slub的伸缩性就越好。 g。slab归并 我们每每遇到相似参数的slab缓存,slub可以在启动时检测并将其归并成相应的统一caches。这样可以进步内存的利用率。通过slab的归并可以减少使用50%的缓存。这异样也可以减少slab碎片,由于partial的slab可以再次被使用。slab归并可以在启动时使用slub_nomerge开关关闭。 h。诊断 目前的slab诊断方法是比较复杂并且需要内核的支持。slub本身包含了调试代码,slub诊断成效可以通过传递slab_debug参数掀开。这些参数还可以指定诊断单个或一组slabcaches。这意味着体系可以通常一般的本能机能运行,可以使运行条件复现。 j。修复 如果基本的完整检讨是掀开的,slub具备检测通用错误并修复的才力,尽一切可能让体系可以继续运行。 j。跟踪 跟踪成效可以通过在启动时传递slab_debug=T,<slabcache>参数掀开。slub将处理定义的slabcache上的整个动作,并在开释是dump对象形式。 k。DMA缓存的按需创立 一般来说DMA缓存并不是必需的。惟有在kmalloc时使用了GFP_DMA才创立这个单一的slabcache。对付体系来说,如果没有ZONE_DMA,就不需要支持了。 l。本能机能进步 一些测试映现slub可以有5-10%的本能机能提拔。slub的锁花消取决于分配尺寸。如果我们能够可靠地分配大量连续页面,就可以进步slub的本能机能。 六。[[Anchor(NBE6)]]kmalloc和kfree 1。kmalloc Slab分配器不仅仅只用来寄存内核专用的结构体,它还被用来处理内核对小块内存的要求恳求。当然鉴于Slab分配器的特质,一般来说内核程序中对小于一页的小块内存的求情才通过Slab分配器提供的接口Kmalloc来完成(虽然它可分配32到字节的内存)。从内核内存分配角度讲kmalloc可被看成是get_free_page(s)的一个有效补充,内存分配粒度更灵活了。 有兴致的话可以到/proc/slabinfo中找到内核执行现场使用的各种slab消息统计,其中你会看到体系中整个slab的使用消息。从消息中可以看到体系中除了专用结构体使用的slab外,还存在大量为Kmalloc而准备的Slab(其中有些为dma准备的)。 2。kfree kfree并没有真正开释内存,只是将对象放回到缓存中。 七。[[Anchor(NBE7)]]vmalloc和vfree 1。vmalloc 内核非连续内存分配(Vmalloc) 伙伴关系也好、slab技术也好,从内存管理理论角度而言目的基本是一致的,它们都是为了防止“分片”,不过分片又分为外局限片和内局限片之说,所谓内局限片是说体系为了餍足一小段内存区(连续)的需要,不得不分配了一大区域连续内存给它,从而造成了空间浪费;外局限片是指体系虽有足够的内存,但却是分散的碎片,无法餍足对大块“连续内存”的需求。无论何种分片都是体系有效利用内存的困穷。slab分配器使得含与一个页面内众多小块内存可独立被分配使用,避免了内局限片,节约了空闲内存。伙伴关系把内存块按大小分组管理,一定水平上减轻了外局限片的危害,由于页框分配不在自觉,而是按照大小依次有序进行,不过伙伴关系只是减轻了外局限片,但并未彻底消除。你自己笔画一下屡次分配页框后,空闲内存的剩余情状吧。 所以避免外局限片的最终思绪还是落到了如何利用不连续的内存块组合成“看起来很大的内存块”——这里的情状很类似于用户空间分配虚拟内存,内存逻辑上连续,其实影射到并不一定连续的物理内存上。Linux内核借用了这个技术,许可内核程序在内核地址空间中分配虚拟地址,异样也利用页表(内核页表)将虚拟地址影射到分散的内存页上。以此完美地解决了内核内存使用中的外局限片问题。内核提供vmalloc函数分配内核虚拟内存,该函数不同于kmalloc,它可以分配较Kmalloc大得多的内存空间(可远大于128K,但必需是页大小的倍数),但相比Kmalloc来说Vmalloc需要对内核虚拟地址进行重影射,必需更新内核页表,因此分配效率上要低一些(用空间换时间) 2。vfree 不象kfree只是将对象放回缓存,vfree真正使用了free_page。 八。[[Anchor(NBE8)]]glibc中的malloc和free 九。[[Anchor(NBE9)]]参考资料 subject/linux/memorymanage.mht developerworks/cn/linux/l-linux-slab-allocator/ Articles// Articles//Articles//Trackback:TrackBack.aspx?PostId=blog/item/b2b7843e7dfb3afe838b1326.html (责任编辑:admin) |







