linux内核奇遇记之md源代码解读之十一raid5d
转载请注明出处:http://blog.csdn.net/liumangxiong
正是有了上一篇的读写基础,我们才开始看raid5d的代码。raid5d不是读写的入口,也不是读写处理的地方,只是简简单单的中转站或者叫做交通枢纽。这个枢纽具有制高点的作用,就像美国在新加坡的基地,直接就控制了太平洋和印度洋的交通枢纽。
[cpp] view plaincopy
  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 plaincopy
  1. 4660                 raid5_activate_delayed(conf);
  2. 4661

4660行,看函数名就是激活延迟条带的意思。那么为什么要延迟条带的处理呢?按照块设备常用的手段,延迟处理是为了合并请求,这里也是同样的道理。那么条带什么时候做延迟处理呢?我们跟进raid5_activate_delayed函数:

[cpp] view plaincopy
  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 plaincopy
  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 plaincopy
  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 plaincopy
  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 plaincopy
  1. 4672          batch_size = handle_active_stripes(conf);
  2. 4673          if (!batch_size)
  3. 4674               break;
handle_active_stripes函数就是我们处理条带的主战场,因为大部分条带的处理都要经过这个函数,我们接着进来看这个函数:
[cpp] view plaincopy
  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 plaincopy
  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 plaincopy
  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 plaincopy
  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

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

  1. linux内核奇遇记之md源代码解读之八阵列同步二

    linux内核奇遇记之md源代码解读之八阵列同步二 转载请注明出处:http://blog.csdn.net/liumangxiong 在上一小节里讲到启动同步线程: 7824 mddev->s ...

  2. linux内核奇遇记之md源代码解读之十二raid读写

    linux内核奇遇记之md源代码解读之十二raid读写 转载请注明出处:http://blog.csdn.net/liumangxiong 我们都知道,对一个linux块设备来说,都有一个对应的请求队 ...

  3. 复制linux内核,linux内核写时复制机制源代码解读

    作者简介 写时复制技术(一下简称COW)是linux内核比较重要的一种机制,我们都知道:父进程fork子进程的时候,子进程会和父进程会以只读的方式共享所有私有的可写页,当有一方将要写的时候会发生COW ...

  4. 高通linux内核目录,高通 android 源代码以及目标系统目录结构

    下面为高通android源代码结构 build/ – Build 环境建立和makefiles生成4 bionic/ – Android C 库 dalvik/ – Android Java 虚拟机 ...

  5. 《Linux内核设计与实现》读书笔记(十一)- 定时器和时间管理

    系统中有很多与时间相关的程序(比如定期执行的任务,某一时间执行的任务,推迟一段时间执行的任务),因此,时间的管理对于linux来说非常重要. 主要内容: 系统时间 定时器 定时器相关概念 定时器执行流 ...

  6. linux内核网络协议栈--kernel bridge转发逻辑(十一)

    1. netdev_rx_handler_register 在分析之前首先要介绍一个重要函数:netdev_rx_handler_register,这个函数是2.6内核所没有的. netdev_rx_ ...

  7. xilinx linux内核,Xilinx-Zynq Linux内核源码编译过程

    本文内容依据http://www.wiki.xilinx.com网址编写,编译所用操作系统为ubuntu 14 1.交叉编译环境的安装配置 2.uboot的编译 1)下载uboot源代码 下载uboo ...

  8. Linux内核调试方法【转】

    转自:http://www.cnblogs.com/shineshqw/articles/2359114.html kdb:只能在汇编代码级进行调试: 优点是不需要两台机器进行调试. gdb:在调试模 ...

  9. Linux内核移植之一:内核源码结构与Makefile分析

    内容来自 韦东山<嵌入式Linux应用开发完全手册> 一.内核介绍 1.版本及其特点 Linux内核的版本号可以从源代码的顶层目录下的Makefile中看到,比如下面几行它们构成了Linu ...

  10. 《Linux内核设计与实现》读书笔记(十九)- 可移植性

    linux内核的移植性非常好, 目前的内核也支持非常多的体系结构(有20多个). 但是刚开始时, linux也只支持 intel i386 架构, 从 v1.2版开始支持 Digital Alpha, ...

最新文章

  1. 吴恩达 coursera AI 专项四第四课总结+作业答案
  2. 自定义图框_Smart3D自定义图纸属性及其应用
  3. bzoj 1597 [Usaco2008 Mar]土地购买——斜率优化dp
  4. power bi 参数_参数化Power BI报表入门
  5. 人民日报发推欢迎Google重返大陆,FB上长文阐述详细立场
  6. 安装 tensorflow 环境
  7. 模拟小型电子商务网站绘制ER图
  8. 分子系统学-多序列比对和系统进化分析教程
  9. Graph Neural Network(GAE,GVAE,ARGA)
  10. EM算法及python实现
  11. 通信中带宽与数据传输速率的联系与区别
  12. CentOS的下载和安装
  13. 人工智能α-β树剪支图文
  14. 《University Calculus》-chape12-偏导数-基本概念
  15. Fluent最全complied错误原因分析:Error: The UDF library you are trying to load (libudf) is not compiled for
  16. 建筑安全检查标准怎么计算机,建筑施工安全检查标准评分 怎么算
  17. 蓝桥杯 模板Template Part9:PCF8591 ADC/DAC
  18. 项目一 认识Linux操作系统
  19. 趣节点:互联网信息大爆炸时代,企业品牌口碑营销需要注意什么?
  20. java反射和反编译

热门文章

  1. 超级强悍的PHP代码编辑器PHPstorm及配置
  2. hdu 3065 病毒侵袭持续中
  3. KVO.非常简单的键值监听模式
  4. mysql 密码保存格式_mysql5.6使用老格式密码
  5. 【Java并发编程学习】3、线程挂起、恢复和终止
  6. FireEye实验室在一次水坑式攻击中发现IE 0DAY
  7. AES算法,DES算法,RSA算法JAVA实现
  8. javascript DOM操作
  9. Docker-registry + GlusterFS
  10. java jvm学习笔记三(class文件检验器)