linux内核奇遇记之md源代码解读之十一raid5d

linux内核奇遇记之md源代码解读之十一raid5d
转载请注明出处:http://blog.csdn.net/liumangxiong
正是有了上一篇的读写基础,我们才开始看raid5d的代码。raid5d不是读写的入口,也不是读写处理的地方,只是简简单单的中转站或者叫做交通枢纽。这个枢纽具有制高点的作用,就像美国在新加坡的基地,直接就控制了 太平洋和印度洋的交通枢纽。
[cpp]  view plain  copy
  1. 4626 /* 
  2. 4627  * This is our raid5 kernel thread. 
  3. 4628  * 
  4. 4629  * We scan the hash table for stripes which can be handled now. 
  5. 4630  * During the scan, completed stripes are saved for us by the interrupt 
  6. 4631  * handler, so that they will not have to wait for our next wakeup. 
  7. 4632  */  
  8. 4633 static void raid5d(struct mddev *mddev)  
  9. 4634 {  
  10. 4635         struct r5conf *conf = mddev->private;  
  11. 4636         int handled;  
  12. 4637         struct blk_plug plug;  
  13. 4638  
  14. 4639         pr_debug("+++ raid5d active\n");  
  15. 4640  
  16. 4641         md_check_recovery(mddev);  
  17. 4642  
  18. 4643         blk_start_plug(&plug);  
  19. 4644         handled = 0;  
  20. 4645         spin_lock_irq(&conf->device_lock);  
  21. 4646         while (1) {  
  22. 4647                 struct bio *bio;  
  23. 4648                 int batch_size;  
  24. 4649  
  25. 4650                 if (  
  26. 4651                     !list_empty(&conf->bitmap_list)) {  
  27. 4652                         /* Now is a good time to flush some bitmap updates */  
  28. 4653                         conf->seq_flush++;  
  29. 4654                         spin_unlock_irq(&conf->device_lock);  
  30. 4655                         bitmap_unplug(mddev->bitmap);  
  31. 4656                         spin_lock_irq(&conf->device_lock);  
  32. 4657                         conf->seq_write = conf->seq_flush;  
  33. 4658                         activate_bit_delay(conf);  
  34. 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写的条带下发。
[cpp]  view plain  copy
  1. 4660                 raid5_activate_delayed(conf);  
  2. 4661  

4660行,看函数名就是激活延迟条带的意思。那么为什么要延迟条带的处理呢?按照块设备常用的手段,延迟处理是为了合并请求,这里也是同样的道理。那么条带什么时候做延迟处理呢?我们跟进raid5_activate_delayed函数:
[cpp]  view plain  copy
  1. 3691static void raid5_activate_delayed(struct r5conf *conf)  
  2. 3692{  
  3. 3693     if (atomic_read(&conf->preread_active_stripes) < IO_THRESHOLD) {  
  4. 3694          while (!list_empty(&conf->delayed_list)) {  
  5. 3695               struct list_head *l = conf->delayed_list.next;  
  6. 3696               struct stripe_head *sh;  
  7. 3697               sh = list_entry(l, struct stripe_head, lru);  
  8. 3698               list_del_init(l);  
  9. 3699               clear_bit(STRIPE_DELAYED, &sh->state);  
  10. 3700               if (!test_and_set_bit(STRIPE_PREREAD_ACTIVE, &sh->state))  
  11. 3701                    atomic_inc(&conf->preread_active_stripes);  
  12. 3702               list_add_tail(&sh->lru, &conf->hold_list);  
  13. 3703          }  
  14. 3704     }  
  15. 3705}  

3693行,这里控制预读数量。
3694行,遍历阵列延迟处理链表
3695行,获取阵列延迟处理链表表头
3697行,获取阵列延迟处理链表第一个条带
3698行,从阵列延迟处理链表取出一个条带
3700行,设置预读标志
3702行,添加到预读链表中
条带在什么情况下会加入阵列延迟处理链表呢?我们搜索conf->delayed_list,发现加入的时机是设置了STRIPE_DELAYED标志的条带:
[cpp]  view plain  copy
  1. 204          if (test_bit(STRIPE_DELAYED, &sh->state) &&  
  2. 205              !test_bit(STRIPE_PREREAD_ACTIVE, &sh->state))  
  3. 206               list_add_tail(&sh->lru, &conf->delayed_list);  

在什么情况下条带会设置STRIPE_DELAYED标志呢?继续搜索STRIPE_DELAYED标志,这里只抽取了相关代码部分:
[cpp]  view plain  copy
  1. 2772static void handle_stripe_dirtying(struct r5conf *conf,  
  2. 2773                       struct stripe_head *sh,  
  3. 2774                       struct stripe_head_state *s,  
  4. 2775                       int disks)  
  5. 2776{  
  6. ...  
  7. 2808     set_bit(STRIPE_HANDLE, &sh->state);  
  8. 2809     if (rmw < rcw && rmw > 0)  
  9. ...  
  10. 2825                    } else {  
  11. 2826                         set_bit(STRIPE_DELAYED, &sh->state);  
  12. 2827                         set_bit(STRIPE_HANDLE, &sh->state);  
  13. 2828                    }  
  14. 2829               }  
  15. 2830          }  
  16. 2831     if (rcw <= rmw && rcw > 0) {  
  17. ...  
  18. 2851                    } else {  
  19. 2852                         set_bit(STRIPE_DELAYED, &sh->state);  
  20. 2853                         set_bit(STRIPE_HANDLE, &sh->state);  
  21. 2854                    }  

这里有两种情况会设置STRIPE_DELAYED,rcw和rmw。不管是rcw还是rmw,都不是满条带写,都需要去磁盘预读,因此在效率上肯定比不上满条带写。所以这里需要延迟处理以合并请求。那么合并请求的流程是怎么样的呢?我们这里根据代码流程简要说明一下:
1)第一次非满条带写过来之后,申请到一个struct stripe_head并加入阵列delayed_list延迟处理
2)第二次写过来并命中前面条带,并将bio加入到同一个struct stripe_head中
3)这时再下发请求就可以减少IO,如果凑到满条带就不需要下发读请求了
当然条带命中还有许多其他情况,只要能命中就能提高速度。
回到raid5d函数中来:
[cpp]  view plain  copy
  1. 4662                 while ((bio = remove_bio_from_retry(conf))) {  
  2. 4663                         int ok;  
  3. 4664                         spin_unlock_irq(&conf->device_lock);  
  4. 4665                         ok = retry_aligned_read(conf, bio);  
  5. 4666                         spin_lock_irq(&conf->device_lock);  
  6. 4667                         if (!ok)  
  7. 4668                                 break;  
  8. 4669                         handled++;  
  9. 4670                 }  

