中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

device-mapper 塊級重刪(dm dedup) <3>代碼結構(1)

發布時間:2020-03-30 23:25:27 來源:網絡 閱讀:1362 作者:慢慢存儲路 欄目:軟件技術

三、代碼結構(1) 基礎構架

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

能夠從復雜的代碼結構中找到邏輯關系也是非常重要的一個技能。

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

四、代碼結構(1) I/O入口 dm_dedup_map

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

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

    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進行分析

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的?
切分這個讀者應該都很容易看懂,就是不斷去ci->sector += len和ci->sector_count -= len;
通過將ci->sector不斷通過len增加,然后ci->sector_count總量不斷減少
制造一個個被split的sub_BIOs。
② 為什么說切分是對齊的?
這就涉及到len的大小,這里我們舉個例子:
bio:【bi_sector:3 size=8】應該被切分為什么樣子?
如果按照size=4去切分?那應該對其后的結果是:
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;
其實這里我們演算出來的規律,正是max_io_len的代碼的邏輯關系:

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里傳的一個參數block_size所決定的,也就是塊大小。
這個block_size值得就是hash index的單位,在dm_dedup里它內約束在了4k到1M的區間內.
#define MIN_DATA_DEV_BLOCK_SIZE (4 1024)
#define MAX_DATA_DEV_BLOCK_SIZE (1024
1024)

OK ,目前我們約定俗成的認為它就是page size 4k,那么這樣就很好理解了。
這樣被對齊split后的bio,為什么要對齊split,主要是為了對齊split bio能夠對應一個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去分發請求到各個cpu。
這里如果想做的更好一點,可以做一個cpu池,在創建設備的時候可讓配置其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去分發請求。

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

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

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

西青区| 大埔县| 鹿泉市| 青阳县| 长子县| 集安市| 车致| 蒲城县| 错那县| 南江县| 邵东县| 沽源县| 镇雄县| 东台市| 巴马| 九江市| 三原县| 宁安市| 荔波县| 五河县| 新泰市| 勐海县| 新闻| 江城| 浦江县| 灯塔市| 明光市| 嵊州市| 余姚市| 古交市| 延庆县| 阿克苏市| 日喀则市| 八宿县| 兴国县| 河曲县| 桃江县| 应城市| 科技| 罗甸县| 绥中县|