flashcache原理

介绍flashcache的文章很多,我就不废话了。使用上,有余峰老哥的文章;原理上,有ningoo同学的flashcache系列。但是ningoo同学漏掉了device mapper和flashcache的动态原理,只讲了静态的数据结构。我就钻个空子补充一下。

一般来说,我们对磁盘的read和write最后都会走到kernel里的submit_bio函数,也就是把io请求变成一个个的bio(bio的介绍看这里),bio是linux内核里文件系统层和block层之间沟通的数据结构(有点像sk_buffer之于网络),

bio
图片来自 http://lwn.net/Articles/26404/

到了block层以后,一般是先做generic_make_request把bio变成request,怎么个变法?如果几个bio要读写的区域是连续的,就攒成一个request(一个request下挂多个连续bio,就是通常说的“合并bio请求”);如果这个bio跟其它bio都连不上,那它自己就创建一个新的request,把自己挂到这个request下。合并bio请求也是有限度的,如果这些连续bio的访问区域加起来超过了一定的大小(在/sys/block/xxx/queue/max_sectors_kb里设置),那么就不能再合并成一个request了。

合并后的request会放入每个device对应的queue(一个机械硬盘即使有多个分区,也只有一个queue)里,之后,磁盘设备驱动程序通过调用peek_request从queue里取出request,进行下一步的处理。(bio和request的结构详细介绍可以看这里 123) 

之所以要把多个bio合并成一个request,是因为机械硬盘在顺序读写时吞吐最大。如果我们换成SSD盘,合并这事儿就没那么必要了,这一点是可选的,在实现设备驱动时,厂商可以选择从kernel的queue里取request,也可以选择自己实现queue的make_request_fn方法,直接拿文件系统层传过来的bio来进行处理(ramdisk、还有很多SSD设备的firmware就是这么做的)。

block
图片来自 http://blog.csdn.net/fudan_abc/article/details/2034264

我曾经弱弱的问过涛哥:既然bio有bio_vec结构指向多个page,那么为什么不干脆把多个bio合并成一个bio呢?何必要多一个request数据结构那么麻烦?
涛哥答曰:每个bio有自己的end_io回调,一个bio结束,就会做自己对应的收尾工作,如果你合并成一个bio了,就丧失了这种灵活性。


linux kernel有一个device mapper框架(以下简称dm框架),linux上的软RAID、multipath等都是通过此框架实现的。dm框架可以将多个设备聚合成一个虚拟设备提供给用户使用,其原理就是把这个虚拟设备的make_request_fn方法实现成了自己的dm_request,这样所有发往这个虚拟设备的bio都会走进dm_request,然后dm框架通过判断这个虚拟设备是基于request(request based)的还是基于bio(bio based)的来分别处理:

如果虚拟设备是request based,则和磁盘设备一样走generic_make_request把bio合并成request(如上),注意这些request最后放到的是虚拟设备的queue里,等到虚拟设备通过kblockd调用dm_request_fn时,dm_request_fn里会调用peek_request,从虚拟设备的queue里拿出request,将其clone(clone后的request里的bio指向的page是同一个page,只是分配了新的bio和新的request),然后调用map_request对request做映射(map_request里把map_rq接口暴露给了使用dm框架的开发者),最后把clone后的request发向底层的真实设备。

如果虚拟设备是bio based,就更简单了,调用_dm_request函数,一样要clone bio,然后调用__map_bio对bio做映射(__map_bio里把map暴露给了使用dm框架的开发者),最后把clone后的bio也是通过generic_make_request发向底层的真实设备,这次generic_make_request生成的request就是放在真实设备的queue里了,这是与request based的不同之处。
关于flashcache的原理,还可以参考尹洋同学的文章


flashcache是基于dm框架实现的,很自然的,是把一个SSD盘和一个机械硬盘聚合成一个虚拟设备供用户使用。
flashcache把cache(指SSD盘)分为多个set,每个set里有多个block(默认一个block是4KB,一个set包含512个block,即2MB),set里的block是用lru链表组织起来的,每个block还记录了自己存放的是disk的哪个sector起始的位置里对应的内容(这个起始的sector编号在flashcache的文档里被称为dbn)。
disk(这里指机械硬盘)也虚拟的分为多个set只是为了方便做hash。hash算法非常简单,先看访问的是disk的什么位置,相当于在disk的哪个set里,然后模上cache里的set数,结果就是在cache里对应的set编号了。找到cache对应的set后,继续在set的lru表里挨个儿block的比对dbn号,对上了就成功,对不上说明cache里没有缓存要读取的disk内容。
例如cache大小为10G,disk大小为100G,用户要读取磁盘上偏移54321MB处的2K内容,那么首先对54321MB这个位置做hash,2MB一个set,对应的set number是27160,cache的总set数为5120,那么 27160 mod 5120 结果为1560,也就是说应该去cache的第1560个cache去找,然后来到cache的1560 set里用 dbn 28479848448 遍历查找lru。

flashcache主要是实现了dm框架暴露出来的map接口(参考flashcache_map函数),收到bio后,先做hash,然后在cache(这里指SSD盘)里查找:

A. 如果是读bio
1 如果查找成功,直接将结果返回
2 如果查找失败,则找set内空闲的block(如果没有空闲的,则用最“旧”的block),直接读取disk里对应的内容返回给用户,返回给用户后设置延时任务将读取的内容放入这个空闲block里

B. 如果是写bio(我们仅列举writeback情况)
1 如果查找成功,拿到对应的block
2 如果查找失败,拿到对应set里最“旧”的block
3 直接将数据写入此block,返回给用户(用户的write系统调用就可以返回了),完成后将该block的状态设为DIRTY并设置延时任务,任务内容为将cache里的内容写往disk(这样既能让用户的写请求迅速完成,又能一定程度保证数据最终被写往disk)。延时任务完成后,便可以去掉block的DIRTY标记了

在set里查找空闲的block时,如果block都是DIRTY的,那么就只能等(这些DIRTY的block要保证写往disk的,所以不能乱动),等到有某个block被写往disk并清除DIRTY标记了,再拿这个block来用。因此,flashcache还会不时的将cache的set里长期不被访问的DIRTY的block写往disk,以保证有足够多的干净的block供以后使用。这个“不时的”不是靠定时器实现的,而是通过在flashcache_write_miss、flashcache_read_miss等函数里调用flashcache_clean_set来做到的。


相关文章

分类

2 Comments

linux-learner said:

well,对于Request-based dm devices cannot be stacked on top of bio-based dm devices 这句话不是特别理解,什么样的设备是request-base or bio-based的,为什么不能stack ?

DongHao Author Profile Page said:

所谓request,其实就是把连续的几个bio攒在一起,所以,通常那些合并io能提高效率的存储设备采用request-based,比如机械硬盘,连续的读写对它更有利;而那些不需要合并io,即对随机io支持很好的存储设备通常就采用bio-based,比如ramdisk和现在的固态硬盘。
至于为什么不能stack,我感觉也不是不能实现——把request拆成多个bio,发给不同的底层设备就行了,但是,既然底层的设备是bio-based的,也就是说它们能很好的支持随机io,上层设备就没必要把bio合并成request然后又拆开,这样太麻烦,白耗cpu

留言:

关于文章

This page contains a single entry by DongHao published on 10 22, 2012 11:21 AM.

CLSF 2012讨论会纪要(二) was the previous entry in this blog.

flashcache改造 is the next entry in this blog.

Find recent content on the main index or look in the 存档 to find all content.