Chinaunix首页 | 论坛 | 博客
  • 博客访问: 92706
  • 博文数量: 26
  • 博客积分: 1400
  • 博客等级: 上尉
  • 技术积分: 345
  • 用 户 组: 普通用户
  • 注册时间: 2009-08-10 15:51
文章分类

全部博文(26)

文章存档

2011年(1)

2009年(25)

我的朋友

分类: LINUX

2009-09-02 17:27:07

访问请求

make_request

1. 如果该请求是限制在一个条带内的读请求的话,则调用chunk_aligned_read处理完以后返回,该bio的结束回调设置为raid5_align_endio

2. 根据请求的数据大小,对其中的每个stripe大小段执行下面工作(先暂不考虑expand):

a)  使用raid5_compute_sector计算出数据盘,效验盘,以及起始扇区。

b)        查找或者获取对应的stripe结构。

c)  调用add_stripe_biobio添加到stripe中。

                          i.       先判断bio是否和stripe中已有的bio存在重叠情况,否则设置本bio访问的磁盘对应的缓存R5_Overlap标志,返回。

                        ii.       bio挂入到strip的对应队列中(如果是读请求,挂到toread;如果是写请求,挂到towrite),增加bio的硬件段计数。(第一个strip其实应该不加1,这个多余加上的1在函数make_request的最后去掉了)

                       iii.       如果是strip的第一个写bio,则调用bitmap_startwrite设置内存bitmap。并设置条带的STRIPE_BIT_DELAY位,这个位的解释见后面说明。

                       iv.       对于写请求,还要判断这个条带下写请求是否覆盖了整个stripe的页面,如果覆盖,则设置条带的R5_OVERWRITE标志,这个标志表示,可以直接对磁盘写,而不需要将该数据块从磁盘读出,改写一部分再写回。

d)        handle_stripe处理这个stripe

e)  release_stripe释放这个stripe

3. 查看stripe的物理段,如果物理段为0,表示bio没有提交给任何的stripe,可以结束处理。(不太能解释什么情况下会这样)

raid5_align_endio

如果一个bio的读操作限制在一个条带内,则读操作完成后调用该函数。

将在chunk_aligned_read中申请的bio结构释放,如果读成功完成,则调用bio_endio将用户的bio结束。否则调用add_bio_to_retrybio添加到retry_read_aligned_list队列中。

raid5_end_write_request

RAID5向磁盘提交的写请求完成后,调用该函数。

如果写错误,则设置目标盘失败;清除缓存区的R5_LOCKED标志,设置条带的STRIPE_HANDLE标志,在release_stripe中用到这个标志对条带进行队列之间的转移。

raid5_end_read_request

向磁盘提交的读请求完成后,调用该函数。

如果读成功,则设置对应缓存区的R5_UPTODATE标志,清除R5_ReadErrorR5_ReWrite标志(在重读或重写时会设置这些标志)。

如果读失败,在磁阵降级,重写(用于纠正磁盘错误),或者读错误次数超过门限时,不进行读的重新尝试,设置磁盘错误;否则设置缓存区的R5_ReadError标志,进行重新尝试。

清除缓存区的R5_LOCKED标志,设置条带的STRIPE_HANDLE标志,在release_stripe中用到这个标志对条带进行队列之间的转移。

条带处理

RAID5的数据处理主流程是针对条带这个对象来开展的。条带中为每一个磁盘留了一个缓存。条带和这些缓存都有一系列的标志,这些标志决定了条带和缓存的状态,我们在后面进行说明。

需要注意的是:代码中定义的条带stripe和实际RAID设备的条带不是一回事!!strip的大小是一个PAGE,而RAID设备的条带的大小是chunk_sizeStripe的大小不是可以定制的。

init_stripe

条带的空间刚刚从内存中被分配时,调用该函数进行初始化。

主要的工作是将每个磁盘的缓存数据中,req是该缓存中要向磁盘提交的请求(读或者写),这个req中的bi_io_vec关联到vec(这个请求只有一个段),而段的页框和缓存中的page进行关联。

get_active_stripe

static struct stripe_head *get_active_stripe(raid5_conf_t *conf, sector_t sector, int disks,

                                        int pd_idx, int noblock)

根据条带在磁盘上的起始物理扇区,条带磁盘数,效验和磁盘下标,查找或者获取一个条带数据结构。Noblock标志表示是否调用者可以睡眠。

函数流程如下:

4. 获取设备锁

5. 磁阵可能被人工沉默(quiesce),即禁止其上进行业务操作,等待这个禁止放开。

