虚位以待(AD)
虚位以待(AD)
首页 > 操作系统 > LINUX > MD模块之处理读写过程分析-3

MD模块之处理读写过程分析-3
类别:LINUX   作者:码皇   来源:互联网   点击:

这节我们来分析神奇而又NB的handle_stripe,2 6 21版本的处理raid5和raid6分别用handle_stripe5和handle_stripe6这两个函数,我们这里主要说handle_stripe5,handle_stripe6和handle_stripe5其实差不多,只要你理

 

这节我们来分析神奇而又NB的handle_stripe,2.6.21版本的处理raid5和raid6分别用handle_stripe5和handle_stripe6这两个函数,我们这里主要说handle_stripe5,handle_stripe6和handle_stripe5其实差不多,只要你理解raid6的原理并理解了handle_stripe5,那么handle_stripe6自然便明白了。

前一节中已经把请求的bio插入到了合适的条带中,那么接下来就是要处理这个条带了。。这个任务就由handle_stripe函数来完成。这一节先讲讲它是如何处理正常的读写,其它部分会在以后涉及到。由于读比较简单,所以先说说读的情况。

对于读来说,在make_request函数中已把要请求的bio加入到了条带中某个设备的toread链表中。然后调用handle_stripe处理这个条带。步骤如下:(这里假设条带中设备缓冲区均为empty)

a、开始会统计这个条带中有多少个r5dev上有需要处理的读请求,找到一个,to_read++,还会统计缓冲区的状态以及失效盘的个数等。

b、这时,to_read值不为0,满足

view plaincopy to clipboardprint?

