正是有了上一篇的讀寫基礎,我們才開始看raid5d的代碼。raid5d不是讀寫的入口,也不是讀寫處理的地方,只是簡簡單單的中轉站或者叫做交通樞紐。這個樞紐具有制高點的作用,就像美國在新加坡的基地,直接就控制了太平洋和印度洋的交通樞紐。
4626 /* 4627 * This is our raid5 kernel thread. 4628 * 4629 * We scan the hash table for stripes which can be handled now. 4630 * During the scan, completed stripes are saved for us by the interrupt 4631 * handler, so that they will not have to wait for our next wakeup. 4632 */ 4633 static void raid5d(struct mddev *mddev) 4634 { 4635 struct r5conf *conf = mddev->private; 4636 int handled; 4637 struct blk_plug plug; 4638 4639 pr_debug("+++ raid5d active\n"); 4640 4641 md_check_recovery(mddev); 4642 4643 blk_start_plug(&plug); 4644 handled = 0; 4645 spin_lock_irq(&conf->device_lock); 4646 while (1) { 4647 struct bio *bio; 4648 int batch_size; 4649 4650 if ( 4651 !list_empty(&conf->bitmap_list)) { 4652 /* Now is a good time to flush some bitmap updates */ 4653 conf->seq_flush++; 4654 spin_unlock_irq(&conf->device_lock); 4655 bitmap_unplug(mddev->bitmap); 4656 spin_lock_irq(&conf->device_lock); 4657 conf->seq_write = conf->seq_flush; 4658 activate_bit_delay(conf); 4659 }
4641行,md_check_recovery這個函數前面看過了,用來檢查觸發同步
4643行,blk_start_plug和4688行blk_finish_plug是一對,用於合並請求。
4646行,這裡為什麼要來個大循環呢?剛開始看4629行注釋可能有點迷糊,可是看到這個循環就知道原來講的是這裡,4629行注釋說我們不必等到下次喚醒raid5線程,可以繼續處理stripes,因為可能有stripes已經在中斷處理函數裡處理完成返回了。
4651行,判斷陣列對應的bitmap_list是否為空,如果這個鏈表不為空則進入分支。bitmap跟條帶處理有什麼關系呢?這個問題就比較有歷史性了。對於raid5陣列來說,最可怕的事情莫過於在寫的過程中異常掉電,這就意味陣列不知道哪些數據是一致的,哪些是不一致的?這就是safemode干的事情,用來記錄陣列數據是否一致。然而數據不一致導致的代碼是全盤同步,這個是raid5最頭疼的問題。好了,現在有bitmap了可以解決這個問題啦,太happy啦。那bitmap是如何解決這個問題的呢?bitmap說你寫每個條帶的時候我都記錄一下,寫完成就清除一下。如果異常掉電就只要同步掉電時未寫完成的條帶就可以啦。娃哈哈太happy了!!!但是請別高興的太早,bitmap也不是一個好侍候的爺,bitmap必須要在寫條帶之前寫完成,這裡的寫完成就是要Write Through即同步寫。這下悲催了,bitmap的寫過程太慢了,完全拖垮了raid5的性能。於是有了這個的bitmap_list,raid5說,bitmap老弟你批量寫吧,有點類似bio的合並請求。但是這也只能部分彌補bitmap帶來的負面性能作用。
4655行,下發bitmap批量寫請求。
4657行,更新bitmap批量寫請求的序號。
4658行,將等待bitmap寫的條帶下發。
4660 raid5_activate_delayed(conf);
4660行,看函數名就是激活延遲條帶的意思。那麼為什麼要延遲條帶的處理呢?按照塊設備常用的手段,延遲處理是為了合並請求,這裡也是同樣的道理。那麼條帶什麼時候做延遲處理呢?我們跟進raid5_activate_delayed函數:
3691static void raid5_activate_delayed(struct r5conf *conf) 3692{ 3693 if (atomic_read(&conf->preread_active_stripes) < IO_THRESHOLD) { 3694 while (!list_empty(&conf->delayed_list)) { 3695 struct list_head *l = conf->delayed_list.next; 3696 struct stripe_head *sh; 3697 sh = list_entry(l, struct stripe_head, lru); 3698 list_del_init(l); 3699 clear_bit(STRIPE_DELAYED, &sh->state); 3700 if (!test_and_set_bit(STRIPE_PREREAD_ACTIVE, &sh->state)) 3701 atomic_inc(&conf->preread_active_stripes); 3702 list_add_tail(&sh->lru, &conf->hold_list); 3703 } 3704 } 3705}
3693行,這裡控制預讀數量。
3694行,遍歷陣列延遲處理鏈表
3695行,獲取陣列延遲處理鏈表表頭
3697行,獲取陣列延遲處理鏈表第一個條帶
3698行,從陣列延遲處理鏈表取出一個條帶
3700行,設置預讀標志
3702行,添加到預讀鏈表中
條帶在什麼情況下會加入陣列延遲處理鏈表呢?我們搜索conf->delayed_list,發現加入的時機是設置了STRIPE_DELAYED標志的條帶:
204 if (test_bit(STRIPE_DELAYED, &sh->state) && 205 !test_bit(STRIPE_PREREAD_ACTIVE, &sh->state)) 206 list_add_tail(&sh->lru, &conf->delayed_list);
在什麼情況下條帶會設置STRIPE_DELAYED標志呢?繼續搜索STRIPE_DELAYED標志,這裡只抽取了相關代碼部分:
2772static void handle_stripe_dirtying(struct r5conf *conf, 2773 struct stripe_head *sh, 2774 struct stripe_head_state *s, 2775 int disks) 2776{ ... 2808 set_bit(STRIPE_HANDLE, &sh->state); 2809 if (rmw < rcw && rmw > 0) ... 2825 } else { 2826 set_bit(STRIPE_DELAYED, &sh->state); 2827 set_bit(STRIPE_HANDLE, &sh->state); 2828 } 2829 } 2830 } 2831 if (rcw <= rmw && rcw > 0) { ... 2851 } else { 2852 set_bit(STRIPE_DELAYED, &sh->state); 2853 set_bit(STRIPE_HANDLE, &sh->state); 2854 }
這裡有兩種情況會設置STRIPE_DELAYED,rcw和rmw。不管是rcw還是rmw,都不是滿條帶寫,都需要去磁盤預讀,因此在效率上肯定比不上滿條帶寫。所以這裡需要延遲處理以合並請求。那麼合並請求的流程是怎麼樣的呢?我們這裡根據代碼流程簡要說明一下:
1)第一次非滿條帶寫過來之後,申請到一個struct stripe_head並加入陣列delayed_list延遲處理
2)第二次寫過來並命中前面條帶,並將bio加入到同一個struct stripe_head中
3)這時再下發請求就可以減少IO,如果湊到滿條帶就不需要下發讀請求了
當然條帶命中還有許多其他情況,只要能命中就能提高速度。
回到raid5d函數中來:
4662 while ((bio = remove_bio_from_retry(conf))) { 4663 int ok; 4664 spin_unlock_irq(&conf->device_lock); 4665 ok = retry_aligned_read(conf, bio); 4666 spin_lock_irq(&conf->device_lock); 4667 if (!ok) 4668 break; 4669 handled++; 4670 }
這裡處理陣列的另外一個鏈表,就是滿條塊讀重試鏈表。在raid5陣列中,如果剛好是滿條塊的IO請求,就可以直接下發到磁盤。但如果此時申請不到struct stripe_head就會加入到滿條塊讀重試鏈表中,等到struct stripe_head釋放的時候喚醒raid5d函數,再重新將滿條塊讀請求下發。
再接著往下看:
4672 batch_size = handle_active_stripes(conf); 4673 if (!batch_size) 4674 break;
handle_active_stripes函數就是我們處理條帶的主戰場,因為大部分條帶的處理都要經過這個函數,我們接著進來看這個函數:
4601#define MAX_STRIPE_BATCH 8 4602static int handle_active_stripes(struct r5conf *conf) 4603{ 4604 struct stripe_head *batch[MAX_STRIPE_BATCH], *sh; 4605 int i, batch_size = 0; 4606 4607 while (batch_size < MAX_STRIPE_BATCH && 4608 (sh = __get_priority_stripe(conf)) != NULL) 4609 batch[batch_size++] = sh; 4610 4611 if (batch_size == 0) 4612 return batch_size; 4613 spin_unlock_irq(&conf->device_lock); 4614 4615 for (i = 0; i < batch_size; i++) 4616 handle_stripe(batch[i]); 4617 4618 cond_resched(); 4619 4620 spin_lock_irq(&conf->device_lock); 4621 for (i = 0; i < batch_size; i++) 4622 __release_stripe(conf, batch[i]); 4623 return batch_size; 4624}
 
