千家信息网

device-mapper 块级重删(dm dedup) <3>代码结构(2)

发表于:2024-11-18 作者:千家信息网编辑
千家信息网最后更新 2024年11月18日,四、代码结构(2) space manager这一篇和下一篇我们来介绍dm dedup的空间管理的部分和核心流程I/O写流程在此之前,我们先分析一下用到的资源有哪些,和了解dm dedup的space
千家信息网最后更新 2024年11月18日device-mapper 块级重删(dm dedup) <3>代码结构(2)

四、代码结构(2) space manager

这一篇和下一篇我们来介绍dm dedup的空间管理的部分和核心流程I/O写流程

在此之前,我们先分析一下用到的资源有哪些,和了解dm dedup的space manager空间管理器

空间管理器,是一个巨型的数组,以allocptr申请指针为标,对整个space进行扫描一周(回到current allocptr)。
用来找到空闲的块(白色),并把它分配给一个hash request,把它变成一个绿色的块,并把这个信息hash_pbn,放到kvs_hash_pbn的表内。

kvs_lbn_pbn和kvs_hash_pbn的空间是预先分配好的,所以这两个表的index都是有确定的含义,非常利于查找。

dc->kvs_hash_pbn = dc->mdops->kvs_create_sparse(md, crypto_key_size,sizeof(struct hash_pbn_value),dc->pblocks, unformatted);dc->kvs_lbn_pbn = dc->mdops->kvs_create_linear(md, 8,sizeof(struct lbn_pbn_value), dc->lblocks, unformatted);

在创建kvs(key value space)的时候,有两种选择,一种是按照linear就是一一映射的lbn-pbn的方式,还有一种是hash index的方式。

其中都会将ksize和vsize会在两种space类型不同而会以不同的方式保存。

① kvs-lbn-pbn,linear的create space方式inram

static struct kvstore *kvs_create_linear_inram(struct metadata *md,u32 ksize, u32 vsize,u32 kmax, bool unformatted){        struct kvstore_inram *kvs;        u64 kvstore_size, tmp;        kvs = kmalloc(sizeof(*kvs), GFP_NOIO);        if (!kvs)            return ERR_PTR(-ENOMEM);        kvstore_size = (kmax + 1) * vsize;        kvs->store = vmalloc(kvstore_size);        /*确定kvs->store的大小,这里的思想很简单,        就是64 bit的lbn寻址到一个ksize的pbn上面,一般的pbn也是64 bit*/        /*kmax是 逻辑设备的大小,这个map-table的含义就是lbn-pbn的映射关系*/        tmp = kvstore_size;        (void)do_div(tmp, (1024 * 1024));        memset(kvs->store, EMPTY_ENTRY, kvstore_size);        kvs->ckvs.vsize = vsize;        kvs->ckvs.ksize = ksize;        kvs->kmax = kmax;        kvs->ckvs.kvs_insert = kvs_insert_linear_inram;   /*插入api*/        kvs->ckvs.kvs_lookup = kvs_lookup_linear_inram; /*查找api*/        kvs->ckvs.kvs_delete = kvs_delete_linear_inram; /*删除api*/        kvs->ckvs.kvs_iterate = kvs_iterate_linear_inram; /*迭代api*/        md->kvs_linear = kvs;         return &(kvs->ckvs);}

我们简单看一看kvs_insert_linear_inram和kvs_lookup_linear_inram,删除和迭代留在垃圾回收的部分介绍。

static int kvs_insert_linear_inram(struct kvstore *kvs, void *key,s32 ksize, void *value,int32_t vsize){    u64 idx;    char *ptr;    struct kvstore_inram *kvinram = NULL;    kvinram = container_of(kvs, struct kvstore_inram, ckvs);    idx = *((uint64_t *)key);    ptr = kvinram->store + kvs->vsize * idx; /*以lbn为key,pbn为value的map*/    memcpy(ptr, value, kvs->vsize);    return 0;}

插入的代码非常简单,就可以理解成linear是个 u64 kvs[kmax] 这样的数组,lbn是数组下标,而value是数组内容。

static int kvs_lookup_linear_inram(struct kvstore *kvs, void *key,                   s32 ksize, void *value,                   int32_t *vsize){    u64 idx;    char *ptr;    int r = -ENODATA;    struct kvstore_inram *kvinram = NULL;    kvinram = container_of(kvs, struct kvstore_inram, ckvs);    idx = *((uint64_t *)key);    ptr = kvinram->store + kvs->vsize * idx;    if (is_empty(ptr, kvs->vsize))        return r;    memcpy(value, ptr, kvs->vsize);    *vsize = kvs->vsize;    return 0;}

② kvs-hash-pbn,sparse的create space方式inram
sparse 的方式和linear不太一样,它的组织形式会更加复杂一些
他要存 key和value两个,key_size是采用hash算法的大小,如:md5是128 bit,value是pbn是64 bit

