内存管理的目标是提供一种方法,为实现各类目的而在各个用户之间实现内存共享。内存管理方法应该实现如下两个功能:linux
内存管理其实是一种关于权衡的零和游戏。您能够开发一种使用少许内存进行管理的算法,可是要花费更多时间来管理可用内存。也能够开发一个算法来有效地管理内存,但却要使用更多的内存。最终,特定应用程序的需求将促使对这种权衡做出选择。算法
每一个内存管理器都使用了一种基于堆的分配策略。在这种方法中,大块内存(称为 堆)用来为用户定义的目的提供内存。当用户须要一块内存时,就请求给本身分配必定大小的内存。堆管理器会查看可用内存的状况(使用特定算法)并返回一块内存。搜索过程当中使用的一些算法有first-fit(在堆中搜索到的第一个知足请求的内存块 )和 best-fit(使用堆中知足请求的最合适的内存块)。当用户使用完内存后,就将内存返回给堆。api
这种基于堆的分配策略的根本问题是碎片(fragmentation)。当内存块被分配后,它们会以不一样的顺序在不一样的时间返回。这样会在堆中留下一些洞,须要花一些时间才能有效地管理空闲内存。这种算法一般具备较高的内存使用效率(分配须要的内存),可是却须要花费更多时间来对堆进行管理。缓存
另一种方法称为 buddy memory allocation,是一种更快的内存分配技术,它将内存划分为 2 的幂次方个分区,并使用 best-fit 方法来分配内存请求。当用户释放内存时,就会检查 buddy 块,查看其相邻的内存块是否也已经被释放。若是是的话,将合并内存块以最小化内存碎片。这个算法的时间效率更高,可是因为使用 best-fit 方法的缘故,会产生内存浪费。函数
本文将着重介绍 Linux 内核的内存管理,尤为是 slab 分配提供的机制。性能
Linux 所使用的 slab 分配器的基础是 Jeff Bonwick 为 SunOS 操做系统首次引入的一种算法。Jeff 的分配器是围绕对象缓存进行的。在内核中,会为有限的对象集(例如文件描述符和其余常见结构)分配大量内存。Jeff 发现对内核中普通对象进行初始化所需的时间超过了对其进行分配和释放所需的时间。所以他的结论是不该该将内存释放回一个全局的内存池,而是将内存保持为针对特定目而初始化的状态。例如,若是内存被分配给了一个互斥锁,那么只需在为互斥锁首次分配内存时执行一次互斥锁初始化函数(mutex_init
)便可。后续的内存分配不须要执行这个初始化函数,由于从上次释放和调用析构以后,它已经处于所需的状态中了。spa
Linux slab 分配器使用了这种思想和其余一些思想来构建一个在空间和时间上都具备高效性的内存分配器。操作系统
图 1 给出了 slab 结构的高层组织结构。在最高层是 cache_chain
,这是一个 slab 缓存的连接列表。这对于 best-fit 算法很是有用,能够用来查找最适合所须要的分配大小的缓存(遍历列表)。cache_chain
的每一个元素都是一个 kmem_cache
结构的引用(称为一个 cache)。它定义了一个要管理的给定大小的对象池。code
每一个缓存都包含了一个 slabs 列表,这是一段连续的内存块(一般都是页面)。存在 3 种 slab:对象
slabs_full
slabs_partial
slabs_empty
注意 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 分配器还能够支持硬件缓存对齐和着色,这容许不一样缓存中的对象占用相同的缓存行,从而提升缓存的利用率并得到更好的性能。
如今来看一下可以建立新 slab 缓存、向缓存中增长内存、销毁缓存的应用程序接口(API)以及 slab 中对对象进行分配和释放操做的函数。
第一个步骤是建立 slab 缓存结构,您能够将其静态建立为:
struct struct kmem_cache *my_cachep;
而后其余 slab 缓存函数将使用该引用进行建立、删除、分配等操做。kmem_cache
结构包含了每一个中央处理器单元(CPU)的数据、一组可调整的(能够经过 proc 文件系统访问)参数、统计信息和管理 slab 缓存所必须的元素。
内核函数 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
参数指定了为缓存启用的选项。这些标志如表 1 所示。
选项 | 说明 |
---|---|
SLAB_RED_ZONE | 在对象头、尾插入标志,用来支持对缓冲区溢出的检查。 |
SLAB_POISON | 使用一种己知模式填充 slab,容许对缓存中的对象进行监视(对象属对象全部,不过能够在外部进行修改)。 |
SLAB_HWCACHE_ALIGN | 指定缓存对象必须与硬件缓存行对齐。 |
ctor
和 dtor
参数定义了一个可选的对象构造器和析构器。构造器和析构器是用户提供的回调函数。当从缓存中分配新对象时,能够经过构造器进行初始化。
在建立缓存以后, kmem_cache_create
函数会返回对它的引用。注意这个函数并无向缓存分配任何内存。相反,在试图从缓存(最初为空)分配对象时,refill 操做将内存分配给它。当全部对象都被使用掉时,也能够经过相同的操做向缓存添加内存。
内核函数 kmem_cache_destroy
用来销毁缓存。这个调用是由内核模块在被卸载时执行的。在调用这个函数时,缓存必须为空。
void kmem_cache_destroy( struct kmem_cache *cachep );
要从一个命名的缓存中分配一个对象,可使用 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 | 从高端内存中分配内存。 |
内核函数 kmem_cache_zalloc
与 kmem_cache_alloc
相似,只不过它对对象执行 memset
操做,用来在将对象返回调用者以前对其进行清除操做。
要将一个对象释放回 slab,可使用 kmem_cache_free
。调用者提供了缓存引用和要释放的对象。
void kmem_cache_free( struct kmem_cache *cachep, void *objp );
内核中最经常使用的内存管理函数是 kmalloc
和 kfree
函数。这两个函数的原型以下:
void *kmalloc( size_t size, int flags ); void kfree( const void *objp );
注意在 kmalloc
中,唯一两个参数是要分配的对象的大小和一组标志(请参看 表 2 中的部分列表)。可是 kmalloc
和 kfree
使用了相似于前面定义的函数的 slab 缓存。kmalloc
没有为要从中分配对象的某个 slab 缓存命名,而是循环遍历可用缓存来查找能够知足大小限制的缓存。找到以后,就(使用 __kmem_cache_alloc
)分配一个对象。要使用 kfree
释放对象,从中分配对象的缓存能够经过调用virt_to_cache
肯定。这个函数会返回一个缓存引用,而后在 __cache_free
调用中使用该引用释放对象。
slab 缓存 API 还提供了其余一些很是有用的函数。 kmem_cache_size
函数会返回这个缓存所管理的对象的大小。您也能够经过调用kmem_cache_name
来检索给定缓存的名称(在建立缓存时定义)。缓存能够经过释放其中的空闲 slab 进行收缩。这能够经过调用kmem_cache_shrink
实现。注意这个操做(称为回收)是由内核按期自动执行的(经过 kswapd
)。
unsigned int kmem_cache_size( struct kmem_cache *cachep ); const char *kmem_cache_name( struct kmem_cache *cachep ); int kmem_cache_shrink( struct kmem_cache *cachep );
下面的代码片段展现了建立新 slab 缓存、从缓存中分配和释放对象而后销毁缓存的过程。首先,必需要定义一个 kmem_cache
对象,而后对其进行初始化(请参看清单 1)。这个特定的缓存包含 32 字节的对象,而且是硬件缓存对齐的(由标志参数 SLAB_HWCACHE_ALIGN
定义)。
static struct kmem_cache *my_cachep; static void init_my_cache( void ) { my_cachep = kmem_cache_create( "my_cache", /* Name */ 32, /* Object Size */ 0, /* Alignment */ SLAB_HWCACHE_ALIGN, /* Flags */ NULL, NULL ); /* Constructor/Deconstructor */ return; }
使用所分配的 slab 缓存,您如今能够从中分配一个对象了。清单 2 给出了一个从缓存中分配和释放对象的例子。它还展现了两个其余函数的用法。
int slab_test( void ) { void *object; printk( "Cache name is %s\n", kmem_cache_name( my_cachep ) ); printk( "Cache object size is %d\n", kmem_cache_size( my_cachep ) ); object = kmem_cache_alloc( my_cachep, GFP_KERNEL ); if (object) { kmem_cache_free( my_cachep, object ); } return 0; }
最后,清单 3 演示了 slab 缓存的销毁。调用者必须确保在执行销毁操做过程当中,不要从缓存中分配对象。
static void remove_my_cache( void ) { if (my_cachep) kmem_cache_destroy( my_cachep ); return; }
proc 文件系统提供了一种简单的方法来监视系统中全部活动的 slab 缓存。这个文件称为 /proc/slabinfo,它除了提供一些能够从用户空间访问的可调整参数以外,还提供了有关全部 slab 缓存的详细信息。当前版本的 slabinfo 提供了一个标题,这样输出结果就更具可读性。对于系统中的每一个 slab 缓存来讲,这个文件提供了对象数量、活动对象数量以及对象大小的信息(除了每一个 slab 的对象和页面以外)。另外还提供了一组可调整的参数和 slab 数据。
要调优特定的 slab 缓存,能够简单地向 /proc/slabinfo 文件中以字符串的形式回转 slab 缓存名称和 3 个可调整的参数。下面的例子展现了如何增长 limit 和 batchcount 的值,而保留 shared factor 不变(格式为 “cache name limit batchcount shared factor”):
# echo "my_cache 128 64 8" > /proc/slabinfo
limit
字段表示每一个 CPU 能够缓存的对象的最大数量。 batchcount
字段是当缓存为空时转换到每一个 CPU 缓存中全局缓存对象的最大数量。 shared
参数说明了对称多处理器(Symmetric MultiProcessing,SMP)系统的共享行为。
注意您必须具备超级用户的特权才能在 proc 文件系统中为 slab 缓存调优参数。
[注]:本文转自IBM Developer:http://www.ibm.com/developerworks/cn/linux/l-linux-slab-allocator/