linux内核奇遇记之md源代码解读之十一raid5d
转载请注明出处:http://blog.csdn.net/liumangxiong
正是有了上一篇的读写基础,我们才开始看raid5d的代码。raid5d不是读写的入口,也不是读写处理的地方,只是简简单单的中转站或者叫做交通枢纽。这个枢纽具有制高点的作用,就像美国在新加坡的基地,直接就控制了
太平洋和印度洋的交通枢纽。
- 4626
-
-
-
-
-
-
- 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
- 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函数看看,究竟是如何选择条带的。
每一个社会都有特权阶段,每一个国家都有贵族,所以条带跟条带还是有不一样的,从函数名我们一眼就看出优先选择特权条带,就跟电影《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的读写流程。