static struct kvstore *kvs_create_sparse_inram(struct metadata *md,                           u32 ksize, u32 vsize,                           u32 knummax, bool unformatted){    struct kvstore_inram *kvs;    u64 kvstore_size, tmp;    kvs = kmalloc(sizeof(*kvs), GFP_NOIO); /* knummax key的最大值这里是按照pbn的最大值申请的,物理设备的大小*/    knummax += (knummax * HASHTABLE_OVERPROV) / 100;/*额外申请了十分之一的空间*/    kvstore_size = (knummax * (vsize + ksize));  /*申请单位是vsize(pbn)64bit ksize(hash_size)128 bit*/    kvs->store = vmalloc(kvstore_size);    tmp = kvstore_size;    (void)do_div(tmp, (1024 * 1024));    memset(kvs->store, EMPTY_ENTRY, kvstore_size);    /*将所有的key都预先变成EMPTY_ENTRY= 0xFB(最新的代码4.13是0xFF)*/    kvs->ckvs.vsize = vsize;    kvs->ckvs.ksize = ksize;    kvs->kmax = knummax;    kvs->ckvs.kvs_insert = kvs_insert_sparse_inram; /*插入api*/    kvs->ckvs.kvs_lookup = kvs_lookup_sparse_inram; /*查找api*/    kvs->ckvs.kvs_delete = kvs_delete_sparse_inram; /*删除api*/    kvs->ckvs.kvs_iterate = kvs_iterate_sparse_inram; /*迭代api*/    md->kvs_sparse = kvs;    return &(kvs->ckvs);}

接下来看一下插入和查找函数,这个函数会比linear的难一些,只要一点例子会很好理解。

看到调试信息中有初始化的过程。
其中也包括两个写流程包括lookup和insert,为了帮助快速理解,就按照这个例子来说会比较容易。

static int kvs_insert_sparse_inram(struct kvstore *kvs, void *key,s32 ksize, void *value, s32 vsize){    u64 idxhead = *((uint64_t *)key); /*无论key是128bit还是64bit,我们取64bit出来*/    u32 entry_size, head, tail;    char *ptr;    struct kvstore_inram *kvinram = NULL;    kvinram = container_of(kvs, struct kvstore_inram, ckvs);    entry_size = kvs->vsize + kvs->ksize;  /*确立每个单位entry_size,一般是:64bit + 128 bit*/    head = do_div(idxhead, kvinram->kmax);     /*这一步算出具体key在散列的位置,是把idxhead取余为head,    hash出来的key相同的概率之前算过了是非常低,除以kmax取余,    就是给key在找位置,这个位置head也一部分key的散列属性*/    tail = head;    /*这个循环很逗,虽然我的调试里没有显示出它起了作用,    但是我们前面说head虽然具有一定的散列属性,但它在这里并不具有唯一性,    因为key本身非常大128bit,他能代表一个pbn的内容的唯一性,    取余出的head却是一个小值,他不能唯一代表pbn,    虽然你可以认为head:150702代表idxhead:0x24100420901436,    并且在这个head位置保存它,但它却没有一唯一性,    那么如果产生了取余后的head所在位置的*ptr已经存在了,    就需要给这个key另找一个head来保存它,代码这里的做法是向后取,    找到一个NULL或者被deleted掉的*ptr,在这个位置把key记录下来*/    do {        ptr = kvinram->store + entry_size * head;    if (is_empty(ptr, entry_size) || is_deleted(ptr, entry_size)) {        memcpy(ptr, key, kvs->ksize);        memcpy(ptr + kvs->ksize, value, kvs->vsize);        return 0;    }    head = next_head(head, kvinram->kmax);    } while (head != tail);    return -ENOSPC;}

查找的代码和插入的代码,几乎就是一样的。
就是在do{}while里先从head开始找,找到一样的key值,就把它的value(pbn)拿出来。

static int kvs_lookup_sparse_inram(struct kvstore *kvs, void *key,s32 ksize, void *value, int32_t *vsize){    u64 idxhead = *((uint64_t *)key);    u32 entry_size, head, tail;    char *ptr;    struct kvstore_inram *kvinram = NULL;    int r = -ENODATA;    kvinram = container_of(kvs, struct kvstore_inram, ckvs);    entry_size = kvs->vsize + kvs->ksize;    head = do_div(idxhead, kvinram->kmax);    tail = head;    do {        ptr = kvinram->store + entry_size * head;        if (is_empty(ptr, entry_size))            return r;        if (memcmp(ptr, key, kvs->ksize)) {            head = next_head(head, kvinram->kmax);        } else {            memcpy(value, ptr + kvs->ksize, kvs->vsize);            return 0;        }    } while (head != tail);    return r;}

这里可能大家就会觉得很奇怪,为什么这里是这么简单的搜索方法,为什么不排序等等。

这里我的思考是这样的:首先这个是在inram的搜索,所以本身查找的性能还是很高的,其次在这个key取余的head值,虽然它不具有唯一性,但他具有key的散列属性,也就是说让它产生冲突的概率都不高,所以在pbn没有申请很多的情况下,应该都是直接一步就可以找到的,而且就算产生了冲突,它也是向后找,很可能就是下一个,所以就算碰撞,它保存的位置也具有相关联性,都会在它的后面的几个就可以找到,我认为这个性能应该还不错。

【本文只在51cto博客作者 "底层存储技术" https://blog.51cto.com/12580077 个人发布,公众号发布:存储之谷】,如需转载,请于本人联系,谢谢。

0