device-mapper塊級重刪(dmdedup)<3>代碼結(jié)構(gòu)(1)

三、代碼結(jié)構(gòu)(1) 基礎(chǔ)構(gòu)架

成都創(chuàng)新互聯(lián)公司專業(yè)為企業(yè)提供伊吾網(wǎng)站建設(shè)、伊吾做網(wǎng)站、伊吾網(wǎng)站設(shè)計、伊吾網(wǎng)站制作等企業(yè)網(wǎng)站建設(shè)、網(wǎng)頁設(shè)計與制作、伊吾企業(yè)網(wǎng)站模板建站服務(wù),10余年伊吾做網(wǎng)站經(jīng)驗,不只是建網(wǎng)站,更提供有價值的思路和整體網(wǎng)絡(luò)服務(wù)。

邏輯推理地看源碼是學(xué)習(xí)代碼最清晰的方法,這樣對代碼的記憶會提高很多。

能夠從復(fù)雜的代碼結(jié)構(gòu)中找到邏輯關(guān)系也是非常重要的一個技能。

device-mapper 塊級重刪(dm dedup) <3>代碼結(jié)構(gòu)(1)
以上是dm dedup的主要代碼邏輯關(guān)系。
因為其主要的設(shè)計已經(jīng)在上一篇有介紹過了,所以我們這里直接分析代碼流程。

四、代碼結(jié)構(gòu)(1) I/O入口 dm_dedup_map

1、dm_dedup_map:這個是從dm.c->dm_dedup.c主要調(diào)用接口
device-mapper 塊級重刪(dm dedup) <3>代碼結(jié)構(gòu)(1)

① chunk data的對其切分
首先要解釋的是:圖中chunk bio的過程,是由dm.c中的split_and_process_bio實現(xiàn)的

    while (ci.sector_count && !error) {
            error = __split_and_process_non_flush(&ci);
            if (current->bio_list && ci.sector_count && !error) {

                struct bio *b = bio_split(bio, bio_sectors(bio) - ci.sector_count,
                              GFP_NOIO, &md->queue->bio_split);
                ci.io->orig_bio = b;
                bio_chain(b, bio);
                ret = generic_make_request(bio);
                break;
            }
        }

這段其中比較核心的是split大的BIO變成以某個方式對齊
看明白如何對齊split的,就必須對_split_and_process_non_flush進(jìn)行分析

static int __split_and_process_non_flush(struct clone_info *ci)
{
    struct bio *bio = ci->bio;
    struct dm_target *ti;
    unsigned len;
    int r;

    ti = dm_table_find_target(ci->map, ci->sector);
    if (!dm_target_is_valid(ti))
        return -EIO;

    if (unlikely(__process_abnormal_io(ci, ti, &r)))
        return r;

    if (bio_op(bio) == REQ_OP_ZONE_REPORT)
        len = ci->sector_count;
    else
        len = min_t(sector_t, max_io_len(ci->sector, ti),
                ci->sector_count);

    r = __clone_and_map_data_bio(ci, ti, ci->sector, &len);
    if (r < 0)
        return r;

    ci->sector += len;
    ci->sector_count -= len;

    return 0;
}

首先I/O對齊split中有比較重要的就是幾個問題:
① 到底是如何切分BIO的?
切分這個讀者應(yīng)該都很容易看懂,就是不斷去ci->sector += len和ci->sector_count -= len;
通過將ci->sector不斷通過len增加,然后ci->sector_count總量不斷減少
制造一個個被split的sub_BIOs。
② 為什么說切分是對齊的?
這就涉及到len的大小,這里我們舉個例子:
bio:【bi_sector:3 size=8】應(yīng)該被切分為什么樣子?
如果按照size=4去切分?那應(yīng)該對其后的結(jié)果是:
3%4 = 3 ,bio_split_1 = [1_bi_sector:3,1_size=1],t_size = 8-1=7;bi_sector:4;
4%4 =0, bio_split_2 = [2_bi_sectoer:4,2_size=4],t_size= 7-4=3;bi_secor:8;
8%4 = 0,bio_split_3 = [3_bi_sectoer:8,3_size=3],t_size= 3-4=-1;bi_secor:11;
其實這里我們演算出來的規(guī)律,正是max_io_len的代碼的邏輯關(guān)系:

static sector_t max_io_len(sector_t sector, struct dm_target *ti)
{
    sector_t len = max_io_len_target_boundary(sector, ti);
    sector_t offset, max_len;

    /*
     * Does the target need to split even further?
     */
    if (ti->max_io_len) {
        offset = dm_target_offset(ti, sector);
        if (unlikely(ti->max_io_len & (ti->max_io_len - 1)))
            max_len = sector_div(offset, ti->max_io_len);
        else
            max_len = offset & (ti->max_io_len - 1);
        max_len = ti->max_io_len - max_len;

        if (len > max_len)
            len = max_len;
    }

    return len;
}

