您好,登錄后才能下訂單哦!
三、代碼結構(1) 基礎構架
邏輯推理地看源碼是學習代碼最清晰的方法,這樣對代碼的記憶會提高很多。
能夠從復雜的代碼結構中找到邏輯關系也是非常重要的一個技能。
以上是dm dedup的主要代碼邏輯關系。
因為其主要的設計已經在上一篇有介紹過了,所以我們這里直接分析代碼流程。
四、代碼結構(1) I/O入口 dm_dedup_map
1、dm_dedup_map:這個是從dm.c->dm_dedup.c主要調用接口
① 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 個人發布,公眾號發布:存儲之谷】,如需轉載,請于本人聯系,謝謝。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。