linux内核奇遇记之md源代码解读之十四raid5非条块内读

linux内核奇遇记之md源代码解读之十四raid5非条块内读
转载请注明出处:http://blog.csdn.net/liumangxiong
如果是非条块内读,那么就至少涉及到两个条块的读,这就需要分别从这两个条块内读出数据,然后再凑成整个结果返回给上层。接下来我们将看到如何将一个完整的bio读请求拆分成多个子请求下发到磁盘,从磁盘返回之后再重新组合成请求结果返回给上层的。
4097     logical_sector = bi->bi_sector & ~((sector_t)STRIPE_SECTORS-1);
4098     last_sector = bi->bi_sector + (bi->bi_size>>9);
4099     bi->bi_next = NULL;
4100     bi->bi_phys_segments = 1;     /* over-loaded to count active stripes */

首先计算请求起始位置,因为md下发到磁盘数据请求的最小单位为STRIPE_SECTORS,所以这里要将请求对齐。计算出请求起始位置为logical_sector ,结束位置为last_sector。4100行复用bi_phys_segments 用于计数下发条带数,这里防止意外释放先设置为1。
4102     for (;logical_sector < last_sector; logical_sector += STRIPE_SECTORS) {
4103          DEFINE_WAIT(w);
4104          int previous;
4105
4106     retry:
4107          previous = 0;
4108          prepare_to_wait(&conf->wait_for_overlap, &w, TASK_UNINTERRUPTIBLE);
...
4134
4135          new_sector = raid5_compute_sector(conf, logical_sector,
4136                                previous,
4137                                &dd_idx, NULL);
4138          pr_debug("raid456: make_request, sector %llu logical %llu\n",
4139               (unsigned long long)new_sector,
4140               (unsigned long long)logical_sector);
4141
4142          sh = get_active_stripe(conf, new_sector, previous,
4143                           (bi->bi_rw&RWA_MASK), 0);

在这个循环中将请求拆分个多个条带,分别下发命令。在处理条带的时候还需要做到互斥,不能有两个线程在同时操作同一个条带。比如说同步线程在同步这个条带,raid5d在写这个条带,那么就会产生非预期的结果。
4103行,等待队列用于条带访问互斥
4108行,加入等待队列
4135行,根据阵列逻辑扇区计算出磁盘物理偏移扇区,并计算对应的数据盘号和校验盘号
4142行,根据磁盘物理偏移扇区获取一个条带
4144          if (sh) {
....
4186               if (test_bit(STRIPE_EXPANDING, &sh->state) ||
4187                   !add_stripe_bio(sh, bi, dd_idx, rw)) {
4188                    /* Stripe is busy expanding or
4189                    * add failed due to overlap.  Flush everything
4190                    * and wait a while
4191                    */
4192                    md_wakeup_thread(mddev->thread);
4193                    release_stripe(sh);
4194                    schedule();
4195                    goto retry;
4196               }
4197               finish_wait(&conf->wait_for_overlap, &w);
4198               set_bit(STRIPE_HANDLE, &sh->state);
4199               clear_bit(STRIPE_DELAYED, &sh->state);
4200               if ((bi->bi_rw & REQ_SYNC) &&
4201                   !test_and_set_bit(STRIPE_PREREAD_ACTIVE, &sh->state))
4202                    atomic_inc(&conf->preread_active_stripes);
4203               release_stripe_plug(mddev, sh);
4204          } else {
4205               /* cannot get stripe for read-ahead, just give-up */
4206               clear_bit(BIO_UPTODATE, &bi->bi_flags);
4207               finish_wait(&conf->wait_for_overlap, &w);
4208               break;
4209          }
4210     }

