MD在處理讀寫錯誤時是不一樣的。寫資料發生錯誤時處理較簡單,讀發生錯誤時會比較麻煩,它會把讀不出來的資料通過計算出來,然後在重新寫回磁碟上。首先先看看如何處理寫錯誤的。
1、寫資料時發生錯誤
如果寫發生錯誤,那麼回呼函數raid5_end_write_request()中bio的BIO_UPTODATE位無效,調用md_error函數將相應的rdev置為Faulty,清除掉In_sync標誌,degreded++。喚醒raid5守護進程,如果有spare盤,則進行recovery。這個過程以後再說。設定該條帶STRIPE_HANDLE位,繼續處理該條帶。
在handle_stripe5函數中,首先會統計失效盤的個數failed。對於raid5來說,它是允許一個盤失效的。如果failed>1的話,那麼陣列也就失效了。在handle_stripe5中會看到相應的處理-----即滿足if (failed > 1 && to_read+to_write+written),對條帶中的所有命令均返回失敗。
如果只有一個盤失效的話,並且該失效rdev上有非滿塊的寫請求,那麼必須讀出其他盤上的資料。為什麼這樣做呢?其實仔細想想就知道了。因為在寫資料時要計算校正盤的資料,要保證校正盤資料的正確性,對於失效盤上的非滿塊寫,我們必須知道它的緩衝區中原來的資料,然後再將一部分資料更新到緩衝區中。這樣才能保證在寫資料的過程中失效盤中緩衝區資料是正確的。而失效盤上的資料需要根據其它盤上的資料計算得到,故要先讀取其他盤上的資料。
如果不是非滿塊寫的話,那麼我們沒必要預讀出其他未失效盤上的資料。這時會走到判斷做rmw還是rcw的流程中。而對於有失效盤的情況,做rmw顯示是不切合實際的。因為做rmw首先要讀取有寫請求的盤上的資料,而失效盤上的資料又要通過預讀其他盤上的資料計算而來,所以這裡直接將rmw值置為2*disks。rcw值也為2*disks,這樣做的目的是選擇rcw做寫資料方式。
之後對於非滿塊寫會根據compute_block()計算出失效盤的資料,而滿塊寫則根據rcw方式讀出了相應的資料,這時資料都已經準備好了,滿足了
if (locked == 0 && (rcw == 0 ||rmw == 0) &&
!test_bit(STRIPE_BIT_DELAY, &sh->state))
判定式,然後compute_parity5計算校正盤資訊,將資料寫到磁碟上。這裡可能會有個疑問,失效盤資料怎麼寫上去啊?在handle_stripe的末尾,會有如下判斷
rcu_read_lock();
rdev = rcu_dereference(conf->disks[i].rdev);
if (rdev && test_bit(Faulty, &rdev->flags))
rdev = NULL;
if (rdev)
atomic_inc(&rdev->nr_pending);
rcu_read_unlock();
這個rcu鎖很重要,有興趣的人可以研究研究。這段代碼就會根據rdev的狀態設定rdev指標值,如果它是NULL的話就不會下發具體的命令到物理磁碟上。這樣寫資料發生錯誤的時候就處理完畢了,下面我們來看看讀發生錯誤的時候。
2、讀資料發生錯誤
和寫發生錯誤一樣,讀發生錯誤首先也會在raid5_end_read_request()函數中體現,只不過會重試命令。重試命令之前會做一些檢查,比如陣列已經處於降級狀態,那麼我們沒有重試該命令了,這時陣列已經壞了。又比如該裝置的發生太多的讀取錯誤,則也不做重試。如果不做重試,則會調用md_error。否則將rdev的狀態置為R5_ReadError,重新處理這個條帶。
在handle_stripe5函數中,如果是失效盤上有讀請求,依舊還是要通過讀出其他盤上的資料來計算出該失效盤上的資料。當其他盤資料都讀出來的時候,調用compute_block()計算失效盤上的資料,之後便滿足
if (failed == 1 && ! conf->mddev->ro &&
test_bit(R5_ReadError, &sh->dev[failed_num].flags)
&& !test_bit(R5_LOCKED, &sh->dev[failed_num].flags)
&& test_bit(R5_UPTODATE, &sh->dev[failed_num].flags)
)
判定式,將rdev狀態位R5_ReWrite置為有效,將失效盤資料重新寫回磁碟上。
如果rewrite成功,則將資料重新寫回磁碟成功,否則想處理寫請求失敗的情況下處理。