6. hash桶中查找(hash桶的数组大小为一页,所以桶大小为HASH_MASK= (PAGE_SIZE / sizeof(struct hlist_head))-1hash算法更简单,stripe% HASH_MASK)。

7. 如果hash桶中找到条带,判断条带的引用计数是否为0,不为0的话,要保证其不在inactive或者handle队列中;如果为0,说明其必然在其中一个队列中,将其从该队列中取出,同时如果其没有在handle队列中,需要增加设备的active_stripes计数;转到6结束。

8. 如果hash桶中没有找到条带,则尝试从空闲链表inactive_list中获取一个条带结构,如果获取成功,则使用入参对条带进行初始化(hash中找到的是已经和入参对应的条带,而从空闲链表中获取的则不是),转到6结束;如果空闲链表是空的,调用者又不允许睡眠,则返回NULL,否则睡眠等待该链表中有可用的条带(这个睡眠唤起的条件比较复杂,大致是链表不为空,并且激活的条带不超过总条带的3/4;或者前面在此睡眠的进程已经被唤醒了),睡醒以后转到3

9. 如果找到条带,则增加该条带的引用计数;释放设备锁,返回找到的sh

release_stripe

每次handle_stripe5调用返回后,都会调用这个release_stripe函数,这个函数不仅仅是将条带释放这么简单,它还进行了一些重要标志的设置。

1.      对条带的引用技术递减,如果引用计数尚不为0,则返回

2.      如果条带的STRIPE_HANDLE标志被设置,说明条带已经准备好被处理了。此时:

a)       如果STRIPE_DELAYED标志被设置,说明条带虽然可以有被处理的需要,但还是要等待一段时间

b)       如果STRIPE_BIT_DELAY标志被设置,条带还要等待一段时间

c)       否则,将条带移入handle_list链表

d)       唤醒守护进程。

3.      如果STRIPE_HANDLE标志未设置,说明条带并未被处理的需要,此时:

a)       如果STRIPE_PREREAD_ACTIVE被设置,则清除,并且将设备的preread_active_stripes计数器减1,表示该条带已经没有预读的需要了。

b)       将设备的激活条带数active_stripes1

c)       如果条带未处于STRIPE_EXPANDING状态,则将条带添加到inactive_list链表中。该链表中的条带可以被重新利用。

retry_aligned_read

在守护进程中,使用这个函数来对失败的单条带读访问请求进行重试。

重试时,不再像chunk_aligned_read中使用用户bio的克隆进行提交,而是和make_request中的常规的bio一样,通过strip的方式来提交。如果获取strip失败或者调用add_stripe_bio发现和其他请求有重叠时,将该strip放入设备的retry_read_aligned指针中。

handle_stripe5

该函数是处理条带数据,代码写得很长(550行),好在逻辑相对清晰,主要是两部分功能:将条带已完成的读写处理结束;如果条带需要新发起读写请求,则向磁盘发出请求。

代码前一部分是做判断,判断是否有读写完成,是否有新的读写需要发出,这部分是在条带的自旋锁保护下进行的;代码后面一部分是具体的结束读写和发出读写请求。

如果有读写完成,前半部分会把完成的读写操作放在以return_bi为头的链表中;如果有读写需要提交,则设置条带的相应磁盘的缓存标志R5_WantwriteR5_Wantread

在开始代码分析前,还需要说明的是:在stripe的缓存中toreadtowrite中挂的是原始的bio,这些原始bio可能跨多个stripe。这些bio不会直接提交给磁盘,stripe提交的读写请求是另外自行构建的(条带缓存中的req域,在init_stripe 中,对req进行了初始化),仅仅是本stripe范围内的读写,前面说过,一个stripe上缓存大小只有一个页。

前面是大致的描述,下面看看具体的操作:

1.      获取条带的锁;清除条带的STRIPE_HANDLESTRIPE_DELAYED标志。

2.      判断是否有读完成——针对条带中的每个disk,如果disktoread不为空,并且已经UPTODATE,说明读已经完成:

a)       toread链表取出另外保存,将条带的toread清空。

b)       如果缓存上R5_Overlap标志被设置,说明有其他的请求由于和条带的请求重叠,而只好睡眠等待,此时对其唤醒。

c)       req的数据拷贝给toread中的bio。这段程序需要注意的是,由于toread中可能有多个bio,而且bio可能跨多个strip,只有在bio的所有物理段都完成读取后,这个bio才能算最后完成。

