Memcached源码分析——slab的初始化

以下内容仅为本人的笔记。

/**
 * Determines the chunk sizes and initializes the slab class descriptors
 * accordingly.
 */
 /**
  * 确定chunk的大小,初始化slabs类的相应的描述符
  */
void slabs_init(const size_t limit, const double factor, const bool prealloc) {
    /**
     * #define POWER_SMALLEST 1          //POWER_SMALLEST的定义,表示slab中最少包含一个chunk
     * 下面这行中的i为chunk计数器,记录当前slab已经分了多少个chunk,初始值为0
     */
    int i = POWER_SMALLEST - 1;
    /**
     * 下面的size为chunk的大小,单位为字节。
     * size的大小为item所占内存大小与settings.chunk_size之和
     * settings.chunk_size的默认值为48,初始化在memcached-1.4.22/memcached.c的static void settings_init(void);中。
     * settings的定义在memcached-1.4.22/memcached.h中。
     */
    unsigned int size = sizeof(item) + settings.chunk_size;

    /**
     * 限制使用内存的总量为limit MB,默认为64MB,这个默认值是由 settings.maxbytes 决定的。
     */
    mem_limit = limit;

    /**
     * 是否预先在内存中分配一个默认大小为64MB的大chunk,默认 prealloc=false
     */
    if (prealloc) {
        /* Allocate everything in a big chunk with malloc */
        mem_base = malloc(mem_limit);
        if (mem_base != NULL) {
            mem_current = mem_base;
            mem_avail = mem_limit;
        } else {
            fprintf(stderr, "Warning: Failed to allocate requested memory in"
                    " one large chunk.\nWill allocate in smaller chunks\n");
        }
    }

    /**
     * 这行很简单,将slabclass对象的sizeof(slabclass)个字节全部初始化为0
     */
    memset(slabclass, 0, sizeof(slabclass));

    /**
     * 下面到了具体切割slab的地方了。
     * #define POWER_LARGEST  200   //这里定义的是slab的种类最多不得超过200种
     * settings.item_size_max为item的最大字节数,默认为1MB。
     * factor为chunk的增长系数,即增长为之前的factor倍。默认为1.25。
     */
    while (++i < POWER_LARGEST && size <= settings.item_size_max / factor) {
        /**
         * 下面的语句实现的功能:保证items的字节数为CHUNK_ALIGN_BYTES的整数倍
         * #define CHUNK_ALIGN_BYTES 8   //CHUNK_ALIGN_BYTES的定义
         */
        /* Make sure items are always n-byte aligned */
        if (size % CHUNK_ALIGN_BYTES)
            size += CHUNK_ALIGN_BYTES - (size % CHUNK_ALIGN_BYTES);

        /**
         * 定义当前slab中的chunk的大小。
         */
        slabclass[i].size = size;
        /**
         * 定义当前slab中的chunk的数目。
         */
        slabclass[i].perslab = settings.item_size_max / slabclass[i].size;
        size *= factor;
        if (settings.verbose > 1) {
            fprintf(stderr, "slab class %3d: chunk size %9u perslab %7u\n",
                    i, slabclass[i].size, slabclass[i].perslab);
        }
    }

    /**
     * 记录一共有多少种slabs
     */
    power_largest = i;
    /**
     * 设置slabs中尺寸最大的chunk的值
     */
    slabclass[power_largest].size = settings.item_size_max;
    /**
     * chunk尺寸最大的slab中chunk的数量
     */
    slabclass[power_largest].perslab = 1;
    if (settings.verbose > 1) {
        fprintf(stderr, "slab class %3d: chunk size %9u perslab %7u\n",
                i, slabclass[i].size, slabclass[i].perslab);
    }

    /* for the test suite:  faking of how much we‘ve already malloc‘d */
    {
        char *t_initial_malloc = getenv("T_MEMD_INITIAL_MALLOC");
        if (t_initial_malloc) {
            mem_malloced = (size_t)atol(t_initial_malloc);
        }

    }

    /**
     * 是否预分配
     */
    if (prealloc) {
        slabs_preallocate(power_largest);
    }
}

/**
 * powers-of-N allocation structures
 *代码在memcached-1.4.22/slabs.c
 */
typedef struct {
    unsigned int size;      /* sizes of items */
    unsigned int perslab;   /* how many items per slab */

    void *slots;           /* list of item ptrs */
    unsigned int sl_curr;   /* total free items in list */

    unsigned int slabs;     /* how many slabs were allocated for this class */

    void **slab_list;       /* array of slab pointers */
    unsigned int list_size; /* size of prev array */

    unsigned int killing;  /* index+1 of dying slab, or zero if none */
    size_t requested; /* The number of requested bytes */
} slabclass_t;
static slabclass_t slabclass[MAX_NUMBER_OF_SLAB_CLASSES];

/**
 * memcache中,每一条数据记录在一个item中,item的具体结构如下
 * Structure for storing items within memcached.
 * 以下代码在memcached-1.4.22/memcached.h的343行到368行
 */
typedef struct _stritem {
    struct _stritem *next;
    struct _stritem *prev;
    struct _stritem *h_next;    /* hash chain next */
    rel_time_t      time;       /* least recent access */
    rel_time_t      exptime;    /* expire time */
    int             nbytes;     /* size of data */
    unsigned short  refcount;
    uint8_t         nsuffix;    /* length of flags-and-length string */
    uint8_t         it_flags;   /* ITEM_* above */
    uint8_t         slabs_clsid;/* which slab class we‘re in */
    uint8_t         nkey;       /* key length, w/terminating null and padding */
    /* this odd type prevents type-punning issues when we do
     * the little shuffle to save space when not using CAS. */
    union {
        uint64_t cas;
        char end;
    } data[];
    /* if it_flags & ITEM_CAS we have 8 bytes CAS */
    /* then null-terminated key */
    /* then " flags length\r\n" (no terminating null) */
    /* then data with terminating \r\n (no terminating null; it‘s binary!) */
} item;

slabs_init函数所完成的工作仅仅是初始化slabclass数组,预定义好所有可能会使用到的slab的大小,并没有真正去申请内存空间。只有在真正执行写数据操作的时候才会根据具体数据的大小选择合适大小slab,然后申请空间并存储相关的数据。所以,当使用stats slabs命令查看memcached中已使用的slabs时会发现使用的slabs的编号不是连续的就是这个原因。

如下图:

技术分享

当使用stats slabs命令查看当前已经使用了哪些slabs时,发现显示的编号是1、4和9,这里其实只存了三条数据:

$str = str_repeat(‘i‘,100);
$memc->set(‘JUST_a_TEST‘,$str,10);
$str = str_repeat(‘i‘,500);
$memc->set(‘JUST_a_TEST‘,$str,10);
$str = str_repeat(‘i‘,5);
$memc->set(‘JUST_a_TEST‘,$str,10);

可见memcached确实是在真正执行写操作是才会去slabclass数组中选择合适大小的slab,然后进行存储的。图中显示的编号其实就是相应slab在slabclass数组中的下标。

这个图中还可以看出一个问题:同一个key存储了三次,每次对应的value大小不一样,没有彼此覆盖,而是一块存在在内存中,这也是memcached使用slab进行存储的一个特点。

 

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。