这篇博文不足之处较多,重新整理了一下,链接:http://www.cnblogs.com/fangpei/p/3890873.html

  我阅读的代码的linux内核版本是2.6.32.61。刚进实验室什么都不懂,处于摸索阶段,近期的任务就是阅读raid1的源码。第一次接触raid相关的东西,网上分析源码的资料又比较少,不详细。逐行阅读代码,做了笔记。如果要对raid1的读流程有个整体上的把握,需要将笔记中的主线提炼出来,这里不写了。理解不足或者有误之处,希望批评指正。

  读流程主要涉及以下函数:

    请求函数make_request
    读均衡read_balance
    回调函数raid1_end_read_request
    读出错处理raid1d
    尝试修复读出错fix_read_error
  下面具体分析raid1的读流程。

请求函数make_request

  读请求封装成bio后,由md设备的md_make_request下发请求,md又发给具体的设备raid1,对应raid1的make_request函数,下面将从raid1的make_request开始理解该部分的流程。

  1.  如果访问要求设置barrier,而设备不支持设置barrier,则结束bio,立即返回。

  2.  等待设备上的barrier消除。

  注:当md收到一个barrier bio请求时,md会先把在该barrier bio之前到达的bio完成,然后再完成barrier bio,之后再处理在barrier bio之后到达的bio。barrier是通过conf->barrier来完成设置。

  这儿的barrier是上一个请求设置的barrier,也就是等待上一个请求处理“结束”,注意此时的“结束”不一定是上一个请求真的完全结束,而有可能设置了behind write模式,在写完除了非WriteMostly,则通知r1_bio的state为UpToDate(有效的,可以进行后面的请求处理)。

  同步一定会设置barrier,代码中有raise_barrier(conf);该障碍在同步结束后end_sync_write中调用put_buf操作conf->barrier来清除。写请求由用户请求本身决定是否设置barrier。

  3.  申请一个r1_bio结构(该结构主要用于管理raid1的bio),该结构中有一个数组bios数组指向对应各磁盘的bio。

  4.  调用read_balance算法,获取读目标的盘号rdisk。(具体分析见下一节)

  5.  如果获取的盘号为错误盘号(即-1),表示目前不能获取读目标,那么结束这个bio(调用raid_end_bio_io(r1_bio)),返回。

  6.  通过盘号rdisk和指针数组首地址conf->mirrors得到读目标的mirror_info结构指针mirror。

  7.  通过mirror->rdev->flags可以知道该磁盘是否设置了WriteMostly属性。如果设置了WriteMostly,则通过bitmap可以知道是否仍然还有延迟写。如果延迟写还没结束,那么等待延迟写结束。其他的情况都不处理,直接往下走。

  8.  将读目标盘号赋值给r1_bio->read_disk。

  9.  将接受到的上层bio(读请求)复制一份,指针read_bio和r1_bio->bios[rdisk]都指向该bio。

  10.设置该read_bio的相关属性,使得符合r1_bio结构的要求,比如读写属性,盘阵上哪个盘,盘的扇区,从而让它能正确下发到下层,最终找到相应的读目标盘;也设置了结束请求函数raid1_end_read_request。

  11.  下发read_bio。