在第一次看这段代码的时候,由于太匆忙完全没有找到重点在哪里。就像一个人在喧嚣的城市里长大,由于被城市的外表所迷惑完全不知道内心真正想追求的生活。当真正静下心来看的时候,终于发现最重要的一句在4187行,即add_stripe_bio函数,从此开始stripe不再孤单,因为有了bio的附体,它已经准备好要加入了条带处理流程,一场轰轰烈烈的条带人生路由此展开。
在4198行和4203行release_stripe_plug之后一个新的条带正式加入了处理队列(conf->handle_list)。
人的上半生在不断地找入口,下半生在不断地找出口。在这里,读stripe找到了入口,那么出口在哪里呢?读过LDD的同学一定知道答案,对于不使用默认请求队列的块设备驱动来说,对应的make_request函数为入口,出口就是bio_endio。接下来我们就一步步迈向这个出口。
release_stripe_plug之后首先进入的是handle_stripe,handle_stripe调用analyse_stripe,在这个函数中设置了to_read:
3245          if (test_bit(R5_Wantfill, &dev->flags))
3246               s->to_fill++;
3247          else if (dev->toread)
3248               s->to_read++;

回到handle_stripe函数中:
3472     if (s.to_read || s.non_overwrite
3473         || (conf->level == 6 && s.to_write && s.failed)
3474         || (s.syncing && (s.uptodate + s.compute < disks))
3475         || s.replacing
3476         || s.expanding)
3477          handle_stripe_fill(sh, &s, disks);

to_read触发了handle_stripe_fill,这个函数的作用就是设置需要读取的标志:
2696               set_bit(R5_LOCKED, &dev->flags);
2697               set_bit(R5_Wantread, &dev->flags);
2698               s->locked++;

