在遇到类似于越界溢出、迷途指针这些漏洞时,一般会对内存进行覆盖(比如喷堆),无论哪种重写方式,无疑都需要了解内存分配器的工作方式。在进行了阅读,源码的分析,包括很多博文的帮助下大概摸清了SLAB分配器的脉络。
SLAB分配器主要的作用是针对常用的对象,以链表的方式缓存它们,缓存的链表会连续的存放,这样频繁地分配和回收就不会导致内存碎片,此外对于很多初始化的对象(如互斥锁)内存回收后仍保持初始化的状态,再次分配的时候只需要将数据写入就可以了,不需要再次初始化。
在SLAB层的设计中,将不同的对象划分成高速缓存组kmem_cache,每个kmem_cache存放不同类型的对象,kmem_cache之间用链表连接。这些高速缓存保存SLAB的链表,每个SLAB结构由一个或多个页page组成,通常为一个。在页里用来保存对象的实例。
结构图如下:
每个缓存都有三种SLAB:
slabs_full:完全分配的 slab
slabs_partial:部分分配的 slab
slabs_free:空 slab,或者没有对象被分配
结合图片和源码,看看SLAB关键结构的实现
struct kmem_cache{ /* 1) per-cpu data, touched during every alloc/free */ struct array_cache *array[NR_CPUS];//array是一个指向数组的指针,每个数组项都对应于系统中的一个CPU。每个数组项都包含了另一个指针,指向下文讨论的array_cache结构的实例 /* 2) Cache tunables. Protected by cache_chain_mutex */ unsigned int batchcount;//指定了在每CPU列表为空的情况下,从缓存的slab中获取对象的数目。它还表示在缓存增长时分配的对象数目 unsigned int limit;//limit指定了每CPU列表中保存的对象的最大数目,如果超出该值,内核会将batchcount个对象返回到slab unsigned int shared; unsigned int buffer_size;//指定了缓存中管理的对象的长度 u32 reciprocal_buffer_size;//buffer_size的倒数值,为了克服出发运算对性能的影响 /* 3) touched by every alloc & free from the backend */ unsigned int flags;//是一个标志寄存器,定义缓存的全局性质,当前只有一个标志位,用于标记slab头得管理数据是在slab内还是外 unsigned int num;//保存了可以放入slab的对象的最大数目 /* 4) cache_grow/shrink */ /* order of pgs per slab (2^n) */ unsigned int gfporder;//指定了slab包含的页数目以2为底得对数 /* force GFP flags, e.g. GFP_DMA */ gfp_t gfpflags;//与伙伴系统交互时所提供的分配标识 size_t colour;//指定了颜色的最大数目 unsigned int colour_off;//是基本偏移量乘以颜色值获得的绝对偏移量 struct kmem_cache *slabp_cache;//如果slab头部的管理数据存储在slab外部,则slabp_cache指向分配所需内存的一般性缓存;如果slab头部在slab上,则其为NULL unsigned int slab_size;//slab管理区的大小 unsigned int dflags;//另一个标志集合,描述slab的“动态性质”,但目前还没有定义标志 /* constructor func */ void (*ctor)(struct kmem_cache *, void *);//创建高速缓存时的构造函数指针 /* 5) cache creation/removal */ const char *name;//缓存的名称 struct list_head next;//用于将kmem_cache的所有实例保存在全局链表cache_chain上 /* 6) statistics */ #if STATS//统计数据字段 unsigned long num_active; unsigned long num_allocations; unsigned long high_mark; unsigned long grown; unsigned long reaped; unsigned long errors; unsigned long max_freeable; unsigned long node_allocs; unsigned long node_frees; unsigned long node_overflow; atomic_t allochit; atomic_t allocmiss; atomic_t freehit; atomic_t freemiss; #endif #if DEBUG /* * If debugging is enabled, then the allocator can add additional * fields and/or padding to every object. buffer_size contains the total * object size including these internal fields, the following two * variables contain the offset to the user object and its size. */ int obj_offset; int obj_size; #endif /* * We put nodelists[] at the end of kmem_cache, because we want to size * this array to nr_node_ids slots instead of MAX_NUMNODES * (see kmem_cache_init()) * We still use [MAX_NUMNODES] and not [1] or [0] because cache_cache * is statically defined, so we reserve the max number of nodes. */ struct kmem_list3 *nodelists[MAX_NUMNODES];//nodelists是一个数组,每个数组对应于系统中一个可能的内存结点。每个数组项都包含kmem_list3的一个实例 /* * Do not add fields after nodelists[] */ }
</pre><pre name="code" class="cpp">struct kmem_list3 { struct list_head slabs_partial;//部分空闲的slab链表 struct list_head slabs_full;//非空闲的slab链表 struct list_head slabs_free;//完全空闲的slab链表 unsigned long free_objects;//部分空闲的slab链表和完全空闲的slab链表中空闲对象的总数 unsigned int free_limit;//指定了所有slab上容许未使用对象的最大数目 unsigned int colour_next;//内核建立的下一个slab的颜色 spinlock_t list_lock; struct array_cache *shared;//结点内共享 struct array_cache **alien;//在其他结点上 unsigned long next_reap;//定义了内核在两次尝试收缩缓存之间,必须经过的时间间隔 int free_touched;//表示缓存是否是活动的 };
struct slab { struct list_head list; unsigned long colouroff;//该Slab上着色区的大小 void *s_mem;//指向对象区的起点 unsigned int inuse;//Slab中所分配对象的个数 kmem_bufctl_t free;//指明了空闲对象链中的第一个对象,kmem_bufctl_t其实是一个整数 unsigned short nodeid;//结点标识号 };
struct array_cache { unsigned int avail;//本地高速缓存中可用的空闲对象数 unsigned int limit;//空闲对象的上限 unsigned int batchcount;//一次转入和转出的对象数量 unsigned int touched;//标识本地CPU最近是否被使用 spinlock_t lock; void *entry[];//这是一个伪数组,便于对后面用于跟踪空闲对象的指针数组的访问 };还要介绍的一个数据结构就是struct array_cache。struct kmem_cache中定义了一个struct array_cache指针数组,数组的元素个数对应了系统的CPU数,和伙伴系统中的每CPU页框高速缓存类似,该结构用来描述每个CPU的本地高速缓存,它可以减少SMP系统中对于自旋锁的竞争。在每个array_cache的末端都用一个指针数组记录了slab中的空闲对象,分配对象时,采用LIFO方式,也就是将该数组中的最后一个索引对应的对象分配出去,以保证该对象还驻留在高速缓存中的可能性。实际上,每次分配内存都是直接与本地CPU高速缓存进行交互,只有当其空闲内存不足时,才会从kmem_list中的slab中引入一部分对象到本地高速缓存中,而kmem_list中的空闲对象也不足了,那么就要从伙伴系统中引入新的页来建立新的slab了,这一点也和伙伴系统的每CPU页框高速缓存很类似。 上述内容来自http://blog.csdn.net/vanbreaker/article/details/7664296