读均衡read_balance

  这个算法是用来做读均衡的,在raid1中涉及到的读操作均会使用到该算法。入口参数为raid1的私有数据结构*conf和raid1管理bio的结构*r1_bio,具体流程分析如下:

  1.  如果盘阵正在进行同步操作,并且访问落在同步窗口中,按照下面的子流程选出盘号。则进入下面的步骤2来处理,否则进入步骤3。

  注:conf->mddev->recovery_cp < MaxSector,表示盘阵正在同步。代码中MaxSector的值为扇区数量的上限,而conf->mddev->recovery_cp表示正在进行同步的扇区号,所以自然就约定没有进行同步的时候conf->mddev->recovery_cp = MaxSector。

  扇区号加扇区数等于最后一个要读的扇区+1,如果大于等于下一个要同步的扇区,则说明落在同步窗口之中。

   1.1  从last_used的磁盘开始,循环遍历盘阵中每个盘(2个盘),选出第一个正确的盘。

    1.1.1  如果盘之前读失败(IO_BLOCKED)(在所有磁盘,都不能读的时候。由于之前为读写状态,所以可能因为写或者同步的原因造成的阻塞),或者盘不存在(之前为只读,但是还是读失败了),或者可能需要同步(In_sync=0),或者是WriteMostly盘,则继续循环。

    1.1.2  如果是WriteMostly盘,则记录下wonly为该盘号。

    1.1.3  如果所有盘遍历完(其实就是第2块盘)还是没有跳出循环,那么只好将WriteMostly盘的盘号选择出来(如果有的话),或者选出一个错误盘号(wonly的初始值-1)。跳出循环。

   1.2  如果选出的是一个错误盘号,那么直接出错返回。

   1.3  如果选出的盘号是正确的,那么继续下面的流程。

    (选出的盘号记录在new_disk中)

  2.  如果选出盘new_disk的磁头位置正好位于本次操作的起点,那么就直接确定为盘号new_disk。分别有如下两种情况: 

   2.1  一种是上次读均衡算法结束之后就更新了conf->next_seq_sect。

   2.2  一种是上次成功读写(包括同步读写)完成后,通过pdate_head_pos来更新了conf->mirrors[new_disk]. head_position。

  3.  如果选出盘new_disk的磁头和本次操作的起点不一样。disk作为循环控制的磁盘号,new_disk作为返回的磁盘号。

   3.1  首先将disk--,变成其他盘号,与上次读的盘号不同。(我觉得这里体现均衡)

   3.2  如果盘之前读失败(IO_BLOCKED)(在所有磁盘,都不能读的时候。由于之前为读写状态,所以可能因为写或者同步的原因造成的阻塞),或者盘不存在(之前为只读,但是还是读失败了),或者可能需要同步(In_sync=0),或者是WriteMostly盘,则继续循环。

   3.3  如果磁盘上无等待的操作,那么确定为该磁盘盘号,跳出循环。

   3.4  如果所有磁盘都有等待操作,那么找到当前磁头位置和本次操作位置最为接近的磁盘盘号。(通过current_distance 和new_distance来控制,current_distance是到目前为止最近的距离,new_distance是最新加入的一个最近距离)

  4.  如果选出盘rdev不存在,那么重新执行read_balance算法。

  5.  递增选出盘rdev的下发IO计数。

  6.  如果到了这一步发现该盘的rdev->flags的设置In_sync(该盘可能需要同步),递减选出盘rdev的下发IO计数,那么重新执行read_balance算法。

  7.  设置新的conf->next_seq_sect(下一个顺序读扇区),新的conf->last_used(最近一次使用的磁盘盘号)

  8.  返回选出的磁盘号,即为读均衡磁盘。

回调函数raid1_end_read_request

  1. 如果bio的状态为有效(BIO_UPTODATE),那么设置uptodate为1。
  2. 调用update_head_pos(mirror, r1_bio)更新当前盘的磁头位置。
  3. 如果bio的状态为有效,设置r1_bio的状态也为有效(R1BIO_Uptodate),并调用raid_end_bio_io结束这个bio。
  4. 如果bio的状态不是有效,并且所有盘都处于降级状态,或者只有一个盘处于正常状态但是该盘出错,那么设uptodate为1(这里不是表示状态为有效,而是表示不用retry了),然后调用raid_end_bio_io结束这个bio。
  5. 其他情况则表示读出错了。并将r1_bio放入retry队列,转由守护进程处理。通过reschedule_retry(r1_bio)调用wake_up(&conf->wait_barrier)。
  6. 递减选出盘rdev的下发IO计数。

