RAID5 IO处理之写请求代码详解

我们知道RAID5一个条带上的数据是由N个数据块和1个校验块组成,其校验块由N个数据块通过异或运算得出,这样才能在任意一个成员磁盘失效时通过其他N个成员磁盘恢复出用户写入的数据 。这也就要求RAID5条带上的数据是一致的、同步的 。
1 写入方式当新数据写入时就需要重新计算校验值,计算方式由以下两种:

  1. 将条带上没有写请求的位置的数据读出,然后使用新数据和旧数据两者重新计算校验
  2. 将条带上将要写数据的位置的数据和校验数据读出 , 然后试用新数据、旧数据和旧校验三者重新计算校验
【RAID5 IO处理之写请求代码详解】我们以5块盘创建的RAID5为例,其每个条带上共有4个数据块和1个校验块 。假设某个条带上的数据块依次为D1、D2、D3、D4 , 校验块为P1,在条带一致的情况下:P1 = D1 ⊕ D2 ⊕ D3 ⊕ D4 。现在新的数据D1'要覆盖原来的D1,两种写入方式的操作如下:
第一种方式:先读出D2、D3、D4,再加上要写入的D1'计算出P1',即:P1' = D2 ⊕ D3 ⊕ D4 ⊕ D1'第二种方式:先读出D1、P1 , 再加上要写入的D1'计算出P1',即:P1' = D1 ⊕ P1 ⊕ D1'
从具体的例子我们可以看出两种方式的本质区别在于要读出旧数据的个数不同,读的越少性能越好 。因此在处理逻辑中会先计算那种方式需要读出的数据更少,然后采用该种写方式 。第一种读其他旧数据直接计算新校验的写方式称为 重构写 ,第二种读旧数据和旧校验的方式称为 读改写。
2 读改写我们以5块盘chunk为4K的RAID5为例 。按照前文的分析,如果我们在RAID5的起始位置写入大小为4K的IO , 此时会采用读改写的方式下发请求 。代码分析如下:
2.1 接收请求函数调用关系:
raid5_make_request() \_ add_stripe_bio() \_ raid5_release_stripe() \_ md_wakeup_thread()接收到请求后,在 raid5_make_request() 按4K大小切分bio,通过 add_stripe_bio() 将bio挂在到条带上 , 推入条带状态机进行处理 。
2.2 下发读请求函数调用关系:
handle_stripe() \_ analyse_stripe() \_ handle_stripe_dirtying() \_ ops_run_io()代码解析:
static void handle_stripe(struct stripe_head *sh){ /* 解析条带状态 */ analyse_stripe(sh, &s); /* to_write条件成立设置需要读数据的条带/设备 */ if (s.to_write && !sh->reconstruct_state && !sh->check_state)handle_stripe_dirtying(conf, sh, &s, disks); /* 下发读请求 */ ops_run_io(sh, &s);}static void analyse_stripe(struct stripe_head *sh, struct stripe_head_state *s){ rcu_read_lock(); /* 遍历所有条带/设备 */ for (i = disks; i--; ) {dev = &sh->dev[i];/* 写请求挂到条带设备的towrite上此时为真 */if (dev->towrite) {s->to_write++;/** 条带头处理数据块的大小为4K,与内核page大小一致* 因此如果写的数据不足4K时需要先将原来的4K数据读出来再用新的数据覆盖* R5_OVERWRITE标记在挂在bio时进行判断并设置*/if (!test_bit(R5_OVERWRITE, &dev->flags))s->non_overwrite++;}/* 正常情况下成员磁盘状态正常 */clear_bit(R5_Insync, &dev->flags);if (test_bit(In_sync, &rdev->flags))set_bit(R5_Insync, &dev->flags); } rcu_read_unlock();}static void handle_stripe_dirtying(struct r5conf *conf,struct stripe_head *sh,struct stripe_head_state *s,int disks){ /** 以下几种情况不能进行读改写只能使用重构写* 1.RAID级别为6,因为读改写利用的是异或运算的特性,因此RAID6不适用* 2.IO起始范围超过了同步的进度 。因为读改写需要用旧的数据和旧的校验,*如果旧数据见不是一致的,那么再进行异或运算数据也是不一致的,*/ if (conf->max_degraded == 2 ||(recovery_cp < MaxSector && sh->sector >= recovery_cp)) {/* Calculate the real rcw later - for now make it* look like rcw is cheaper*/rcw = 1; rmw = 2;pr_debug("force RCW max_degraded=%u, recovery_cp=%llu sh->sector=%llu\n",conf->max_degraded, (unsigned long long)recovery_cp,(unsigned long long)sh->sector); } else for (i = disks; i--; ) {/* 如果dev有写请求或保存的是校验值那么该dev在读改写逻辑中是需要读的 */struct r5dev *dev = &sh->dev[i];if ((dev->towrite || i == sh->pd_idx) &&!test_bit(R5_LOCKED, &dev->flags) &&!(test_bit(R5_UPTODATE, &dev->flags) ||test_bit(R5_Wantcompute, &dev->flags))) {rmw++;}/* 如果dev非满写(为写满4K或无IO)并且不是校验那么该dev在重构写逻辑中是需要读的 */if (!test_bit(R5_OVERWRITE, &dev->flags) && i != sh->pd_idx &&!test_bit(R5_LOCKED, &dev->flags) &&!(test_bit(R5_UPTODATE, &dev->flags) ||test_bit(R5_Wantcompute, &dev->flags))) {rcw++;} } /* 进行读改写需要读的次数少 */ if (rmw < rcw && rmw > 0) {/* 遍历所有条带/设备 */for (i = disks; i--; ) {struct r5dev *dev = &sh->dev[i];/** 以下几种情况该dev下发读请求* 1.有写请求或保存的是校验值* 2.并且dev没有下发请求(R5_LOCKED)* 3.并且dev中的数据不是磁盘中实际保存的数据(R5_UPTODATE)*并且没有在进行计算(R5_Wantcompute)* 4.并且dev对应成员磁盘状态正常(R5_Insync)*/if ((dev->towrite || i == sh->pd_idx) &&!test_bit(R5_LOCKED, &dev->flags) &&!(test_bit(R5_UPTODATE, &dev->flags) ||test_bit(R5_Wantcompute, &dev->flags)) &&test_bit(R5_Insync, &dev->flags)) {/* 给条带/设备上锁表明正在进行IO */set_bit(R5_LOCKED, &dev->flags);/* 表明该条带/设备要调度读请求 */set_bit(R5_Wantread, &dev->flags);/* locked增加计数 */s->locked++;}} } /* locked不等于0本轮不调度schedule_reconstruction */ if ((s->req_compute || !test_bit(STRIPE_COMPUTE_RUN, &sh->state)) &&(s->locked == 0 && (rcw == 0 || rmw == 0) &&!test_bit(STRIPE_BIT_DELAY, &sh->state)))schedule_reconstruction(sh, s, rcw == 0, 0);}static void ops_run_io(struct stripe_head *sh, struct stripe_head_state *s){ /* 遍历所有条带/设备 */ for (i = disks; i--; ) {/* 对设置了读标记的下发读请求 */if (test_and_clear_bit(R5_Wantread, &sh->dev[i].flags))rw = READ;/* 跳过其他不需要读的设备 */elsecontinue;if (rdev) {bio_reset(bi);bi->bi_bdev = rdev->bdev;bi->bi_rw = rw;bi->bi_end_io = raid5_end_read_request;bi->bi_private = sh;atomic_inc(&sh->count);if (use_new_offset(conf, sh))bi->bi_sector = (sh->sector + rdev->new_data_offset);elsebi->bi_sector = (sh->sector + rdev->data_offset);if (test_bit(R5_ReadNoMerge, &sh->dev[i].flags))bi->bi_rw |= REQ_FLUSH;bi->bi_vcnt = 1;bi->bi_io_vec[0].bv_len = STRIPE_SIZE;bi->bi_io_vec[0].bv_offset = 0;bi->bi_size = STRIPE_SIZE;/* 提交bio */generic_make_request(bi);} }}

推荐阅读