六、代码结构(4) I/O “小”写流程
上一篇,介绍了dm dedup的写流程,这一篇,介绍它的一个特殊流程
如果我们接收到的对齐bio但是它的size < block_size,那么这时候是不能直接进行hash的。
需要将它的缺少的部分读出来,填充成一个完整的block_size才能计算hash。
接下来我们就介绍这一部分的代码流程。
static int handle_write(struct dedup_config *dc, struct bio *bio) { u64 lbn; u8 hash[MAX_DIGEST_SIZE]; struct hash_pbn_value hashpbn_value; u32 vsize; struct bio *new_bio = NULL; int r; /* If there is a data corruption make the device read-only */ if (dc->corrupted_blocks > dc->fec_fixed) return -EIO; dc->writes++; /* Read-on-write handling */ if (bio->bi_iter.bi_size < dc->block_size) { dc->reads_on_writes++; new_bio = prepare_bio_on_write(dc, bio); if (!new_bio || IS_ERR(new_bio)) return -ENOMEM; bio = new_bio; } /*.....*/ }
对于“小”写这种操作,也被叫做reads_on_writes,或者read_motify_write
一起看看这个new_bio是如何被构造出来的。
struct bio *prepare_bio_on_write(struct dedup_config *dc, struct bio *bio) { int r; sector_t lbn; uint32_t vsize; struct lbn_pbn_value lbnpbn_value; struct bio *clone; //DMINFO("\nEntered prepare bio on write"); lbn = compute_sector(bio, dc); (void) sector_div(lbn, dc->sectors_per_block); /* check for old or new lbn and fetch the appropriate pbn */ r = dc->kvs_lbn_pbn->kvs_lookup(dc->kvs_lbn_pbn, (void *)&lbn, sizeof(lbn), (void *)&lbnpbn_value, &vsize); if (r == -ENODATA) clone = prepare_bio_without_pbn(dc, bio); else if (r == 0) clone = prepare_bio_with_pbn(dc, bio, lbnpbn_value.pbn * dc->sectors_per_block); else return ERR_PTR(r); //DMINFO("\nExiting prpare_bio_on_write"); return clone; }
我们注意:这里compute_sector算出来的,是bio sector的lbn
①:它有以下几种情况:
[x---y]
[x--------z] -> x/block_size
[x----y]
[a--------y] -> x/block_size
[x--y]
[a----------z] -> x/block_size
他们都会得到相同的lbn=x/block_size
② dc->kvs_lbn_pbn->kvs_lookup
这里需要将刚才算出来的lbn来求出是否存在,不存在填充zero。
接下啦就是两种情况了,这个lbn是否存在pbn。
一、 lbn without pbn "clone = prepare_bio_without_pbn(dc, bio);"
static struct bio *prepare_bio_without_pbn(struct dedup_config *dc, struct bio *bio) { int r = 0; struct bio *clone = NULL; clone = create_bio(dc, bio); if (!clone) goto out; zero_fill_bio(clone); r = merge_data(dc, bio_page(clone), bio); if (r < 0) return ERR_PTR(r); out: return clone; }
这个很容易理解,既然不存在lbn_pbn的对应关系,那么这里就是直接填充zero。
这里用了内核提供的函数zero_fill_bio。如果我做,我可能不知道这个函数,大家要利用这个函数。
那么这里很简单,就是先创建一个null的bio,然后把这个bio的page全部填充成zero。
在和bio的进行合并。这里merge_data是代码实现的,我们看一下。
static int merge_data(struct dedup_config *dc, struct page *page, struct bio *bio) { sector_t bi_sector = bio->bi_iter.bi_sector; void *src_page_vaddr, *dest_page_vaddr; int position, err = 0; struct bvec_iter iter; struct bio_vec bvec; /* Relative offset in terms of sector size */ position = sector_div(bi_sector, dc->sectors_per_block); if (!page || !bio_page(bio)) { err = -EINVAL; goto out; } /* Locating the right sector to merge */ dest_page_vaddr = page_address(page) + to_bytes(position); bio_for_each_segment(bvec, bio, iter) { src_page_vaddr = page_address(bio_iter_page(bio, iter)) + bio_iter_offset(bio, iter); /* Merging Data */ memmove(dest_page_vaddr, src_page_vaddr, bio_iter_len(bio, iter)); /* Updating destinaion address */ dest_page_vaddr += bio_iter_len(bio, iter); } out: return err; }
我们做块级功能研发的人,要对内存和bio sectors操作也要熟悉,
我曾经有段时间自己写代码就被segments和sectors各种对应绕晕。
这个代码写的还是比较鲜明的,先找出bi_sector在block_size内的位置
比如
[01234567] [01234567]
< data > free<>
< 00000000 > ------------><000data0>
position就是3
这时候,用page_address将[position]的数据转换成ptr
然后将data memmove到3456的<0-0>的ptr上面就可以了。
那么有一点复杂的是< data >他也可能是割裂的比如:
sector [01234567] [01234567] [01234567]
s1 < da> free<s1> free<s1>
s2 <ta > <ta > free<s2>
< 00000000 > ----><000da000> --><000data0>
这里需要将多个segments的page,s1和s2的内容,分别memmove到page里。
--------------未完待续--------------
【本文只在51cto博客作者 “底层存储技术” http://blog.51cto.com/12580077 个人发布,公众号发布:存储之谷】,如需转载,请于本人联系,谢谢。