背景
在内核中,slab分配器用于分配较小块内存,它将多个相同大小的对象放在同一个内存页中,但这些对象可能不能正好占满整个内存页,从而产生碎片造成浪费,于是内核尝试为对象分配多个物理连续内存页从而使碎片尽可能小,但在低内存设备上要分配多个连续内存页可能十分困难,特别在系统运行较长时间后,这变得几乎不可能。最坏情况下,当对象大小略大于PAGE_SIZE/2时,每个内存页接近一般的内存将被浪费。
所以,我们需要一种新的分配器,用于像vmalloc那样分配只要求虚拟连续的内存块。
Zsmalloc分配器尝试将多个相同大小的对象存放在组合页(称为zspage)中,这个组合页不要求物理连续,从而提高内存的使用率。但是,使用zsmalloc分配器有以下条件:
- 不要求物理内存连续
- 在使用对象前必须显示映射
- 对象必须在原子上下文中访问
分配器API
struct zs_pool *zs_create_pool(const char *name, gfp_t flags);
该函数用于创建一个zsmalloc的内存池,在使用zsmalloc之前必须调用该函数创建zs_pool。name是内存池的名称,flags是分配标志。zs_pool的定义在实现文件中,因为使用者不需要关心它的定义。
void zs_destroy_pool(struct zs_pool *pool);
该函数释放zs_pool实例。当一个pool被释放后,从其中分配的对象将不可访问。
unsigned long zs_malloc(struct zs_pool *pool, size_t size);
void zs_free(struct zs_pool *pool, unsigned long obj);
zs_malloc和zs_free分别用于分配和释放对象。
size是需要分配的内存块的大小(字节),如果成功,返回一个对象的handle,否则返回0。
这个handle不是一个可以直接访问的对象,要获得真正可访问的对象需要调用如下函数进行映射:
void *zs_map_object(struct zs_pool *pool, unsigned long handle, enum zs_mapmode mm);
handle是从zs_malloc获得的待映射的handle,mm为映射模式:
enum zs_mapmode {
ZS_MM_RW, /* normal read-write mapping */
ZS_MM_RO, /* read-only (no copy-out at unmap time) */
ZS_MM_WO /* write-only (no copy-in at map time) */
};
该函数的返回值是一个可访问的对象的虚拟地址。该地址本质上是一个per-CPU对象,在同一时间,在确定的cpu上,只有一个对象可以被安全的映射,同时调用该函数也将进入原子上下文,直到调用zs_unmap_object解除映射:
void zs_unmap_object(struct zs_pool *pool, unsigned long handle);
API的声明在include/linux/zsmalloc.h中;实现在mm/zsmalloc.c中。本文使用的内核版本为4.4-rc5
数据结构
struct zs_pool {
const char *name;
struct size_class **size_class;
struct kmem_cache *handle_cachep;
gfp_t flags; /* allocation flags used when growing pool */
atomic_long_t pages_allocated;
struct zs_pool_stats stats;
/* Compact classes */
struct shrinker shrinker;
/*
* To signify that register_shrinker() was successful
* and unregister_shrinker() will not Oops.
*/
bool shrinker_enabled;
#ifdef CONFIG_ZSMALLOC_STAT
struct dentry *stat_dentry;
#endif
};
name 该pool的名称
size_class 该成员是一个指针数组,数组的每一项都指向一个struct size_class结构,该结构保存了为分配特定大小对象的内存页。
handle_cachep slab缓存池,后面介绍
flags 分配标志
pages_allocated 已分配的内存页个数
stats 内存池统计信息
struct zs_pool_stats {
/* How many pages were migrated (freed) */
unsigned long pages_compacted;
};
可以看到,该成员只保存了在收缩时有多少页被释放掉。
shrinker 用来缩减内核缓存的收缩器。暂不讨论
stat_dentry 当开启zsmalloc统计功能时有该成员,用于在proc中显示统计信息。暂不讨论
enum fullness_group {
ZS_ALMOST_FULL,
ZS_ALMOST_EMPTY,
_ZS_NR_FULLNESS_GROUPS,
ZS_EMPTY,
ZS_FULL
};
enum zs_stat_type {
OBJ_ALLOCATED,
OBJ_USED,
CLASS_ALMOST_FULL,
CLASS_ALMOST_EMPTY,
};
#ifdef CONFIG_ZSMALLOC_STAT
#define NR_ZS_STAT_TYPE (CLASS_ALMOST_EMPTY + 1)
#else
#define NR_ZS_STAT_TYPE (OBJ_USED + 1)
#endif
struct zs_size_stat {
unsigned long objs[NR_ZS_STAT_TYPE];
};
struct size_class {
spinlock_t lock;
struct page *fullness_list[_ZS_NR_FULLNESS_GROUPS];
/*
* Size of objects stored in this class. Must be multiple
* of ZS_ALIGN.
*/
int size;
unsigned int index;
/* Number of PAGE_SIZE sized pages to combine to form a 'zspage' */
int pages_per_zspage;
struct zs_size_stat stats;
/* huge object: pages_per_zspage == 1 && maxobj_per_zspage == 1 */
bool huge;
};
fullness_list 保存了几乎满或几乎空的内存页链表(zspage)。完全满和完全空的内存页不保存其中。
完全满和完全空容易