這個函數幾乎可以一覽無余。首先是一個大循環,獲取最大MAX_STRIPE_BATCH個條帶存放到batch數組,4615行挨個處理這個條帶數組,4618行調度一下,4621行條帶重新進入陣列鏈表,然後開始下一輪的處理。
我們進入__get_priority_stripe函數看看,究竟是如何選擇條帶的。
3966/* __get_priority_stripe - get the next stripe to process 3967 * 3968 * Full stripe writes are allowed to pass preread active stripes up until 3969 * the bypass_threshold is exceeded. In general the bypass_count 3970 * increments when the handle_list is handled before the hold_list; however, it 3971 * will not be incremented when STRIPE_IO_STARTED is sampled set signifying a 3972 * stripe with in flight i/o. The bypass_count will be reset when the 3973 * head of the hold_list has changed, i.e. the head was promoted to the 3974 * handle_list. 3975 */
每一個社會都有特權階段,每一個國家都有貴族,所以條帶跟條帶還是有不一樣的,從函數名我們一眼就看出優先選擇特權條帶,就跟電影《2012》一樣,只有被選上才可以上到諾亞方舟。我們雖然不能像古代帝皇那樣翻牌子,但我們仍然有優先選擇條帶處理的權力。
第一特權是handle_list鏈表,第二特權是hold_list鏈表。
3976static struct stripe_head *__get_priority_stripe(struct r5conf *conf) 3977{ 3978 struct stripe_head *sh; 3979 3980 pr_debug("%s: handle: %s hold: %s full_writes: %d bypass_count: %d\n", 3981 __func__, 3982 list_empty(&conf->handle_list) ? "empty" : "busy", 3983 list_empty(&conf->hold_list) ? "empty" : "busy", 3984 atomic_read(&conf->pending_full_writes), conf->bypass_count); 3985 3986 if (!list_empty(&conf->handle_list)) { 3987 sh = list_entry(conf->handle_list.next, typeof(*sh), lru); 3988 3989 if (list_empty(&conf->hold_list)) 3990 conf->bypass_count = 0; 3991 else if (!test_bit(STRIPE_IO_STARTED, &sh->state)) { 3992 if (conf->hold_list.next == conf->last_hold) 3993 conf->bypass_count++; 3994 else { 3995 conf->last_hold = conf->hold_list.next; 3996 conf->bypass_count -= conf->bypass_threshold; 3997 if (conf->bypass_count < 0) 3998 conf->bypass_count = 0; 3999 } 4000 } 4001 } else if (!list_empty(&conf->hold_list) && 4002 ((conf->bypass_threshold && 4003 conf->bypass_count > conf->bypass_threshold) || 4004 atomic_read(&conf->pending_full_writes) == 0)) { 4005 sh = list_entry(conf->hold_list.next, 4006 typeof(*sh), lru); 4007 conf->bypass_count -= conf->bypass_threshold; 4008 if (conf->bypass_count < 0) 4009 conf->bypass_count = 0; 4010 } else 4011 return NULL; 4012 4013 list_del_init(&sh->lru); 4014 atomic_inc(&sh->count); 4015 BUG_ON(atomic_read(&sh->count) != 1); 4016 return sh; 4017}
3986行,優先選擇handle_list鏈表。
3987行,取出一個條帶
3989行,判斷hold_list鏈表是否為空。這裡是特權階級的社會,為什麼要去視察下面老百姓是否有吃飽呢?因為linux內核深谙“水能載舟,也能覆舟”的道理,如果把下面老百姓逼得太緊難免會社會不安定,所以到關鍵時刻還是得開倉放糧。這裡統計handle_list連續下發的請求個數,如果達到一定數量則在空閒的時候下發hold_list鏈表的請求。
3991行,如果不是已經在下發請求
3992行,hold_list在這一段時間內未下發條帶
3993行,遞增bypass_count計數
3995行,reset last_hold,遞減bypass_count
4001行,hold_list非空,bypass_count超過上限或者有滿條帶寫
4005行,返回hold_list鏈表中條帶
4007行,更新bypass_count
這裡這麼多對bypass_count的處理,簡單小結一下bypass_count的作用:
1)從handle_list取條帶處理,遞增bypass_count
2)如果handle_list為空,則判斷bypass_count是否達到bypass_threshold,如果是則可以從hold_list取出一個條帶來處理,bypass_count減去bypass_threshold
bypass_count就是用來限制低效率preread的下發速度的,增加IO合並機會。
接著看raid5d函數:
4675 handled += batch_size; 4676 4677 if (mddev->flags & ~(1<<MD_CHANGE_PENDING)) { 4678 spin_unlock_irq(&conf->device_lock); 4679 md_check_recovery(mddev); 4680 spin_lock_irq(&conf->device_lock); 4681 } 4682 }
4675行,統計處理條帶數4677行,陣列有變化,則釋放設備鎖,進行同步檢查raid5d函數也就這樣了,每個條帶從申請到釋放至少要到raid5d走一趟,raid5d迎來一批新條帶,又會送走一批條帶,每個條帶都只是匆匆的過客。raid5d的介紹就到此,下一小節接著講raid5的讀寫流程。
出處:http://blog.csdn.net/liumangxiong