Chinaunix首页 | 论坛 | 博客
  • 博客访问: 418685
  • 博文数量: 82
  • 博客积分: 2600
  • 博客等级: 少校
  • 技术积分: 961
  • 用 户 组: 普通用户
  • 注册时间: 2010-03-08 21:44
文章分类
文章存档

2013年(2)

2012年(56)

2010年(24)

我的朋友

分类:

2012-06-21 23:14:29

一。系统启动时的内存操作

二。伙伴算法

三。slab分配器

四。slob分配器

五。slub分配器

六。kmalloc和kfree

七。vmalloc和vfree

八。glibc中的malloc和free

九。参考资料

一。[[Anchor(NBE1)]]系统启动时的内存操作

1。pg0的位置和尺寸

当 系统刚刚启动时,在分页功能未打开前,线性地址和物理地址是一一对应的。刚开启分页功能时,pg0的内存地址是 在编译内核时定义好的,见arch\i386\kernel\vmlinux.lsd.S,大小为4096字节,启始地址紧跟内核在内存中物理地址。由于 内核保护模式代码启始位置为0x100000,所以pg0地址=0x100000+内核保护模式代码尺寸。

对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= 内核保护模式代码启始地址(0x100000)+内核保护模式代码尺 寸+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(struct page);需要的空间就是系统中页框数和页面结构的乘积,也就是为系统中存在的每一个页表建立一个页表结构

页表结构的内存搜索从MAX_DMA_ADDRESS以上寻找。0xC0000000+0x1000000=0xC1000000,即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--;空余页面信息减1

        • rmv_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。算法原理

Buddy System是一种经典的内存管理算法。在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都有一个buddy system。

struct zone中的unsigned long free_pages保存的是空闲块的大小。

特别强调的是struct free_area free_area[MAX_ORDER],保存着zone中的空闲块。数组中的每一个元素都有个双链表结构。

struct free_area {

  • struct list_head free_list;
  • unsigned long nr_free;

};

比 如说 free_area中第K个元素保存着大小为2的k次方大小的块的链表结构。数组中保存的是表头结构,即指向第一个2的k次方大小块的第一个页面。那块的 剩余的页面怎么办?不用管,因为都是按块来操作的,只需要知道块的第一个页面即可,最后一个页面就是第一个页面加上2的k次方。同属于一个链表的块与块之 间由每一个块的第一个页面的struct page 中的list_head lru来相互链接。

分配一个大小为2的m次方的页面块, 首先看freearea的第m个元素,如果其nr_free大于0,则从这个 链表中取出来一个块来满足要求,如果不大于0,则看数组中m+1个元素,那要下去。如果找到能够分配的,那么就将块的第一部分大小为2的m次方的块分出 去,剩下的继续保存在buddy system中。

而每一个zone结构里都有一个struct pglist_data *zone_pgdat域,这个域的成员struct page *node_mem_map指向这个zone的第一个page 在mem_map的位置,mem_map是一个struct page指针,对应系统中所有的物理内存页。

页面分配代码使用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分配器的实现 相当复杂,但原理不难,其核心思想就是“存储池”的运用。内存片段(小块内存)被看作对象,当被使用完后,并不直接释放而是被缓存到“存储池”里,留做下 次使用,这无疑避免了频繁创建与销毁对象所带来的额外负载。

Linux slab分配器使用了这种思想和其他一些思想来构建一个在空间和时间上都具有高效性的内存分配器。

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缓存结构

struct struct kmem_cache *my_cachep;

kmem_cache 结构包含了每个中央处理器单元的数据、一组可调整的(可以通过 proc 文件系统访问)参数、统计信息和管理slab 缓存所必须的元素。

2.2kmem_cache_create

内核函数 kmem_cache_create 用来创建一个新缓存。这通常是在内核初始化时执行的,或者在首次加载内核模块时执行

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));

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 用来销毁缓存。这个调用是由内核模块在被卸载时执行的。在调用这个函数时,缓存必须为空。

void kmem_cache_destroy( struct kmem_cache *cachep );

2.4kmem_cache_alloc

要从一个命名的缓存中分配一个对象,可以使用 kmem_cache_alloc 函数。调用者提供了从中分配对象的缓存以及一组标志:

void kmem_cache_alloc( struct kmem_cache *cachep, gfp_t flags );

这 个函数从缓存中返回一个对象。注意如果缓存目前为空,那么这个函数就会调用 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。调用者提供了缓存引用和要释放的对象。

void kmem_cache_free( struct kmem_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成员:

short unsigned int inuse;slab中分配对象的数目

short unsigned int offset;下一个空余对象的偏移

void *freelist;指向slab中第一个空余对象

当slab刚被分配器创建时是没有立刻分配对象的。当对象被分配以后,就以"partial" slab形式被保存在kmem_cache结构的list中。对于每个NUMA节点都包含一个"partial"的list。

在 多CPU环境中,每个CPU都包含一组活跃的slab,以避免cache line的污染。系统提供了一个特殊的线程(通过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列表。这意味着随着时间推移,大量的partial slab将聚集在这些列表上的。而分配器只有在特定的节点上才可以复用。slub使用了一个全局的partial slab池子,通过这个池子来消费slab,以减少碎片。

g。可微调的

slab 机制对每个slab缓存的微调是很诡异的。可以详细地操作队列尺寸,填充队列仍然需要使用spin lock来检查slab。slub提供了一个全局参数min_slab_order来微调。增加这个值可以减少锁的费用。这个值越大,在per CPU和partial列表之间产生的页面移动就越小,slub的伸缩性就越好。

g。slab合并

我们经常遇到相似参数 的slab缓存,slub可以在启动时检测并将其合并成相应的统一caches。这样可以提 高内存的利用率。通过slab的合并可以减少使用50%的缓存。这同样也可以减少slab碎片,因为partial的slab可以再次被使用。slab合 并可以在启动时使用slub_nomerge开关关闭。

h。诊断

目前的slab诊断方法是比较复杂并且需要内核的支持。slub本身包含了调试代码,slub诊断功能可以通过传递slab_debug参数打开。这些参数还可以指定诊断单个或一组slab caches。这意味着系统可以平时正常的性能运行,可以使运行条件复现。

j。修复

如果基本的完整检查是打开的,slub具备检测通用错误并修复的能力,尽一切可能让系统可以继续运行。

j。跟踪

跟踪功能可以通过在启动时传递slab_debug=T,参数打开。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 到131072字节的内存)。从内核内存分配角度讲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)]]参考资料

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