Device Mapper 是 Linux2.6 内核中支持逻辑卷管理的通用设备映射机制,它为实现用于存储资源管理的块设备驱动提供了一个高度模块化的内核架构

模块化的 target drivers 插件实现对 I/O 请求的过滤或者重新定向等工作。 target drivers 插件包括软 raid、软加密、逻辑卷条带、多路径、镜像、快照等
Device mapper 用户空间相关部分主要负责配置具体的策略和控制逻辑,比如逻辑设备和哪些物理设备建立映射,怎么建立这些映射关系等等,而具体过滤和重定向 I/O 请求的工作由内核中相关代码完成。Device mapper以ioctl的方式向外提供接口,用户通过用户空间的device mapper库,向device mapper的字符设备发送ioctl命令,完成向内的通信。它还通过ioctl提供向往的事件通知机制,允许target driver将I/O相关的某些事件传送到用户空间
这篇文章对一些 device mapper 的资料进行整理,相关资料在参考部分
内核部分
相关代码在
driver/md/
中。后面图片中的代码对应的内核版本为4.4.68
相关结构
Device mapper包含3个重要的对象概念:
- mapped device:一个逻辑抽象,可以理解成内核对外提供的逻辑设备。它通过映射表描述的映射关系与target device建立映射
- 映射表:由一个多元组表示,表示 mapped device 逻辑的起始地址、范围、和表示在 target device 所在物理设备的地址偏移量以及target 类型等变量组成(这些地址和偏移量都是以磁盘的扇区为单位的,即 512 个字节大小)
- target device:表示的是 mapped device 所映射的物理空间段,对 mapped device 所表示的逻辑设备来说,就是该逻辑设备映射到的一个物理设备
Device mapper 中这三个对象和 target driver 插件一起构成了一个可迭代的设备树。在该树型结构中的顶层根节点是最终作为逻辑设备向外提供的 mapped device,叶子节点是 target device 所表示的底层物理设备。最小的设备树由单个 mapped device 和 target device 组成。一个 mapped device 可以映射到一个或者多个 target device 上,而一个 mapped device 又可以作为它上层 mapped device的 target device 被使用,该层次在理论上可以在 device mapper 架构下无限迭代下去

- 在内核中,mapped device由mapped_device结构表示
- 对应的映射表由dm_table结构表示。该结构中包含一个 dm_target结构数组,dm_target 结构具体描述了 mapped_device 到它某个 target device 的映射关系。在 dm_table 结构中将这些 dm_target 按照 B 树的方式组织起来方便 I/O 请求映射时的查找操作‘
- dm_target 结构具体记录该结构对应 target device 所映射的 mapped device 逻辑区域的开始地址和范围,同时还包含指向具体 target device 相关操作的 target_type 结构的指针。dm_target中的private域指向mapped device所映射的具体target device对应的结构,表示target device的具体结构因不同的target 类型而不同,比如最简单的线性映射target类型对应的target device结构是linear_c
- target_type 结构主要包含了 target device 对应的 target driver 插件的名字、定义的构建和删除该类型target device的方法、该类target device对应的I/O请求重映射和结束I/O的方法等

I/O流
Device mapper本质功能就是根据映射关系和target driver描述的I/O处理规则,将I/O请求从逻辑设备mapped device转发相应的target device上
Device mapper处理所有从generic_make_request和submit_bio接口中定向到mapped device的所有块读写I/O请求。I/O请求在device mapper的设备树中通过请求转发从上到下地进行处理

向下转发:当一个bio请求在设备树中的mapped deivce向下层转发时,一个或者多个bio的克隆被创建并发送给下层target device。然后相同的过程在设备树的每一个层次上重复,只要设备树足够大理论上这种转发过程可以无限进行下去
向上返回事件:在设备树上某个层次中,target driver结束某个bio请求后,将表示结束该bio请求的事件上报给它上层的mapped device,该过程在各个层次上进行直到该事件最终上传到根mapped device的为止,然后device mapper结束根mapped device上原始bio请求,结束整个I/O请求过程
Bio在device mapper的设备树进行逐层的转发时,最终转发到一个或多个叶子target节点终止。因为一个bio请求不可以跨多个target device(亦即物理空间段), 因此在每一个层次上,device mapper根据用户预先告知的mapped device 的target映射信息克隆一个或者多个bio,将bio进行拆分后转发到对应的target device上。这些克隆的bio先交给mapped device上对应的target driver上进行处理,根据target driver中定义的I/O处理规则进行I/O请求的过滤等处理,然后再提交给target device完成
用户空间部分
Device mapper在用户空间相对简单,主要包括device mapper库(如LVM2和dmraid等)和dmsetup工具。Device mapper库就是对ioctl、用户空间创建删除device mapper逻辑设备所需必要操作的封装,dmsetup是一个提供给用户直接可用的创建删除device mapper设备的命令行工具。用户空间主要负责如下工作:
- 发现每个mapped device相关的target device;
- 根据配置信息创建映射表;
- 将用户空间构建好的映射表传入内核,让内核构建该mapped device对应的dm_table结构;
- 保存当前的映射信息,以便未来重新构建。
比如下面的映射表例子将逻辑设备0~1023扇区、1024~1535扇区以及1536~1663三个地址范围分别以线形映射的方式映射到/dev/sda设备第204号扇区、/dev/sdb设备第766号扇区和/dev/sdc设备的第0号扇区开始的区域
1 | 0 1024 linear /dev/sda 204 |
画图表示如下:

下面以 LVM(逻辑卷管理)为例子,介绍用户空间的device mapper库
LVM(逻辑卷管理)可以弹性调整文件系统的容量。LVM可以整合多个物理分区,让这些分区看起来就像是一个磁盘一样。除此之外,还可以在将来新增新的分区到这个LVM管理的磁盘中或从中移除分区
LVM中的概念
- PV(物理卷):LVM最底层的物理卷。实际的分区需要使用
fdisk
命令调整系统标识符(system ID)成为8e(LVM的标识符),然后再使用pvcreate
命令将它转成PV后才能加以利用(标识符可以使用fdisk -l查看
) - PE(物理扩展块):整个LVM最小的存储块。我们的文件数据都是由写入PE来处理的。简单地说,这个PE有点像文件系统中的block大小。LVM默认使用4MB的PE块
- VG(卷组):就是将很多PV整合成的LVM大磁盘。每个VG最大仅能包含65534个PE,如果使用LVM默认的PE值,则一个VG最大可达到256GB
- LV(逻辑卷):VG还会被切成LV,这个LV就是最后可以被格式化使用的类似分区的东西,也可以称为逻辑设备。LV可以被格式化成某种文件系统

LVM弹性更改文件系统的容量,实际上就是通过交换PE来进行数据转换,将原本LV内的PE转移到其它设备中以降低LV容量,或将其它设备的PE加到此LV中以加大容量
模式
- 线性模式(linear):如果VG包含多个设备(分区)。线性模式就是当一个设备用完后,才使用其他设备
- 条带模式(striped):条带模式就是将一条数据拆成多个部分,分别写入多个设备。理论上,读写性能会比较好
LVM最主要的用处是在实现一个可以弹性调整容量的文件系统上,而不是在新建一个性能为主的磁盘上,所以,我们应该利用的是LVM可以弹性管理整个分区大小的用途上,而不是着眼在性能上。因此,LVM默认的读写模式是线性模式。如果使用条带模式,当任何一个设备(分区)”归天“时,所有的数据都会”损坏“
流程与命令
LVM必须要有内核支持(device mapper),且需要安装lvm2
LVM各组件的实现流程如下:

相关的命令:
任务 | PV | VG | LV |
---|---|---|---|
搜索(scan) | pvscan | vgscan | lvscan |
建立(create) | pvcreate | vgcreate | lvcreate |
列出(display) | pvdisplay | vgdisplay | lvdisplay |
增加(extend) | vgextend | lvextend | |
减少(reduce) | vgreduce | lvreduce | |
删除(remove) | pvremove | vgremove | lvremove |
改变容量(resize) | lvresize |
快照
下图左为最初建立系统快照区的状况,LVM会预留一个区域作为数据存放处。此时快照区内并没有任何数据,而快照区与系统区共享所有的PE数据
- 修改原LV:如果原LV中的数据发生改动,如下图右中的数据A,改动前系统会将该区域的数据移动到快照区。所以快照区被占用了一块PE成为A
- 修改快照区:如果在快照区中修改共享的数据,则数据会被拷贝到快照区,然后修改
快照可以用于数据恢复,但是恢复的数据量不能高于快照区所能负载的实际容量。由于原始数据会被移到快照区,如果快照区不够大,若原始数据被改动的实际数据量比快照区大,那么快照区就容纳不了,此时快照功能会失效

参考
- Linux 内核中的 Device Mapper 机制
- 《鸟哥的Linux私房菜》(第3版)
- 基于LVM的快照实现原理分析