读出错处理raid1d

  raid1d是守护进程,很多情况都会切换到该进程,而该进程描述了这些不同种情况下的处理流程。先处理激活同步守护进程,再提交pending_bio_list的所有请求,再一个一个的提交retry_list的r1_bio。判断r1_bio->state,如果是R1BIO_IsSync,则转入同步写流程;如果是R1BIO_BarrierRetry,这个情况只发生在写操作,则转入处理barrier的流程;如果是其他情况,则是读出错,进入读失败处理流程。下面分析读出错的处理流程。

  1. 如果md是读写状态。

    1.1  冻结盘阵。调用freeze_array(conf)实现。

      1.1.1  通过barrier计数挡住所有新的I/O。 

      1.1.2  把触发freeze_array的I/O(即失败的readI/O)加到wait计数。

      1.1.3  等待所有已经进入raid1层的I/O完成。

        这里等待的事件是“所有已进入raid1的readI/O挂入retry表,所有已进入raid1的writeI/O完成写动作。”

    1.2  尝试修复读出错。调用fix_read_error()实现。

    1.3  解冻盘阵。调用unfreeze_array(conf)实现。

  2.  如果md是只读状态,那么失效(md_error)读目标盘。

  3.  重新调用读均衡算法,得到一个盘号disk。

  4.  如果返回的盘号仍然为负值,那么表示无法恢复读出错,调用raid_end_bio_io结束这个bio。

  5.  如果返回盘号为正常值,那么根据是否只读,设置原读出错的盘指针为IO_BLOCKED或者NULL。

  6.  设置r1_bio->read_disk为新的读目标盘号disk。

  7.  Put掉原bio,bio_put(bio)。

  8.  将master_bio复制一份给bio,并用r1_bio->bios[r1_bio->read_disk指向该bio。

  9.  设置该bio,并将回调函数设置为raid1_end_read_request,下发该bio。

  10. cond_resched()如果有进程要抢占,则切换进程;如果不是,则继续循环。

尝试修复读出错fix_read_error

  1. 大循环,判断条件为sectors不为0,进入循环。
  2. 将需要处理的扇区数sectors复制给变量s,将读目标盘号read_disk赋值给d。
  3. 控制s的大小在 PAGE_SIZE>>9 以内。
  4. 从read_disk开始遍历盘阵中的每个盘。找到一个可以读这次page的盘,记录盘号为d,并读出来置于conf->tmppage中,跳出循环;或者没有可以读的盘,跳出循环。
  5. 如果没有可以读的盘,那么失效(md_error)原读目标盘,退出大循环,返回。
  6. 从步骤4中选出d号盘,往回遍历,直到read_disk。将conf->tmppage依次写入这些盘中。如果某次磁盘写入错误,那么失效(md_error)该次写入的磁盘。
  7. 从步骤4中选出d号盘,往回遍历,直到read_disk。将conf->tmppage依次从这些盘中读出。

    7.1  如果某次磁盘写入错误,那么失效(md_error)该次写入的磁盘。

    7.2  如果某次磁盘写入正确,那么该次磁磁盘对该大循环对应的page修复成功。

  8.  判断条件sectors = sectors – s,每次循环的sync_page_io起点标记sect = sect+1。进入下一个循环。

  raid1还有写流程和同步流程,以后再记录下来。努力让自己尽快入门,加油!

Raid1源代码分析--读流程的更多相关文章

  1. Raid1源代码分析--读流程(重新整理)

    五.Raid1读流程分析 两个月前,刚刚接触raid1,就阅读了raid1读流程的代码,那个时候写了一篇博客.现在回过头看看,那篇的错误很多,并且很多地方没有表述清楚.所以还是决定重新写一篇以更正之前 ...

  2. Raid1源代码分析--同步流程

    同步的大流程是先读,后写.所以是分两个阶段,sync_request完成第一个阶段,sync_request_write完成第二个阶段.第一个阶段由MD发起(md_do_sync),第二个阶段由守护进 ...

  3. Raid1源代码分析--写流程

    正确写流程的总体步骤是,raid1接收上层的写bio,申请一个r1_bio结构,将其中的所有bios[]指向该bio.假设盘阵中有N块盘.然后克隆N份上层的bio结构,并分别将每个bios[]指向克隆 ...

  4. Raid1源代码分析--初始化流程

    初始化流程代码量比较少,也比较简单.主要是run函数.(我阅读的代码的linux内核版本是2.6.32.61) 四.初始化流程分析 run函数顾名思义,很简单这就是在RAID1开始运行时调用,进行一些 ...

  5. Raid1源代码分析--开篇总述

    前段时间由于一些事情耽搁了,最近将raid1方面的各流程整理了一遍.网上和书上,能找到关于MD下的raid1的文档资料比较少.决定开始写一个系列的关于raid1的博客,之前写过的一篇读流程也会在之后加 ...

  6. Raid1源代码分析--Barrier机制

    本想就此结束Raid1的专题博客,但是觉得Raid1中自己构建的一套barrier机制的设计非常巧妙,值得单独拿出来分析.它保证了同步流程和正常读写流程的并发性,也为设备冻结/解冻(freeze/un ...

  7. Raid1源代码分析--一些补充

    Raid1的源码的读.写.同步,在本系列博客中都已经分析完成.除了barrier机制要专门拿出来分析(下一篇会写)以外,有一些问题值得思考和注意,分析如下. 1.freeze_array是如何做的? ...

  8. MD中bitmap源代码分析--清除流程

    bitmap的清零是由bitmap_daemon_work()来实现的.Raid1守护进程定期执行时调用md_check_recovery,然后md_check_recovery会调用bitmap_d ...

  9. MD中bitmap源代码分析--设置流程

    1. 同步/异步刷磁盘 Bitmap文件写磁盘分同步和异步两种: 1) 同步置位:当盘阵有写请求时,对应的bitmap文件相应bit被置位,bitmap内存页被设置了DIRTY标志.而在下发写请求给磁 ...

随机推荐

  1. i&1、负数二进制

    if(i&1==1) 表示 如果是 奇数 则...i&1 -- 按位与运算,取 2进制整数 i 的最低位,如果最低位是1 则得1,如果最低位是0 则得0. 奇数 i 的最低位 是1,偶 ...

  2. CSS 入门

    以下内容均来自 慕课网 CSS全称为"层叠样式表 (Cascading Style Sheets)",它主要是用于定义HTML内容在浏览器内的显示样式,如文字大小.颜色.字体加粗等 ...

  3. vue + vuex 表单处理

    使用场景:在一个表单中,各项数据提交后需要重置表单中各<input>元素的值为空. 组件中关联: <template> <el-form ref="form&q ...

  4. PHP设计模式笔记六:数据对象映射模式 -- Rango韩老师 http://www.imooc.com/learn/236

    数据对象映射模式 1.数据对象映射模式,是将对象和数据存储映射起来,对一个对象的操作会映射为对数据存储的操作 2.在代码中实现数据对象映射模式,我们将实现一个ORM类,将复杂的SQL语句映射成对象属性 ...

  5. 用户与 Oracle DB 交互具体过程

    与 Oracle DB 交互 以下的演示样例从最主要的层面描写叙述 Oracle DB 操作.该演示样例说明了一种 Oracle DB 配置,在该配置中,用户和关联server进程执行于通过网络连接的 ...

  6. 题目:[NOIP1999]拦截导弹(最长非递增子序列DP) O(n^2)和O(n*log(n))的两种做法

    题目:[NOIP1999]拦截导弹 问题编号:217 题目描述 某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统.但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发 ...

  7. 实现类似QQ的折叠效果

    //  主要核心是点击自定义header来展开和收起每一组里面的cell,模型里面应该有isShow此属性来记录开展还是收起. //  ViewController.m//  实现类似QQ的折叠效果/ ...

  8. Ghost克隆软件

    克隆软件Ghost初级使用教程 一.什么是Ghost ? Ghost(幽灵)软件是美国赛门铁克公司推出的一款出色的硬盘备份还原工具,可以实现FAT16.FAT32.NTFS.OS2等多种硬盘分区格式的 ...

  9. js版iphone通讯录分组列表效果

    <!DOCTYPE html> <html> <head> <meta http-equiv="content-type" content ...

  10. Hibernate学习笔记-Hibernate关系映射

    1. 初识Hibernate——关系映射 http://blog.csdn.net/laner0515/article/details/12905711 2. Hibernate 笔记8 关系映射1( ...