这里处理阵列的另外一个链表,就是满条块读重试链表。在raid5阵列中,如果刚好是满条块的IO请求,就可以直接下发到磁盘。但如果此时申请不到struct stripe_head就会加入到满条块读重试链表中,等到struct stripe_head释放的时候唤醒raid5d函数,再重新将满条块读请求下发。
再接着往下看:
[cpp]  view plain  copy
  1. 4672          batch_size = handle_active_stripes(conf);  
  2. 4673          if (!batch_size)  
  3. 4674               break;  

handle_active_stripes函数就是我们处理条带的主战场,因为大部分条带的处理都要经过这个函数,我们接着进来看这个函数:
[cpp]  view plain  copy
  1. 4601#define MAX_STRIPE_BATCH 8  
  2. 4602static int handle_active_stripes(struct r5conf *conf)  
  3. 4603{  
  4. 4604     struct stripe_head *batch[MAX_STRIPE_BATCH], *sh;  
  5. 4605     int i, batch_size = 0;  
  6. 4606  
  7. 4607     while (batch_size < MAX_STRIPE_BATCH &&  
  8. 4608               (sh = __get_priority_stripe(conf)) != NULL)  
  9. 4609          batch[batch_size++] = sh;  
  10. 4610  
  11. 4611     if (batch_size == 0)  
  12. 4612          return batch_size;  
  13. 4613     spin_unlock_irq(&conf->device_lock);  
  14. 4614  
  15. 4615     for (i = 0; i < batch_size; i++)  
  16. 4616          handle_stripe(batch[i]);  
  17. 4617  
  18. 4618     cond_resched();  
  19. 4619  
  20. 4620     spin_lock_irq(&conf->device_lock);  
  21. 4621     for (i = 0; i < batch_size; i++)  
  22. 4622          __release_stripe(conf, batch[i]);  
  23. 4623     return batch_size;  
  24. 4624}  

