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

linux内核奇遇记之md源代码解读之十一raid5d
转载请注明出处:http://blog.csdn.net/liumangxiong
正是有了上一篇的读写基础,我们才开始看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);
4661

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
  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值