RAID5 IO處理之寫請求代碼詳解

我們知道RAID5一個條帶上的數據是由N個數據塊和1個校驗塊組成,其校驗塊由N個數據塊通過異或運算得出,這樣才能在任意一個成員磁盤失效時通過其他N個成員磁盤恢復出用戶寫入的數據 。這也就要求RAID5條帶上的數據是一致的、同步的 。
1 寫入方式當新數據寫入時就需要重新計算校驗值,計算方式由以下兩種:

  1. 將條帶上沒有寫請求的位置的數據讀出,然后使用新數據和舊數據兩者重新計算校驗
  2. 將條帶上將要寫數據的位置的數據和校驗數據讀出 , 然后試用新數據、舊數據和舊校驗三者重新計算校驗
【RAID5 IO處理之寫請求代碼詳解】我們以5塊盤創建的RAID5為例,其每個條帶上共有4個數據塊和1個校驗塊 。假設某個條帶上的數據塊依次為D1、D2、D3、D4 , 校驗塊為P1,在條帶一致的情況下:P1 = D1 ⊕ D2 ⊕ D3 ⊕ D4 ?,F在新的數據D1'要覆蓋原來的D1,兩種寫入方式的操作如下:
第一種方式:先讀出D2、D3、D4,再加上要寫入的D1'計算出P1',即:P1' = D2 ⊕ D3 ⊕ D4 ⊕ D1'第二種方式:先讀出D1、P1 , 再加上要寫入的D1'計算出P1',即:P1' = D1 ⊕ P1 ⊕ D1'
從具體的例子我們可以看出兩種方式的本質區別在于要讀出舊數據的個數不同,讀的越少性能越好 。因此在處理邏輯中會先計算那種方式需要讀出的數據更少,然后采用該種寫方式 。第一種讀其他舊數據直接計算新校驗的寫方式稱為 重構寫 ,第二種讀舊數據和舊校驗的方式稱為 讀改寫。
2 讀改寫我們以5塊盤chunk為4K的RAID5為例 。按照前文的分析,如果我們在RAID5的起始位置寫入大小為4K的IO , 此時會采用讀改寫的方式下發請求 。代碼分析如下:
2.1 接收請求函數調用關系:
raid5_make_request() \_ add_stripe_bio() \_ raid5_release_stripe() \_ md_wakeup_thread()接收到請求后,在 raid5_make_request() 按4K大小切分bio,通過 add_stripe_bio() 將bio掛在到條帶上 , 推入條帶狀態機進行處理 。
2.2 下發讀請求函數調用關系:
handle_stripe() \_ analyse_stripe() \_ handle_stripe_dirtying() \_ ops_run_io()代碼解析:
static void handle_stripe(struct stripe_head *sh){ /* 解析條帶狀態 */ analyse_stripe(sh, &s); /* to_write條件成立設置需要讀數據的條帶/設備 */ if (s.to_write && !sh->reconstruct_state && !sh->check_state)handle_stripe_dirtying(conf, sh, &s, disks); /* 下發讀請求 */ ops_run_io(sh, &s);}static void analyse_stripe(struct stripe_head *sh, struct stripe_head_state *s){ rcu_read_lock(); /* 遍歷所有條帶/設備 */ for (i = disks; i--; ) {dev = &sh->dev[i];/* 寫請求掛到條帶設備的towrite上此時為真 */if (dev->towrite) {s->to_write++;/** 條帶頭處理數據塊的大小為4K,與內核page大小一致* 因此如果寫的數據不足4K時需要先將原來的4K數據讀出來再用新的數據覆蓋* R5_OVERWRITE標記在掛在bio時進行判斷并設置*/if (!test_bit(R5_OVERWRITE, &dev->flags))s->non_overwrite++;}/* 正常情況下成員磁盤狀態正常 */clear_bit(R5_Insync, &dev->flags);if (test_bit(In_sync, &rdev->flags))set_bit(R5_Insync, &dev->flags); } rcu_read_unlock();}static void handle_stripe_dirtying(struct r5conf *conf,struct stripe_head *sh,struct stripe_head_state *s,int disks){ /** 以下幾種情況不能進行讀改寫只能使用重構寫* 1.RAID級別為6,因為讀改寫利用的是異或運算的特性,因此RAID6不適用* 2.IO起始范圍超過了同步的進度 。因為讀改寫需要用舊的數據和舊的校驗,*如果舊數據見不是一致的,那么再進行異或運算數據也是不一致的,*/ if (conf->max_degraded == 2 ||(recovery_cp < MaxSector && sh->sector >= recovery_cp)) {/* Calculate the real rcw later - for now make it* look like rcw is cheaper*/rcw = 1; rmw = 2;pr_debug("force RCW max_degraded=%u, recovery_cp=%llu sh->sector=%llu\n",conf->max_degraded, (unsigned long long)recovery_cp,(unsigned long long)sh->sector); } else for (i = disks; i--; ) {/* 如果dev有寫請求或保存的是校驗值那么該dev在讀改寫邏輯中是需要讀的 */struct r5dev *dev = &sh->dev[i];if ((dev->towrite || i == sh->pd_idx) &&!test_bit(R5_LOCKED, &dev->flags) &&!(test_bit(R5_UPTODATE, &dev->flags) ||test_bit(R5_Wantcompute, &dev->flags))) {rmw++;}/* 如果dev非滿寫(為寫滿4K或無IO)并且不是校驗那么該dev在重構寫邏輯中是需要讀的 */if (!test_bit(R5_OVERWRITE, &dev->flags) && i != sh->pd_idx &&!test_bit(R5_LOCKED, &dev->flags) &&!(test_bit(R5_UPTODATE, &dev->flags) ||test_bit(R5_Wantcompute, &dev->flags))) {rcw++;} } /* 進行讀改寫需要讀的次數少 */ if (rmw < rcw && rmw > 0) {/* 遍歷所有條帶/設備 */for (i = disks; i--; ) {struct r5dev *dev = &sh->dev[i];/** 以下幾種情況該dev下發讀請求* 1.有寫請求或保存的是校驗值* 2.并且dev沒有下發請求(R5_LOCKED)* 3.并且dev中的數據不是磁盤中實際保存的數據(R5_UPTODATE)*并且沒有在進行計算(R5_Wantcompute)* 4.并且dev對應成員磁盤狀態正常(R5_Insync)*/if ((dev->towrite || i == sh->pd_idx) &&!test_bit(R5_LOCKED, &dev->flags) &&!(test_bit(R5_UPTODATE, &dev->flags) ||test_bit(R5_Wantcompute, &dev->flags)) &&test_bit(R5_Insync, &dev->flags)) {/* 給條帶/設備上鎖表明正在進行IO */set_bit(R5_LOCKED, &dev->flags);/* 表明該條帶/設備要調度讀請求 */set_bit(R5_Wantread, &dev->flags);/* locked增加計數 */s->locked++;}} } /* locked不等于0本輪不調度schedule_reconstruction */ if ((s->req_compute || !test_bit(STRIPE_COMPUTE_RUN, &sh->state)) &&(s->locked == 0 && (rcw == 0 || rmw == 0) &&!test_bit(STRIPE_BIT_DELAY, &sh->state)))schedule_reconstruction(sh, s, rcw == 0, 0);}static void ops_run_io(struct stripe_head *sh, struct stripe_head_state *s){ /* 遍歷所有條帶/設備 */ for (i = disks; i--; ) {/* 對設置了讀標記的下發讀請求 */if (test_and_clear_bit(R5_Wantread, &sh->dev[i].flags))rw = READ;/* 跳過其他不需要讀的設備 */elsecontinue;if (rdev) {bio_reset(bi);bi->bi_bdev = rdev->bdev;bi->bi_rw = rw;bi->bi_end_io = raid5_end_read_request;bi->bi_private = sh;atomic_inc(&sh->count);if (use_new_offset(conf, sh))bi->bi_sector = (sh->sector + rdev->new_data_offset);elsebi->bi_sector = (sh->sector + rdev->data_offset);if (test_bit(R5_ReadNoMerge, &sh->dev[i].flags))bi->bi_rw |= REQ_FLUSH;bi->bi_vcnt = 1;bi->bi_io_vec[0].bv_len = STRIPE_SIZE;bi->bi_io_vec[0].bv_offset = 0;bi->bi_size = STRIPE_SIZE;/* 提交bio */generic_make_request(bi);} }}

推薦閱讀