linux dm

最近想学习Linux IO子系统, 找了flashcache代码, 它通过内核提供的Device Mapper机制, 将一块SSD和一块普通磁盘虚拟为一个块设备, 其中SSD作为cache, 数据最终落地到普通磁盘. 这种混合存储的策略, 向上层应用(如mysql)屏蔽了底层的实现, 上层应用看到的只是一个挂载到虚拟块设备上的某种文件系统, 使用常见的文件系统接口即可读写数据, 一方面保持兼容, 一方面获得不错的性能. flashcache的代码只有几千行, 从commit log中可以看到版本迭代比较频繁, 也因此引入了较多我个人不关心的新特性. flashcache源码中作者写到借鉴了dm-cache的代码, 所以查了下资料, 竟是国人出品, sloc不足两千, 一晚上就可以看完, 正合胃口. dm-cache的使用可以参考flashcache文档, 原理见flashcache原理.

内存结构

dm-cache思路非常简单, 它把SSD作为cache, 将数据持久化到普通磁盘. 其中, SSD cache组织方式为set-associative map, 这和CPU cache的组织非常相像, 只是这里的key是cacheblock编号. cacheblock是dm-cache为了方便存取数据引入的单位, 粒度在磁盘block之上. 在通过dmsetup创建dm-cache块设备时时可以指定cacheblock的大小, 默认为8个连续的磁盘block组成一个cacheblock, 即4k字节. 上层的IO请求由Device Mapper框架切割为cacheblock大小(且对齐)的bio, 然后交由dm-cache处理. 也就是说, 不管是对SSD, 还是普通磁盘, dm-cache处理IO的单位都是cacheblock. 它在内存中的metadata为:

117/*Cache block metadata structure*/

118struct cacheblock {

119    spinlock_t lock;    /*Lock to protect operations on the bio list*/

120    sector_t block;     /*Sector number of the cached block*/

121    unsigned short state;  /*State of a block*/

122    unsigned long counter; /*Logical timestamp of the block’s last access*/

123    struct bio_list bios;  /*List of pending bios*/

124};

其中, block字段表示当前cacheblock的起始扇区编号. 既然SSD作为cache, 针对写请求必定会有writeback和writethrough等多种选择. writeback即数据先写到SSD, 然后由后台线程在合适的时间写回磁盘. writethrough指数据同时写入磁盘和SSD. (flashcache在这基础上又增加了writearound的方式, 意思是绕过SSD cache, 数据直接写入磁盘, 在处理读请求时更新到SSD.) 不管是writeback, 还是writethrough, 数据写入磁盘(或者由磁盘读取数据更新至cache)都不可能一蹴而就, 所以每个cacheblock必定会有一个状态(state字段). 另外, cache有淘汰的概念, dm-cache支持FIFO或LRU淘汰, 所以需要为每个cacheblock保存其最后访问时间(counter字段). 最后, 为了互斥同时请求同一个cacheblock, 每个cacheblock还对应一个spinlock. 被互斥的后发请求记录在bios链表中. 在当前cacheblock上的操作完成后, dm-cache将重新提交bios链表上的bio.

接下来看下dm-cache的总控结构体cache_c:

80/*

81* Cache context

82*/

83struct cache_c {

84    struct dm_dev *src_dev;        /*Source device*/

85    struct dm_dev *cache_dev;  /*Cache device*/

86    struct dm_kcopyd_client *kcp_client; /*Kcopyd client for writing back data*/

87

88    struct cacheblock *cache;  /*Hash table for cache blocks*/

89    sector_t size;          /*Cache size*/

90    unsigned int bits;     /*Cache size in bits*/

91    unsigned int assoc;        /*Cache associativity*/

92    unsigned int block_size;   /*Cache block size*/

93    unsigned int block_shift;  /*Cache block size in bits*/

94    unsigned int block_mask;   /*Cache block mask*/

95    unsigned int consecutive_shift;    /*Consecutive blocks size in bits*/

96    unsigned long counter;     /*Logical timestamp of last access*/

97    unsigned int write_policy; /*Cache write policy*/

98    sector_t dirty_blocks;      /*Number of dirty blocks*/

99

100    spinlock_t lock;        /*Lock to protect page allocation/deallocation*/

101    struct page_list *pages;   /*Pages for I/O*/

102    unsigned int nr_pages;     /*Number of pages*/

103    unsigned int nr_free_pages;    /*Number of free pages*/

104    wait_queue_head_t destroyq; /*Wait queue for I/O completion*/

105    atomic_t nr_jobs;       /*Number of I/O jobs*/

106    struct dm_io_client *io_client;   /*Client memory pool*/

107

108    /*Stats*/

109    unsigned long reads;       /*Number of reads*/

110    unsigned long writes;      /*Number of writes*/

111    unsigned long cache_hits;  /*Number of cache hits*/

112    unsigned long replace;     /*Number of cache replacements*/

113    unsigned long writeback;   /*Number of replaced dirty blocks*/

114    unsigned long dirty;       /*Number of submitted dirty blocks*/

115};

其中, src_dev和cache_dev分别为磁盘和SSD在DM框架的抽象. cache字段为连续的cacheblock数组, 元素个数即size字段. 其余字段顾名思义, 不再赘述.

初始化

dm-cache的初始化代码相对简单, DM框架获取dmsetup参数, 传递给cache_ctr(), dm-cache通过该函数构造一个cache_c对象, 保存在dm_target.private中. dm_target结构中另一重要字段为split_io, 这个字段表示DM框架分割bio的粒度, cache_ctr()函数指定其为cacheblock大小.

上层的读写请求在IO内核路径上表示为bio, 针对Device Mapper框架虚拟出来的块设备的bio请求, DM框架通过bio的block编号找到所属的dm_targets(一个bio的请求可能横跨多个dm_target), 逐个回调dm_target.type->map, 该字段为函数指针, 在dm-cache模块加载到内核时, 由该模块的初始化函数dm_cache_init()注册为cache_map(). 也就是说, 读写请求的入口都是cache_map().

请求处理

如上所述, 读写请求的入口都是cache_map(), 其实现如下:

1202/*

1203* Decide the mapping and perform necessary cache operations for a bio request.

1204*/

1205static int cache_map(struct dm_target *ti, struct bio *bio,

1206              union map_info *map_context)

1207{

1208    struct cache_c *dmc = (struct cache_c *) ti->private;

<