zsmalloc分配器源码分析

背景

在内核中,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)。完全满和完全空的内存页不保存其中。
完全满和完全空容易

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值