块IO子系统整体有下图所示的架构,文件系统层向block层下发bio后,最终会转换为块设备驱动层的request执行,因此,要搞清楚设备是如何最终被操作的,就需要去弄清楚块设备驱动层的一些东西,咱们使用的Android虚拟机正好就使用了device mapper这一个块设备驱动,因此为了Android项目继续推进的需要,需要来学习一下这一部分的知识。
同Mult-Disk模块一样,Device Mapper模块也是一个虚拟块设备层,隶属于上图中的块设备驱动层,MD层和DM层可以相互嵌套,构成了“栈式”块设备层,本质原理就在于“重定向”请求方式。
DM将虚拟块设备的逻辑范围分段进行映射,将一定范围按照特定的规则映射到“低层设备”。因此,如果换作我们来实现这个结构,只要能给出一个映射表就可以了,实际上也是这么做的。映射表由一系列有序规则组成,每条规则相当于一条映射目标,具有如下形式:
<logical_start_sector><num_sectors><target_type>[<target_args>...]
逻辑空间开始扇区 逻辑空间段长度 映射类型 相关参数
它表明将新设备从<logical_start_sector> 映射<num_sectors>个扇区到类型为<target_type>的目标(实际目标往往由最后的参数去决定)上。映射表的规则顺序描述新设备的逻辑范围,相邻规则之间不能出现间隙。在第一条规则中,起始扇区为0,接下来的每一条规则,起始扇区均为上一条规则起始扇区+上一条规则扇区长度。这样保证了新设备的每一个扇区都能从表中找到对应的映射目标。
需要注意的是,真实存在的设备应该是target_type中定义的设备才对,某种意义上来说,logical设备才是真正的target。
Linux源码树中和Device Mapper相关的代码主要放在两个目录下:
头文件目录:
include/linux/
c文件目录:
drivers/md/
那目标类型究竟有哪些呢?
线性映射规则
线性映射规则简单的把一段线性映射范围映射到目标设备的一段长度相同的线性范围,很像在目标设备上分了一块出来做新的逻辑设备。
<logical_start_sector><num_sector>linear<destination_device><start_sector>
线性类型,后跟两个参数,目标设备位置和起点扇区(长度与num_sector)相同。destination_device是目标设备,即低层块设备的完整路径名,或“major:minor"格式的设备号。
条带映射规则
条带映射规则被用来在一个或几个低层块设备的基础上创建一个条带,条带这个概念类似于,将一块连续的空间划分为若干等长段,散开存放,所以,规则中需要指出划分为多少个等长段,每个段长度为多少(必须为2的倍数),每个段分别在哪,故写作:
<logical_start_sector><num_sectors>striped<num_stripes><chunk_size>[<destination_device1><start_sevtor1>]一共num_stripes对
例如:
0 128 striped 2 32 /dev/hda 0 /dev/hdb 0
将映射设备映射为一个条带共128扇区,分为4个chunk,每个chunk32扇区,
则映射设备的第一个chunk位于/dev/hda的第一个chunk
则映射设备的第二个chunk位于/dev/hdb的第一个chunk
则映射设备的第三个chunk位于/dev/hda的第二个chunk
则映射设备的第四个chunk位于/dev/hdb的第二个chunk
采用这种方式就能在物理设备上零散而在逻辑中连续。
快照源及快照映射规则
DM在存储中被广泛应用的一个原因是它支持为逻辑卷设备创建快照,这就关系到快照源映射规则和快照映射规则
快照源映射:
数据块编号对应不变地将映射设备映射到目标设备上,在映射设备上是0扇区,在目标设备就是0扇区,相当于为映射设备找了一个真实的“家”。故,只需要知道目标设备的位置就可以做这件事了:
<logical_start_sector><num_sectors>snapshot-origin<dev_path>
快照映射规则:
<logical_start_sector><num_sector>snapshot<origin_dev><COW-dev><p/n><chunk-size>
首先搜索一下什么是COW:
COW
COW被称为即写即拷快照技术或写时拷贝快照技术,当主机将数据第一次写入到存储某个位置时,首先将原有的位置的内容读取,写到COW数据空间,然后将新数据写入到存储设备中。而下次主机针对这一位置的写操作将不再执行写时拷贝。这种实现方式在首次写入时需要完成一个读操作(读取源位置的数据),两个写操作(写原位置和写快照预留空间)。如果写入频繁,那么这种方式将非常消耗I/O操作。因此可以推断,当某个LUN上的I/O多数以读操作为主,写操作较少时,可以采用COW快照技术。另外,如果一个应用易出现写入热点,即只针对某个有限范围内容的数据进行写操作,那么也可以采用COW快照技术,因为同一份数据的多次写操作只会出现一次写时拷贝。
所以COW是一种快照技术,那么上面的规则就可以解读为快照映射设备的从logic_start_sector开始的num_sectors个扇区是快照,低层快照源设备的路径由origin_dev指定,底层COW设备位置由COW-dev指定,创建快照时可以指定是否持续化,即是否将管理copy-on-write映射的元数据(而不只是更改的数据)也记录到低层COW设备,p表示持续persistent,n表示不持续,chunk_size指出数据输入/输出的单位长度。
除了以上的映射规则,Linux内核中还有其他的映射规则,例如我们熟悉的zero读操作返回0,写操作被忽略的归零映射规则,null只写不读的黑洞规则,将范围内的IO请求都映射为失败的致错映射规则,用于数据副本保存到多个低层目标的镜像映射规则等。
用户可以自行设计映射规则,这为我们支持zoned设备提供了一个很好的思路。