接着又来到了ops_run_io,将读请求下发到磁盘。读请求的回调函数为raid5_end_read_request:
1745     if (uptodate) {
1746          set_bit(R5_UPTODATE, &sh->dev[i].flags);
...
1824     rdev_dec_pending(rdev, conf->mddev);
1825     clear_bit(R5_LOCKED, &sh->dev[i].flags);
1826     set_bit(STRIPE_HANDLE, &sh->state);
1827     release_stripe(sh);

这个函数做了两件事情,一是设置了R5_UPTODATE标志,另一是调用了release_stripe重新将条带送回了handle_stripe处理。
带着R5_UPTODATE标志进入了analyse_stripe函数:
3231          if (test_bit(R5_UPTODATE, &dev->flags) && dev->toread &&
3232              !test_bit(STRIPE_BIOFILL_RUN, &sh->state))
3233               set_bit(R5_Wantfill, &dev->flags);
3234
3235          /* now count some things */
3236          if (test_bit(R5_LOCKED, &dev->flags))
3237               s->locked++;
3238          if (test_bit(R5_UPTODATE, &dev->flags))
3239               s->uptodate++;
3240          if (test_bit(R5_Wantcompute, &dev->flags)) {
3241               s->compute++;
3242               BUG_ON(s->compute > 2);
3243          }
3244
3245          if (test_bit(R5_Wantfill, &dev->flags))
3246               s->to_fill++;

在3255行设置了R5_Wantfill标志,在3246行设置了to_fill,再次回来handle_stripe:
3426     if (s.to_fill && !test_bit(STRIPE_BIOFILL_RUN, &sh->state)) {
3427          set_bit(STRIPE_OP_BIOFILL, &s.ops_request);
3428          set_bit(STRIPE_BIOFILL_RUN, &sh->state);
3429     }

条带状态设置了STRIPE_OP_BIOFILL,只要设置了s.ops_request,就必须马上知道这个域对应的处理函数为raid_run_ops,实际操作在__raid_run_ops:
1378     if (test_bit(STRIPE_OP_BIOFILL, &ops_request)) {
1379          ops_run_biofill(sh);
1380          overlap_clear++;
1381     }

对应的处理函数是ops_run_biofill:
812static void ops_run_biofill(struct stripe_head *sh)
813{
814     struct dma_async_tx_descriptor *tx = NULL;
815     struct async_submit_ctl submit;
816     int i;
817
818     pr_debug("%s: stripe %llu\n", __func__,
819          (unsigned long long)sh->sector);
820
821     for (i = sh->disks; i--; ) {
822          struct r5dev *dev = &sh->dev[i];
823          if (test_bit(R5_Wantfill, &dev->flags)) {
824               struct bio *rbi;
825               spin_lock_irq(&sh->stripe_lock);
826               dev->read = rbi = dev->toread;
827               dev->toread = NULL;
828               spin_unlock_irq(&sh->stripe_lock);
829               while (rbi && rbi->bi_sector <
830                    dev->sector + STRIPE_SECTORS) {
831                    tx = async_copy_data(0, rbi, dev->page,
832                         dev->sector, tx);
833                    rbi = r5_next_bio(rbi, dev->sector);
834               }
835          }
836     }
837
838     atomic_inc(&sh->count);
839     init_async_submit(&submit, ASYNC_TX_ACK, tx, ops_complete_biofill, sh, NULL);
840     async_trigger_callback(&submit);
841}

终于见到庐山真面目了,不禁感慨一下代码就是这样裹着一层又一层,就好像神秘的生日礼物一样要拆开一层又一层的包装,又像老胡同巷子走过一道又一道才能找到那个卖酒的店子。但不管怎么样,代码都对你毫无保留的,真诚的。而且越是复杂的代码就越是风情万种、婀娜多姿,前提是你要懂得如何走入她的内心里才能体会得到,等真正体会到的时候你就会拍案叫绝,从而获得征服的快感久久不能忘怀。在征服了这样一个又一个风情万种的代码之后,你的追求就不再局限于肉体之上,转而追求精神上的高度,像欧洲建筑师一样去设计大教堂,然后花个600多年把哥特式的科隆大教堂建好,这才叫艺术。好吧,那个时候你我都已经不在了,但那种精神始终是你我要追求的境界。
823行,我们刚刚完成了对磁盘的读取,这下将读取的数据从缓存区中拷贝到dev->page上,而此时dev->toread也转移到了dev->read。这里先构造了dma的描述符,839和840将请求提交给DMA,在请求完成之后会回调到839传入的参数ops_complete_biofill:
769static void ops_complete_biofill(void *stripe_head_ref)
770{
771     struct stripe_head *sh = stripe_head_ref;
772     struct bio *return_bi = NULL;
773     int i;
774
775     pr_debug("%s: stripe %llu\n", __func__,
776          (unsigned long long)sh->sector);
777
778     /* clear completed biofills */
779     for (i = sh->disks; i--; ) {
780          struct r5dev *dev = &sh->dev[i];
781
782          /* acknowledge completion of a biofill operation */
783          /* and check if we need to reply to a read request,
784          * new R5_Wantfill requests are held off until
785          * !STRIPE_BIOFILL_RUN
786          */
787          if (test_and_clear_bit(R5_Wantfill, &dev->flags)) {
788               struct bio *rbi, *rbi2;
789
790               BUG_ON(!dev->read);
791               rbi = dev->read;
792               dev->read = NULL;
793               while (rbi && rbi->bi_sector <
794                    dev->sector + STRIPE_SECTORS) {
795                    rbi2 = r5_next_bio(rbi, dev->sector);
796                    if (!raid5_dec_bi_active_stripes(rbi)) {
797                         rbi->bi_next = return_bi;
798                         return_bi = rbi;
799                    }
800                    rbi = rbi2;
801               }
802          }
803     }
804     clear_bit(STRIPE_BIOFILL_RUN, &sh->state);
805
806     return_io(return_bi);
807
808     set_bit(STRIPE_HANDLE, &sh->state);
809     release_stripe(sh);
810}

如果你已经练就了一目十行的火眼睛睛的话,你一定看到了806行的return_io,没错,这就是我之前提到的出口了:
177static void return_io(struct bio *return_bi)
178{
179     struct bio *bi = return_bi;
180     while (bi) {
181
182          return_bi = bi->bi_next;
183          bi->bi_next = NULL;
184          bi->bi_size = 0;
185          bio_endio(bi, 0);
186          bi = return_bi;
187     }
188}

终于看到bio_endio了吧,happy吧去庆祝喝一杯吧。
狂欢够了吗?接下来有两个思考题:
1)return_bi为什么不是一个bio,而有bi_next?
2)既然return_io结束了,808/809行为什么又要重新加入到处理链表?
转载请注明出处:http://blog.csdn.net/liumangxiong
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: MDLinux内核中的一个模块,可实现磁盘阵列的软件级RAID,它和RAID0、RAID1、RAID4、RAID5、RAID6一样,就是一种磁盘阵列方案。 MD中最主要的部分是驱动程序,它运行在内核态中。它将多个磁盘设备组合在一起,成为一个逻辑设备,该逻辑设备对应着一个块设备文件。在这个逻辑设备上,可实现磁盘阵列的软件级RAID功能。 MD驱动程序的主要源代码是在/drivers/md目录下的md.c文件中,它包括了MD的全部源代码,还有一些其他相关文件,比如raid5.c等。 在这个文件中,最值得学习的是内核模块化编程思想。模块化编程是一种将代码划分为模块的软件设计方法,通过将代码划分为不同的模块,实现代码的解耦、可重用、可维护性等目标。 在MD.c中我们还可以看到内核中的锁、内存管理等基本的内核技术的应用。通过对MD.c进行源代码解读,能够深入了解Linux内核的实现原理,特别是MDRAID功能的实现,对于我们进一步学习Linux内核的相关知识和对其进行应用开发具有很大的帮助。 总之,通过对MD.c源代码解读,我们可以学习到Linux内核模块化编程思想、内存管理、锁机制等基本内核技术,进一步掌握Linux内核的实现原理,从而在Linux应用开发中更加熟练娴熟。 ### 回答2: MD(Multiple Devices)是一种常用的软件RAID方案,可以在Linux内核中实现,同时也是Linux内核中最基本的RAID模式之一。MD在实现中使用了驱动程序和用户空间工具,其中驱动程序包含在内核中,因此我们需要对MD源代码进行解读MD源代码是由C语言编写的,主要包含在drivers/md/目录下。在这个目录下,可以看到一些重要的文件和子目录,例如md.c、md.h、raid1.c、raid5.c等。这些文件和子目录定义了MD的基本结构和函数,如磁盘阵列的基本信息结构、磁盘块的操作函数等。 MD的实现思路比较清晰,可以简单地理解为将多个物理磁盘组合在一起,形成一个虚拟的块设备。在这个虚拟的块设备上,可以进行读写等操作,而具体的数据操作则由MD提供的不同RAID模式实现。例如,MD支持的RAID1模式就是将数据同步写入两个物理磁盘,以实现磁盘容错。而MD支持的RAID5模式则是将数据分散写入多个物理磁盘,通过奇偶校验等方式实现磁盘容错。 在MD源代码解读过程中,需要重点关注这些RAID模式的实现方式和相关函数。同时,还需要了解整个MD的插入和移除机制、数据恢复机制等,以便更好地理解和修改MD源代码。 总之,对于想要深入了解Linux内核RAID相关实现的开发者来说,对MD源代码进行解读是一个常有价值的学习和探索过程。 ### 回答3: mdlinux内核中的一个重要模块,支持多种存储设备,包括硬盘、闪存和网络存储等。如果想要深入了解linux内核的运行机制,就必须掌握md源代码。下面就对md源代码进行解读md源代码的核心是md.c文件。这个文件中定义了md模块的核心函数,包括md_init()、md_run()和md_stop()等。其中md_init()函数主要负责初始化md模块的各个子系统,包括raid核心、hotplugging、proc文件系统和sysfs文件系统等。md_run()函数则是md模块的主要循环,负责轮询设备状态并执行相应的IO操作。md_stop()函数则是md模块的关闭函数,用于释放模块占用的各种资源。 除了md.c文件外,md模块的代码还包括一些关键性质的文件,例如mddev.c、md.h和md_u.h等。其中,mddev.c文件定义了md设备的数据结构,包括磁盘阵列、线性设备和伪设备等。md.h和md_u.h文件则分别定义了用户空间和内核空间的md控制接口,包括创建和删除设备、添加和删除磁盘等。 在理解md源代码时需要注意的是,md模块涉及到多个子系统,包括块设备、文件系统和RAID等,因此需要对这些子系统的工作原理和相互关系有清晰的理解。同时,由于md模块的代码相当复杂,需要仔细地阅读和调试,才能完成内核的定制和优化工作。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值