3.      遍历条带中所有的磁盘缓存,对toreadtowritewritenfail(读错误,磁盘未工作,磁盘未同步等)的各种情况进行统计,后面会根据这些信息进行处理。

4.      如果条带fail的数目大于1,表示该条带上的读写请求将无法继续,此时要终止这些读写请求,等待这些读写请求的睡眠队列也被唤醒。对于写请求,还要设置bitmap为需要同步。如果条带的fail数目大于1,此条带上的同步也将取消。

5.      到这里,最多只可能有1fail了,由于RAID5的属性,其上的数据读写操作总是可以继续的。

6.      这一步检查条带是否有写操作已经完成。写操作完成的标志是(有可能有多个数据盘还在写,但效验盘写成功,则认为可以返回已完成写操作的内容):

a)       有磁盘的written队列不为空,该队列是在上一次写操作中从towrite队列移到written队列中的(compute_parity5)。

b)       同时满足:效验盘数据工作正常(Insync)并且效验盘写已经结束(!LockedUptodate),或者条带中恰好是效验盘损坏了

对于满足写操作完成的磁盘,如果其上的bio已经完成全部物理段操作,则结束该bio的处理。这里还有一个处理,如果磁盘上的towrite队列为空,则调用bitmap_endwrite清除内存中位图(只是减写操作计数而已,具体的操作见bitmap相关的描述)。

7.      已完成的读写处理完之后,我们来看看是否有需要提交的读写请求。首先看读,在这些情况下需要向磁盘提交读请求:

a)       有用户提交的读请求

b)       用户写请求并未覆盖条带的整个页,也就是说要先读出,改写后再写回

c)       同步未完成,此时条带中并非所有的磁盘都处于Uptodate状态

d)       Expanding进行中,后面专题讨论

如果条带满足上述条件之一,则条带需要继续处理,设置条带的HANDLE标志。

针对需要提交读请求的盘,如果磁盘工作正常,则设置磁盘的wantread标志位,设置Locked位。

有一种情况,就是如果有一个坏盘,且这个坏盘恰好需要读取,而且条带中其他磁盘的数据已经更新,此时就可以将这个坏盘上的数据计算出来。

8.      针对写操作继续判断是否需要提交读请求,前面仅考虑了非覆盖写的情况。对于写操作,需要重新计算效验和,效验和的计算有两种形式:rmwrcwRmw是先将要写的数据盘数据D和效验盘数据P读出,根据新写入的数据D‘计算出新的效验数据P=P^D^D’。rcw是取出不需要写的数据盘的数据,加上要写入的数据盘数据,重新计算出效验和。

Rmwrcw这两种情况都需要读出数据,Rmw需要读的是效验盘和写数据盘;rcw需要读的是不需要写的数据盘。rmwrcw的选择,以需要的读操作为最少为原则。

选出需要读的磁盘后,和7一样,如果磁盘工作正常,则设置磁盘的wantread标志位,设置Locked位。和7稍有不同的是,如果此时条带还不在handle队列中,设置其DELAYED标志和HANDLE标志,继续等待。

而如果条带中有写,而且写操作要求的相关读操作此时已经完成的话,则此时可以计算出效验和,进行写操作的提交了,在compute_parity5中,将写目标磁盘和效验和磁盘的R5_LOCKED位都进行了设置。

9.      下面是对同步的[c1] ,如果磁阵完好,条带并未处于已同步态,此时其他磁盘的数据都已经读入了,将效验和计算后写效验盘;如果磁阵有故障盘,此时计算出故障盘的数据提交给故障盘,尝试恢复。

10.   如果条带发生一个读错误,则尝试修复这个读错误,具体的修复方法和RAID1是一样的,就是先写后读,要完成这个纠正需要对这个函数进行三次调用。第一次是发现读错误,在7中对其他好的磁盘提交读请求;第二次这些读已经完成,在7中计算出错误盘的数据,在10这里提交写请求;第三次是提交读请求。而这个读请求是在哪一步返回给用户的呢??,在7中调用compute_block时,在这个函数中会设置R5_UPTODATE标志,所以第三次调用时,会向用户返回读完成。

11.   这一步的expand处理我们后面再看。

12.   到这一步已经完成了是否有读写请求结束,以及是否有新的读写请求提交的判断。下面的工作就是根据这个判断结果进行实际的处理,将结束的读写请求提交给调用者;将新的读写请求提交给磁盘,前面说过,此时提交的并非用户的bio,而是条带中对应磁盘缓存的req。注意在提交过程中,条带的引用计数加1


 

阅读(2293) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~