if (to_read || non_overwrite || (syncing && (uptodate < disks)) || expanding) { 

中条件,可见,进入这个判定条件还是蛮多的:读请求,非满块写,同步,扩容等,确实,这些操作是需要先读出数据。之后对于每个设备缓冲区,如果有读请求(dev->to_read)并且缓冲区状态为empty(!test_bit(R5_LOCKED, &dev->flags) && !test_bit(R5_UPTODATE, &dev->flags)),就会把缓冲区标志位置为want(set_bit(R5_LOCKED, &dev->flags);set_bit(R5_Wantread, &dev->flags);)表明要从底层读取数据。

 

c、在handle_stripe5的末尾,统计那些设备有读请求。如果有读请求的话,初始化r5dev中req,包括设置回调函数bi_end_io=raid5_end_read_request,req的起始扇区及长度等,最后使用generic_make_request下发这个req。

d、请求处理完毕,raid5_end_read_request被调用,如果读取数据成功的话,会把设备缓冲区的R5_UPTODATE置为有效,并清除掉R5_LOCKED位,表明缓冲区状态为clean。到现在为止,我们已经把数据从磁盘上读取到了条带的设备缓冲区中,但这仅仅读到了缓冲区中,并没有把数据填充到原始请求bio中。所以需要对这个条带在进行一次处理,设置条带STRIPE_HANDLE为有效,调用release_stripe把条带放到handle_list中在进行处理,并唤醒守护线程raid5d。

e、raid5d从handle_list中取出条带,再次调用handle_stripe对条带进行处理。这次我们发现缓冲区状态为R5_UPTODATE即(test_bit(R5_UPTODATE, &dev->flags) && dev->toread条件满足),调用copy_data函数将缓冲区的数据拷贝到bio相应的段中。如果bi_phys_segments等于0的话,那么说明这个bio已经处理完毕,可以返回给上层了,则加入到return _bi的链表中。

f、最后执行

while ((bi=return_bi)) {   

        int bytes = bi->bi_size;   

        return_bi = bi->bi_next;   

        bi->bi_next = NULL;   

        bi->bi_size = 0;   

        bi->bi_end_io(bi, bytes,   

                  test_bit(BIO_UPTODATE, &bi->bi_flags)   

                    ? 0 : -EIO);   

    }  

 

 

通知上层处理完毕。

 

对于写请求的情况,比较复杂,涉及到了延迟写,下面来一步一步分析:

a、与处理读请求一样,开始也统计条带中有多少r5dev要处理的写请求,如果有的话,to_write++,还会统计非满块写的数量,如果该r5dev是非满块写,则non_overwrite++;

b、如果条带中有个设备被标记为非满块写,即R5_OVERWRITE位有效,则需要先读出这块的数据(为什么要读出来下面再说)设备缓冲区为want,表明需要从磁盘读取数据。

c、接下来就判断使用何种写方式进行写操作。我们知道raid5的写数据的同时还是写新校验,这就决定要了写数据首先要限度数据。这里有两种写方式,说白了就是两种计算校验的方法,一种是rmw(read-modify-write),一种是rcw(reconstruct-write).第一种方式先读出有写请求的设备和校验盘上的数据,然后将这些读出来的数据和要写的数据做xor,求得新的校验值。第2种方式是要读出非满块写和没有写请求设备上的数据,这里我们就看到了为何要标记非满块写,由于使用rcw,那么仅仅读出来没有写请求上设备的数据是不够的。因为有非满块写的存在,还要把非满块写的设备上数据读出来,然后把copy其中一部分数据,这样这个非满块写设备上的数据才可以用来计算新校验。rcw就用这个新构造的数据和读出来的数据以及要写的数据(除去非满块写)来计算新校验。

d、以rmw为例,我们要先读出有写请求和校验盘上的数据(如果缓冲区状态为empty),这时我们可以看到有个对条带状态的判断,即test_bit(STRIPE_PREREAD_ACTIVE, &sh->state) 。如果STRIPE_PREREAD_ACTIVE有效,则表明预读激活了,这时便可以发送读请求。如果无效的话,则set_bit(STRIPE_DELAYED, &sh->state);表明要延迟处理这个条带。我们这里以延迟处理条带来继续分析。安我的理解,延迟处理一个条带,其本质上就是处理条带时尽可能地少读取数据。

e、条带被置为STRIPE_DELAYED,这一轮handle_stripe结束,在release_stripe函数中,会把这个条带加入到delayed_list中,然后激活块设备驱动blk_plug_device(conf->mddev->queue);当定时器到期时(默认3ms),内核通过一些类函数调用最终会调用md对列的q->unplug_fn方法,该方法有raid5_unplug_device实现,该方法会调用raid5_activate_delayed函数,从delayed_list中取出条带,去掉STRIPE_DELAYED标记,设置STRIPE_PREREAD_ACTIVE有效,并将条带加入到handle_list中,唤醒raid5d线程,处理该条带。

f、handle_stripe函数再被调用,这时发现STRIPE_PREREAD_ACTIVE位有效,则将设备缓冲区状态为want,下发读请求到下层。读请求处理完毕,缓冲区状态为clean,然后把条带加入到handle_list中继续进行处理。

g、handle_stripe函数再一次被调用,这时要读的数据已经都读出来,那么便调用compute_parity5函数来计算新校验值,此时缓冲区状态为dirty,表明要有新数据写到磁盘中,将to_writen链表置空,并连接到written链表。设置set_bit(R5_Wantwrite, &sh->dev[i].flags),并且清楚掉STRIPE_PREREAD_ACTIVE,唤醒其他等待预读的条带。

i、raid5_end_write_request回调函数被调用,数据写成功,缓冲区状态为clean,此时数据已经写到磁盘上,但请求并没有处理结束,所以还要吧条带加入到handle_list中在进行处理。

j、handle_stripe有一次被调用,这事发现written链表不为空并且缓冲区为clean了,便可以将请求返回了。

 

综上所说,一次简单的读写命令就处理完了,过程还是有些复杂。上面所说的仅仅是一次普通的读写,我们知道raid5是可以允许一个盘失效的,如果有盘失效的话,读写处理回是什么样的呢?下一篇继续做分析。

相关热词搜索: MD模块