這節我們來分析神奇而又NB的handle_stripe,2.6.21版本的處理raid5和raid6分別用handle_stripe5和handle_stripe6這兩個函數,我們這裡主要說handle_stripe5,handle_stripe6和handle_stripe5其實差不多,只要你理解raid6的原理並理解了handle_stripe5,那麼handle_stripe6自然便明白了。
前一節中已經把請求的bio插入到了合適的條帶中,那麼接下來就是要處理這個條帶了。。這個任務就由handle_stripe函數來完成。這一節先講講它是如何處理正常的讀寫,其它部分會在以後涉及到。由於讀比較簡單,所以先說說讀的情況。
對於讀來說,在make_request函數中已把要請求的bio加入到了條帶中某個裝置的toread鏈表中。然後調用handle_stripe處理這個條帶。步驟如下:(這裡假設條帶中裝置緩衝區均為empty)
a、開始會統計這個條帶中有多少個r5dev上有需要處理的讀請求,找到一個,to_read++,還會統計緩衝區的狀態以及失效盤的個數等。
b、這時,to_read值不為0,滿足
view plaincopy to clipboardprint?
if (to_read || non_overwrite || (syncing && (uptodate < disks)) || expanding) {
中條件,可見,進入這個判定條件還是蠻多的:讀請求,非滿塊寫,同步,擴容等,確實,這些操作是需要先讀出資料。之後對於每個裝置緩衝區,如果有讀請求(dev->to_read)並且緩衝區狀態為empty(!test_bit(R5_LOCKED, &dev->flags) && !test_bit(R5_UPTODATE, &dev->flags)),就會把緩衝區標誌位置為want(set_bit(R5_LOCKED, &dev->flags);set_bit(R5_Wantread, &dev->flags);)表明要從底層讀取資料。
c、在handle_stripe5的末尾,統計那些裝置有讀請求。如果有讀請求的話,初始化r5dev中req,包括設定回呼函數bi_end_io=raid5_end_read_request,req的起始扇區及長度等,最後使用generic_make_request下發這個req。
d、請求處理完畢,raid5_end_read_request被調用,如果讀取資料成功的話,會把裝置緩衝區的R5_UPTODATE置為有效,並清除掉R5_LOCKED位,表明緩衝區狀態為clean。到現在為止,我們已經把資料從磁碟上讀取到了條帶的裝置緩衝區中,但這僅僅讀到了緩衝區中,並沒有把資料填充到原始請求bio中。所以需要對這個條帶在進行一次處理,設定條帶STRIPE_HANDLE為有效,調用release_stripe把條帶放到handle_list中在進行處理,並喚醒守護線程raid5d。
e、raid5d從handle_list中取出條帶,再次調用handle_stripe對條帶進行處理。這次我們發現緩衝區狀態為R5_UPTODATE即(test_bit(R5_UPTODATE, &dev->flags) && dev->toread條件滿足),調用copy_data函數將緩衝區的資料拷貝到bio相應的段中。如果bi_phys_segments等於0的話,那麼說明這個bio已經處理完畢,可以返回給上層了,則加入到return _bi的鏈表中。
f、最後執行
while ((bi=return_bi)) {
int bytes = bi->bi_size;
return_bi = bi->bi_next;
bi->bi_next = NULL;
bi->bi_size = 0;
bi->bi_end_io(bi, bytes,
test_bit(BIO_UPTODATE, &bi->bi_flags)
? 0 : -EIO);
}
通知上層處理完畢。
對於寫請求的情況,比較複雜,涉及到了延遲寫,下面來一步一步分析:
a、與處理讀請求一樣,開始也統計條帶中有多少r5dev要處理的寫請求,如果有的話,to_write++,還會統計非滿塊寫的數量,如果該r5dev是非滿塊寫,則non_overwrite++;
b、如果條帶中有個裝置被標記為非滿塊寫,即R5_OVERWRITE位有效,則需要先讀出這塊的資料(為什麼要讀出來下面再說)裝置緩衝區為want,表明需要從磁碟讀取資料。
c、接下來就判斷使用何種寫方式進行寫操作。我們知道raid5的寫資料的同時還是寫新校正,這就決定要了寫資料首先要限度資料。這裡有兩種寫方式,說白了就是兩種計算校正的方法,一種是rmw(read-modify-write),一種是rcw(reconstruct-write).第一種方式先讀出有寫請求的裝置和校正盤上的資料,然後將這些讀出來的資料和要寫的資料做xor,求得新的校正值。第2種方式是要讀出非滿塊寫和沒有寫請求裝置上的資料,這裡我們就看到了為何要標記非滿塊寫,由於使用rcw,那麼僅僅讀出來沒有寫請求上裝置的資料是不夠的。因為有非滿塊寫的存在,還要把非滿塊寫的裝置上資料讀出來,然後把copy其中一部分資料,這樣這個非滿塊寫裝置上的資料才可以用來計算新校正。rcw就用這個新構造的資料和讀出來的資料以及要寫的資料(除去非滿塊寫)來計算新校正。
d、以rmw為例,我們要先讀出有寫請求和校正盤上的資料(如果緩衝區狀態為empty),這時我們可以看到有個對條帶狀態的判斷,即test_bit(STRIPE_PREREAD_ACTIVE, &sh->state) 。如果STRIPE_PREREAD_ACTIVE有效,則表明預讀啟用了,這時便可以發送讀請求。如果無效的話,則set_bit(STRIPE_DELAYED, &sh->state);表明要延遲處理這個條帶。我們這裡以延遲處理條帶來繼續分析。安我的理解,延遲處理一個條帶,其本質上就是處理條帶時儘可能地少讀取資料。
e、條帶被置為STRIPE_DELAYED,這一輪handle_stripe結束,在release_stripe函數中,會把這個條帶加入到delayed_list中,然後啟用區塊裝置驅動blk_plug_device(conf->mddev->queue);當定時器到期時(預設3ms),核心通過一些類函數調用最終會調用md對列的q->unplug_fn方法,該方法有raid5_unplug_device實現,該方法會調用raid5_activate_delayed函數,從delayed_list中取出條帶,去掉STRIPE_DELAYED標記,設定STRIPE_PREREAD_ACTIVE有效,並將條帶加入到handle_list中,喚醒raid5d線程,處理該條帶。
f、handle_stripe函數再被調用,這時發現STRIPE_PREREAD_ACTIVE位有效,則將裝置緩衝區狀態為want,下發讀請求到下層。讀請求處理完畢,緩衝區狀態為clean,然後把條帶加入到handle_list中繼續進行處理。
g、handle_stripe函數再一次被調用,這時要讀的資料已經都讀出來,那麼便調用compute_parity5函數來計算新校正值,此時緩衝區狀態為dirty,表明要有新資料寫到磁碟中,將to_writen鏈表置空,並串連到written鏈表。設定set_bit(R5_Wantwrite, &sh->dev[i].flags),並且清楚掉STRIPE_PREREAD_ACTIVE,喚醒其他等待預讀的條帶。
i、raid5_end_write_request回呼函數被調用,資料寫成功,緩衝區狀態為clean,此時資料已經寫到磁碟上,但請求並沒有處理結束,所以還要吧條帶加入到handle_list中在進行處理。
j、handle_stripe有一次被調用,這事發現written鏈表不為空白並且緩衝區為clean了,便可以將請求返回了。
綜上所說,一次簡單的讀寫命令就處理完了,過程還是有些複雜。上面所說的僅僅是一次普通的讀寫,我們知道raid5是可以允許一個盤失效的,如果有盤失效的話,讀寫處理回是什麼樣的呢?下一篇繼續做分析。