RAID5 IO处理之条带读代码详解( 四 )

2.2.3 拷贝数据到BIOstatic void ops_complete_compute(void *stripe_head_ref){ /* 调用mark_target_uptodate函数设置dev状态为uptodate */ mark_target_uptodate(sh, sh->ops.target); mark_target_uptodate(sh, sh->ops.target2); /* 计算工作完成清除STRIPE_COMPUTE_RUN标记 */ clear_bit(STRIPE_COMPUTE_RUN, &sh->state); /* 将条带推入状态机处理 */ set_bit(STRIPE_HANDLE, &sh->state); release_stripe(sh);}之后进入到 BIOFILL 流程与前一小节的流程一致,这里不再赘述 。
2.2.4 向上返回该流程与读成功流程一致,这里不再赘述 。
2.3 读IO报错这里所说的读IO报错指的是在下发IO之前RAID成员磁盘状态正常,但在执行IO过程中底层报告错误,进而将该成员磁盘标记为故障磁盘 。条带的第一轮下发读请求的处理过程与读成功章节相同,这里不再赘述 。在IO完成的回调函数 raid5_end_read_request() 中处理不同,流程如下 。
static void raid5_end_read_request(struct bio * bi, int error){ int uptodate = test_bit(BIO_UPTODATE, &bi->bi_flags); if (!uptodate) {/* 清除R5_UPTODATE标记表明dev IO异常 */clear_bit(R5_UPTODATE, &sh->dev[i].flags);/* 阵列第一次出现读错误的情况下设置retry标记进入重试 */if (retry)/** 如果设置了R5_ReadNoMerge标记表明是正在重试的对齐读,也说明异常的是IO本身* 所以此时设置R5_ReadError标记给dev并清除R5_ReadNoMerge标记*/if (test_bit(R5_ReadNoMerge, &sh->dev[i].flags)) {set_bit(R5_ReadError, &sh->dev[i].flags);clear_bit(R5_ReadNoMerge, &sh->dev[i].flags);} else/** 如果未设置R5_ReadNoMerge标记 , 此次异常可能由于进行了IO合并其他IO异常导致返回错误,* 所以设置该标记再次下发重试,如果成功则按照“读成功”流程继续执行* 如果重试失败则执行1817行的内容*/set_bit(R5_ReadNoMerge, &sh->dev[i].flags); }}static void analyse_stripe(struct stripe_head *sh, struct stripe_head_state *s){ rcu_read_lock(); for (i = disks; i--; ) {/* 未设置R5_UPTODATE标记所以不设置R5_Wantfill填充数据 */if (test_bit(R5_UPTODATE, &dev->flags) && dev->toread &&!test_bit(STRIPE_BIOFILL_RUN, &sh->state))set_bit(R5_Wantfill, &dev->flags);/* 统计读请求 */if (dev->toread)s->to_read++;/* 磁盘未设置为Faulty所以依然设置dev为R5_Insync状态 */if (test_bit(In_sync, &rdev->flags))set_bit(R5_Insync, &dev->flags);/* 因为设置了R5_ReadError故清除dev的R5_Insync标记 */if (test_bit(R5_ReadError, &dev->flags))clear_bit(R5_Insync, &dev->flags);/* 记录异常磁盘索引并统计异常dev */if (!test_bit(R5_Insync, &dev->flags)) {if (s->failed < 2)s->failed_num[s->failed] = i;s->failed++;if (rdev && !test_bit(Faulty, &rdev->flags))do_recovery = 1;} } rcu_read_unlock();}后续执行与 IO所在磁盘异常 流程相同的 读其他成员磁盘、计算校验、向上返回 流程 。
正常来讲,执行到这里上层已经获取到了想要的结果,我们可以结束流程了,但是成员磁盘还设置了R5_ReadError标记 , 我们是不是可以尝试进行修复呢?因为现在生产的硬盘都有这样的功能:在对硬盘的读/写过程中,如果发现一个坏扇区,则由内部管理程序自动分配一个备用扇区来替换该扇区 。这样一来,少量的坏扇区有可能在使用过程中被自动替换掉了,我们还可以继续使用 。基于这个想法 , 我们可以将计算好的数据再写回成员磁盘 , 我们不管他是否写到了一个替换过的扇区,只要能再成功读回该数据就可以了 。
2.4 阵列超冗余阵列超冗余时IO直接返回错误 , 处理流程如下图所示 。
函数调用关系如下:
handle_stripe() \_ analyse_stripe() \_ handle_failed_stripe()各函数执行的代码逻辑如下:
static void handle_stripe(struct stripe_head *sh){ /* 解析条带 */ analyse_stripe(sh, &s); /* 异常磁盘数超过RAID冗余 */ if (s.failed > conf->max_degraded) {sh->check_state = 0;sh->reconstruct_state = 0;/* s.to_read为真 */if (s.to_read+s.to_write+s.written)/* 调用handle_failed_stripe处理条带中的请求 */handle_failed_stripe(conf, sh, &s, disks, &s.return_bi); } /* s.to_read为真进入到handle_stripe_fill中 , 但是条带的toread设置为NULL* 因此在fetch_block中不会设置R5_Wantread标记*/ if (s.to_read || s.non_overwrite|| (conf->level == 6 && s.to_write && s.failed)|| (s.syncing && (s.uptodate + s.compute < disks))|| s.replacing|| s.expanding)handle_stripe_fill(sh, &s, disks); /* 无需要调度的请求 */ ops_run_io(sh, &s); /* 向上层返回 */ return_io(s.return_bi);}static void analyse_stripe(struct stripe_head *sh, struct stripe_head_state *s){ rcu_read_lock(); for (i = disks; i--; ) {/* 统计读请求 */if (dev->toread)s->to_read++;/* 磁盘异常rdev设置为NULL */if (rdev && test_bit(Faulty, &rdev->flags))rdev = NULL;/* 清除dev的R5_Insync标记 */clear_bit(R5_Insync, &dev->flags);if (!test_bit(R5_Insync, &dev->flags)) {if (s->failed < 2)s->failed_num[s->failed] = i;/* 统计异常计数 */s->failed++;if (rdev && !test_bit(Faulty, &rdev->flags))do_recovery = 1;} } rcu_read_unlock();}static voidhandle_failed_stripe(struct r5conf *conf, struct stripe_head *sh,struct stripe_head_state *s, int disks,struct bio **return_bi){ /* 遍历所有dev */ for (i = disks; i--; ) {spin_lock_irq(&sh->stripe_lock);/** R5_Wantfill标记表示读成功需要将数据从dev拷贝到bio* R5_Insync表示dev对应磁盘是正常的,R5_ReadError表示读错误* 因此这三个判断的是dev中没有也无法读取正确的数据* 没有正确的数据在超冗余的情况下直接返回所有的读请求*/if (!test_bit(R5_Wantfill, &sh->dev[i].flags) &&(!test_bit(R5_Insync, &sh->dev[i].flags) ||test_bit(R5_ReadError, &sh->dev[i].flags))) {spin_lock_irq(&sh->stripe_lock);bi = sh->dev[i].toread;/* 将条带的toread指针设置为NULL */sh->dev[i].toread = NULL;spin_unlock_irq(&sh->stripe_lock);if (test_and_clear_bit(R5_Overlap, &sh->dev[i].flags))wake_up(&conf->wait_for_overlap);while (bi && bi->bi_sector <sh->dev[i].sector + STRIPE_SECTORS) {struct bio *nextbi =r5_next_bio(bi, sh->dev[i].sector);clear_bit(BIO_UPTODATE, &bi->bi_flags);if (!raid5_dec_bi_active_stripes(bi)) {bi->bi_next = *return_bi;/* 将条带中的bio赋值给return_bi */*return_bi = bi;}bi = nextbi;}}clear_bit(R5_LOCKED, &sh->dev[i].flags); }}

推荐阅读