这个函数几乎可以一览无余。首先是一个大循环,获取最大MAX_STRIPE_BATCH个条带存放到batch数组,4615行挨个处理这个条带数组,4618行调度一下,4621行条带重新进入阵列链表,然后开始下一轮的处理。
我们进入__get_priority_stripe函数看看,究竟是如何选择条带的。
[cpp]  view plain  copy
  1. 3966/* __get_priority_stripe - get the next stripe to process 
  2. 3967 * 
  3. 3968 * Full stripe writes are allowed to pass preread active stripes up until 
  4. 3969 * the bypass_threshold is exceeded.  In general the bypass_count 
  5. 3970 * increments when the handle_list is handled before the hold_list; however, it 
  6. 3971 * will not be incremented when STRIPE_IO_STARTED is sampled set signifying a 
  7. 3972 * stripe with in flight i/o.  The bypass_count will be reset when the 
  8. 3973 * head of the hold_list has changed, i.e. the head was promoted to the 
  9. 3974 * handle_list. 
  10. 3975 */  

每一个社会都有特权阶段,每一个国家都有贵族,所以条带跟条带还是有不一样的,从函数名我们一眼就看出优先选择特权条带,就跟电影《2012》一样,只有被选上才可以上到诺亚方舟。我们虽然不能像古代帝皇那样翻牌子,但我们仍然有优先选择条带处理的权力。
第一特权是handle_list链表,第二特权是hold_list链表。
[cpp]  view plain  copy
  1. 3976static struct stripe_head *__get_priority_stripe(struct r5conf *conf)  
  2. 3977{  
  3. 3978     struct stripe_head *sh;  
  4. 3979  
  5. 3980     pr_debug("%s: handle: %s hold: %s full_writes: %d bypass_count: %d\n",  
  6. 3981            __func__,  
  7. 3982            list_empty(&conf->handle_list) ? "empty" : "busy",  
  8. 3983            list_empty(&conf->hold_list) ? "empty" : "busy",  
  9. 3984            atomic_read(&conf->pending_full_writes), conf->bypass_count);  
  10. 3985  
  11. 3986     if (!list_empty(&conf->handle_list)) {  
  12. 3987          sh = list_entry(conf->handle_list.next, typeof(*sh), lru);  
  13. 3988  
  14. 3989          if (list_empty(&conf->hold_list))  
  15. 3990               conf->bypass_count = 0;  
  16. 3991          else if (!test_bit(STRIPE_IO_STARTED, &sh->state)) {  
  17. 3992               if (conf->hold_list.next == conf->last_hold)  
  18. 3993                    conf->bypass_count++;  
  19. 3994               else {  
  20. 3995                    conf->last_hold = conf->hold_list.next;  
  21. 3996                    conf->bypass_count -= conf->bypass_threshold;  
  22. 3997                    if (conf->bypass_count < 0)  
  23. 3998                         conf->bypass_count = 0;  
  24. 3999               }  
  25. 4000          }  
  26. 4001     } else if (!list_empty(&conf->hold_list) &&  
  27. 4002             ((conf->bypass_threshold &&  
  28. 4003               conf->bypass_count > conf->bypass_threshold) ||  
  29. 4004              atomic_read(&conf->pending_full_writes) == 0)) {  
  30. 4005          sh = list_entry(conf->hold_list.next,  
  31. 4006                    typeof(*sh), lru);  
  32. 4007          conf->bypass_count -= conf->bypass_threshold;  
  33. 4008          if (conf->bypass_count < 0)  
  34. 4009               conf->bypass_count = 0;  
  35. 4010     } else  
  36. 4011          return NULL;  
  37. 4012  
  38. 4013     list_del_init(&sh->lru);  
  39. 4014     atomic_inc(&sh->count);  
  40. 4015     BUG_ON(atomic_read(&sh->count) != 1);  
  41. 4016     return sh;  
  42. 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函数:
[cpp]  view plain  copy
  1. 4675          handled += batch_size;  
  2. 4676  
  3. 4677          if (mddev->flags & ~(1<<MD_CHANGE_PENDING)) {  
  4. 4678               spin_unlock_irq(&conf->device_lock);  
  5. 4679               md_check_recovery(mddev);  
  6. 4680               spin_lock_irq(&conf->device_lock);  
  7. 4681          }  
  8. 4682     }  

4675行,统计处理条带数
4677行,阵列有变化,则释放设备锁,进行同步检查
raid5d函数也就这样了,每个条带从申请到释放至少要到raid5d走一趟,raid5d迎来一批新条带,又会送走一批条带,每个条带都只是匆匆的过客。
raid5d的介绍就到此,下一小节接着讲raid5的读写流程。
转载请注明出处:http://blog.csdn.net/liumangxiong
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值