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批量写请求的序号。
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