③ 明白了切分的方法,那么還有一個問題就是,max_io_len的n%splt_size的ti>max_io_len是多少呢?
按照多大切分的我們也需要搞明白一下。
這個過程很簡單,大概的過程就是向上推找到這個值的賦值,初始含義和可配置的地方。

最終看到這個值是在dm_dedup_ctr里傳的一個參數(shù)block_size所決定的,也就是塊大小。
這個block_size值得就是hash index的單位,在dm_dedup里它內(nèi)約束在了4k到1M的區(qū)間內(nèi).
#define MIN_DATA_DEV_BLOCK_SIZE (4 1024)
#define MAX_DATA_DEV_BLOCK_SIZE (1024
1024)

OK ,目前我們約定俗成的認(rèn)為它就是page size 4k,那么這樣就很好理解了。
這樣被對齊split后的bio,為什么要對齊split,主要是為了對齊split bio能夠?qū)?yīng)一個pbn,這樣就可以以某個pbn的hash來代表它。

② 多線程處理每個chunk_bio

static int dm_dedup_map(struct dm_target *ti, struct bio *bio)
{
    dedup_defer_bio(ti->private, bio);

    return DM_MAPIO_SUBMITTED;
}

static void dedup_defer_bio(struct dedup_config *dc, struct bio *bio)
{
    struct dedup_work *data;

    data = mempool_alloc(dc->dedup_work_pool, GFP_NOIO);
    if (!data) {
        bio->bi_error = -ENOMEM;
        bio_endio(bio);
        return;
    }

    data->bio = bio;
    data->config = dc;

    INIT_WORK(&(data->worker), do_work);

    queue_work(dc->workqueue, &(data->worker));
}

這個代碼原理非常簡單,用mempool申請work,用queue_work去分發(fā)請求到各個cpu。
這里如果想做的更好一點,可以做一個cpu池,在創(chuàng)建設(shè)備的時候可讓配置其cpu親和,單cpu命令隊列深度(最大IO合并的大小)。

static void process_bio(struct dedup_config *dc, struct bio *bio)
{
    int r;

    if (bio->bi_opf & (REQ_PREFLUSH | REQ_FUA) && !bio_sectors(bio)) {
        r = dc->mdops->flush_meta(dc->bmd);
        if (r == 0)
            dc->writes_after_flush = 0;
        do_io_remap_device(dc, bio);
        return;
    }

    switch (bio_data_dir(bio)) {
    case READ:
        r = handle_read(dc, bio);
        break;
    case WRITE:
        r = handle_write(dc, bio);
    }

    if (r < 0) {
        bio->bi_error = r;
        bio_endio(bio);
    }
}

最后解析一下bio讀寫的方向然后去給handle_read和handle_write去分發(fā)請求。

如果認(rèn)真看的讀者,應(yīng)該已經(jīng)清楚明白了,map的流程就是:dm_bio(大bio)被以block_size對齊split后帶多cpu處理的一個流程。
這里是dm-dedup的發(fā)動機,很多人可能要問,為什么這里要做成異步處理的形式,為什么不直接就在上層派發(fā)dm_bio的task里就把dedup的工作做完?
我認(rèn)為這里這么做,主要是考慮到了dedup算hash index需要大量的時間,所以高并發(fā)情況下這個程序最終表現(xiàn)出的性能,可能都在多個cpu在計算hash上面。
如果在dm_bio的task里面做hash ,相當(dāng)于沒有流水線并發(fā)能力,單線程在算hash,計算就會是io性能的瓶頸,這里比較好的解決了這個問題,但是這里沒有很好的考慮到I/O合并(如果I/O不能合并,可能會造成巨大的I/O latency),和各個cpu的請求隊列深度均衡問題。

【本文只在51cto博客作者 “底層存儲技術(shù)” https://blog.51cto.com/12580077 個人發(fā)布,公眾號發(fā)布:存儲之谷】,如需轉(zhuǎn)載,請于本人聯(lián)系,謝謝。

當(dāng)前名稱:device-mapper塊級重刪(dmdedup)<3>代碼結(jié)構(gòu)(1)
網(wǎng)站網(wǎng)址:http://muchs.cn/article26/pipdjg.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供企業(yè)建站、網(wǎng)頁設(shè)計公司域名注冊、做網(wǎng)站搜索引擎優(yōu)化、電子商務(wù)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

成都seo排名網(wǎng)站優(yōu)化