Device Mapper(DM)是Linux 2.6全面引入的块设备新构架,通过DM可以灵活地管理系统中所有的真实或虚拟的块设备。

DM以块设备的形式注册到Linux内核中,凡是挂载(或者说“映射”)于DM结构下的块设备,不管他们是如何组织,如何通讯,在Linux看来都是一个完整的DM块设备。因此DM让不同组织形式的块设备或者块设备集群在Linux内核面前有一个完整统一的DM表示。

一、DM与MD

在Linux内核代码中(本文以2.6.33内核代码为参照),DM指的是整个Device Mapper的设计框架。MD(Mapped Device)是框架所虚拟出来的各种设备。简而言之DM就是不同种类的MD经过特定的关系连接到块设备管理器上的大构架。

DM的内核代码集中在drivers/md目录中。DM构架相关的代码在dm.c和dm-io.c文件中,各种抽象的MD虚拟设备代码分散在同目录的其他文件中,除了dm-ioctl.c是dm设置接口设备。

二、使用DMSetup

使用DM的工具是dmsetup。这个命令帮助我们“组装”、“解散”、“监视”我们自己的虚拟存储结构。具体的用法可以参照[1] ,本文的目的是结合Linux内核源代码来窥视dm的运作机制。

在dmsetup的子命令中,create、load、reload子命令都带有 --table <table> 的参数,<table>字符串是创建dm设备的关键,若非在命令行中以参数形式给出,则必然需要写在一个table文件中传给dmsetup。

table字符串有如下形式:

<start> <length> <type> <arguments>

为了弄清楚这些参数,首先必须明白,create、load、reload子命令总是将一个或一组已经存在的块设备A的一部分或者全部虚拟称为块设备B。在内核代码中,块设备B(也是我们直接打交道的设备)称为mapped device,那一组块设备A中指定的那部分抽象(可以看作是我们将它交给dm来管理的那一部分)称为target device(s),对应的驱动为target driver。我们并不要求设备A必须是一个真实的磁盘,它也可以是dm已经虚拟出来的另外一个mapped device。[2] 详尽的解释了mapped device、target driver和target device之间的关系。但是这篇文章并没有着重分析源代码,而这是本文分析的重点。

table字符串中的<start>和<length>是设备A中,交由dm管理的区域,单位是sector。也就是说,由此创建的mapped device刚好映射在源设备中偏移为start、长度为length的这些sectors中;<type>为target driver的类型,每一个type字符串都对应一个target driver;<arguments>是用来创建target device的参数,这些参数传递给target device的创建函数的形式就如同命令行参数传递给int main(int argc, char *argv[])一样。

Linux内核自带的target driver有linear、stripe、mirror、multi-path、dm-crypt以及一组标准raid的驱动。接下来,我们以stripe的代码为例,来解释target driver是如何创建、运行target device的。

三、Target Driver

每一个target device在内核代码中体现为对应的驱动,这些驱动都必须符合DM构架,受DM的管理。有人可能会疑问,为什么DM构架中的驱动都是target驱动,而不是MD的驱动?因为DM的设计中,MD只是一个对外的统一接口,不同target driver的对外接口都是一样的,因此无需为不同的虚拟方式编写不同的MD,只用提供不同的target driver即可(PS:也许这里叫做mapped driver可以避免混淆,因为MD和target driver(以后简称driver )的实例之间是一对一的关系,而target driver同target device(以后简称target )之间是一对多的关系。将driver的概念融合进入md就变成md与target之间一对多的二元关系,而不是现在的md-driver-target三元关系。但md与driver毕竟一个是通用的,一个是特殊的,由此分解为三元关系也就不难理解)。

再此统一一下术语的简称:我们将mapped device简称为md,target device简称为target。之所以这样简称是因为内核代码的命名规则也大致如此。另外的target driver简称为driver(源代码不会出现,因为DM框架管理的是target,不是driver。driver直接insmod就可以了);源设备简称为device(源代码中只有通过名字包含bdev的变量来代表这些设备)。

本文以dm-stripe.c为例,来分析一个target应该具备哪些基本的要素来完成设计好的设备抽象。stripe所要做的是将多个设备的等长区域合并起来组成一个完成的抽象设备,其重点在于寻址。假设有n个devices,每个的区域长度为m,那么第i个块应该存放在第 (i%n) 个target中的偏移量为 (i/n) 的块(要注意的是实际的偏移还得加上target相对于设备的偏移)。

首先,每个driver需要有一个struct target_type结构向DM注册自己,并且这个结构在所有driver实例间共享,换句话说所有driver实例都可以看作从属于这种类型,因此这个target_type应该理解为driver type才对。dm-stripe的struct target_type结构如下

static struct target_type stripe_target = {
.name = "striped", // <type>名称
.version = {1, 3, 0},
.module = THIS_MODULE,
.ctr = stripe_ctr, // 创建器
.dtr = stripe_dtr, // 销毁器
.map = stripe_map, // 映射
.end_io = stripe_end_io,// IO结束通知
.status = stripe_status,
.iterate_devices = stripe_iterate_devices, // 迭代遍历源设备
.io_hints = stripe_io_hints,
};

创建器、销毁器和映射是一般target driver都具备的功能。

每个创建器都有如下函数原型:

int xxx_ctr(struct dm_target *ti, unsigned int argc, char **argv);

在设备创建时,DM框架会自动创建对应的struct dm_target结构,并力所能及地初始化了一些成员。现在创建器所要做的就是完成对该结构的初始化。那么先来看看DM框架初始化了哪些,需要ctr初始化另外那一些:

struct dm_target {
struct dm_table *table; // @driver 到 target device 的映射表,由DM框架维护
struct target_type *type; // @driver 所注册的那个type
/* target limits */
sector_t begin; // @<start>
sector_t len; // @<length>
/* Always a power of 2 */
sector_t split_io; // 块大小(每个块的扇区数)
/*
* A number of zero-length barrier requests that will be submitted
* to the target for the purpose of flushing cache.
*
* The request number will be placed in union map_info->flush_request.
* It is a responsibility of the target driver to remap these requests
* to the real underlying devices.
*/
unsigned num_flush_requests;
/* target specific data */
void *private; // 自定义的设备相关数据
/* Used to provide an error string from the ctr */
char *error;
};

带@标记的成员由DM初始化,或者部分初始化,其他初始化工作由ctr完成。一般来说ctr主要做两件事:

1. 将源设备的dev信息记录到table中。

2. 将target device(s)的信息初始化并记录在private中

table成员就是driver实例到target(s)之间的映射表。DM框架提供了int dm_get_device(struct dm_target *ti, const char *path, sector_t start, sector_t len, fmode_t mode, struct dm_dev **result)函数将path所指定的设备的bdev以及对应的区间、权限、模式等填入ti->table中。stripe_ctr要做的就是将参数中对应的字符串传递给这个函数。

同时,stripe_ctr创建了自定义的struct stripe_c结构sc,并记录在ti->private上。l利用dm_get_device的result出参填满sc->stripes数组(关键就是记住源设备的dev结构,DM中统一用struct dm_dev *指针来引用)。

对应的销毁器stripe_dtr就是将stripe_ctr向内核申请的资源一一释放掉,在此不累述。

最关键的是map函数。任何一个bio(块设备的io请求)都要映射到最终存储它的设备上的相应位置,map函数就是完成这一功能。该函数的原型如下:

int xxx_map(struct dm_target *ti, struct bio *bio, union map_info *map_context);

ti代表target,bio是发给这个target的io请求。一个bio有三个关键成员:bi_sector(位置)、bi_bdev(设备)、bi_io_vec(数据)。DM框架将bio发给map函数,使得target有机会来改变bio的这三个关键成员,从而实现两个目的:重定位和修改数据。map_context在许多情况下并没有许多作用。

如果map函数将bio赋值后又分发出去,那么就返回DM_MAPIO_SUBMITTED告诉DM不要再处理了;如果map函数修改了bio的内容,希望DM将bio按照新内容再分发,那么就返回DM_MAPIO_REMAPPED即可;如果map函数将bio加入队列中等待后续处理,则返回DM_MAPIO_REQUEUE。DM相应的处理代码可以在dm.c中的__map_bio()函数中找到。

stripe_map就很简单了,直接修改bio的bi_sector和bi_bdev,返回DM_MAPIO_REMAPPED通知DM再分发一次即可。stripe所扮演的角色就好比是一个邮件中转站,下辖N个子邮箱。所有邮件都按照规则被转发到对应子邮箱中,中转站的工作就是把每个邮件的地址和收件人改一改再让邮递员送一遍即可,接下来的bio传递路径分析将详细展示这一中转过程。

四、DM转发bio的过程

DM为每一个driver的实例创建一个md作为对外的接口,每一个md在内核中注册成为一个块设备,因此每一个driver的实例就是一个虚拟的块设备。

每一个md通过driver的实例管理一个或多个target,driver的主要工作就是把每个提交给md的bio请求进行数据转换并转发给对应的target。md实现了一个标准的块设备驱动,这里仅分析bio的转发过程。

每个块设备都有一个请求队列,请求队列包含一个make_request_fn指针指向原型为int make_request(struct request_queue *q, struct bio *bio);的函数,Linux内核中的void generic_make_request(struct bio *bio);函数就是通过bio找到对应的bi_bdev,然后找到该bdev对应的request_queue,并调用其make_request_fn函数:

block/blk_core.c 1484行:ret = q->make_request_fn(q, bio);

这就是bio从内核进入DM的起点。为什么这么说?因为在md创建的时候(通过dm.c中alloc_dev())将md->queue的make_request_fn指针设置为了dm_request:

drivers/md/dm.c 1908行:blk_queue_make_request(md->queue, dm_request);

dm_request接收到bio之后有两种选择:如果q->queue_flags被设置了QUEUE_FLAG_STACKABLE,则对request进行排队处理,否则直接分发。alloc_dev中创建queue的时候按照QUEUE_FLAG_DEFAULT创建,包含了QUEUE_FLAG_STACKABLE,但是接着该标志被清除了:

drivers/md/dm.c 1889行:md->queue = blk_init_queue(dm_request_fn, NULL);
...
drivers/md/dm.c 1903行:queue_flag_clear_unlocked(QUEUE_FLAG_STACKABLE, md->queue);

因此dm_request()函数将走_dm_request()分支。以下是bio所走过的流程:

int dm_request(struct request_queue *q, struct bio *bio)
`-> _dm_request(q, bio);
`-> __split_and_process_bio(md, bio); // md 由 q->queue_data 获得
`-> __clone_and_map(&ci); // md、bio等信息记录在 ci 结构体中
`-> __map_bio(ti, clone, tio); // ti 由 ci->md 查表获得,clone由ci->bio克隆获得
`-> ti->type->map(ti, clone, &tio->info);

最终,bio传递给了对应ti的map函数。

要说明的是,DM构架及其驱动一般不会是真实设备的驱动,因此只会对bio进行处理之后再转发出去。转发的方法就是修改bio->bi_bdev和bio->bi_sector。其中bi_bdev必需是在内核中已注册的设备,这些块设备和dm的块设备一道在Linux内核中注册,在Linux看来是平等的。而一个md其实是将其他块设备的bdev记录在自己的映射表中,按照自身的逻辑规律对bio进行映射转发而已。

参考文献

from :http://blog.csdn.net/SonicLing/article/details/5460311#[2]

Device Mapper 代码分析的更多相关文章

  1. Linux内核中的GPIO系统之(3):pin controller driver代码分析

    一.前言 对于一个嵌入式软件工程师,我们的软件模块经常和硬件打交道,pin control subsystem也不例外,被它驱动的硬件叫做pin controller(一般ARM soc的datash ...

  2. Linux kernel的中断子系统之(七):GIC代码分析

    返回目录:<ARM-Linux中断系统>. 总结: 原文地址:<linux kernel的中断子系统之(七):GIC代码分析> 参考代码:http://elixir.free- ...

  3. Linux中断 - GIC代码分析

    一.前言 GIC(Generic Interrupt Controller)是ARM公司提供的一个通用的中断控制器,其architecture specification目前有四个版本,V1-V4(V ...

  4. Linux内核中的GPIO系统之(3):pin controller driver代码分析--devm_kzalloc使用【转】

    转自:http://www.wowotech.net/linux_kenrel/pin-controller-driver.html 一.前言 对于一个嵌入式软件工程师,我们的软件模块经常和硬件打交道 ...

  5. Device Tree(三):代码分析【转】

    转自:http://www.wowotech.net/linux_kenrel/dt-code-analysis.html Device Tree(三):代码分析 作者:linuxer 发布于:201 ...

  6. Device Tree(三):代码分析

    一.前言 Device Tree总共有三篇,分别是: 1.为何要引入Device Tree,这个机制是用来解决什么问题的?(请参考引入Device Tree的原因) 2.Device Tree的基础概 ...

  7. 【转】Device Tree(三):代码分析

    原文网址:http://www.wowotech.net/linux_kenrel/dt-code-analysis.html 一.前言 Device Tree总共有三篇,分别是: 1.为何要引入De ...

  8. Linux 内核中的 Device Mapper 机制

    本文结合具体代码对 Linux 内核中的 device mapper 映射机制进行了介绍.Device mapper 是 Linux 2.6 内核中提供的一种从逻辑设备到物理设备的映射框架机制,在该机 ...

  9. [转] Linux 内核中的 Device Mapper 机制

    本文结合具体代码对 Linux 内核中的 device mapper 映射机制进行了介绍.Device mapper 是 Linux 2.6 内核中提供的一种从逻辑设备到物理设备的映射框架机制,在该机 ...

随机推荐

  1. 3分钟搞掂Set集合

    前言 声明,本文用的是jdk1.8 前面章节回顾: Collection总览 List集合就这么简单[源码剖析] Map集合.散列表.红黑树介绍 HashMap就是这么简单[源码剖析] LinkedH ...

  2. libevent中evmap实现(哈希表)

    libevent中,需要将大量的监听事件event进行归类存放,比如一个文件描述符fd可能对应多个监听事件,对大量的事件event采用监听的所采用的数据结构是event_io_map,其实现通过哈希表 ...

  3. RPC服务和HTTP服务

    很长时间以来都没有怎么好好搞清楚RPC(即Remote Procedure Call,远程过程调用)和HTTP调用的区别,不都是写一个服务然后在客户端调用么?这里请允许我迷之一笑~Naive!本文简单 ...

  4. contenteditable 插入及粘贴纯文本内容

    本文主要介绍 div 标签设置  contenteditable = ' true ' 时,在光标位置插入输入的内容,或在光标位置粘贴纯文本内容.文中涉及知识,可参考以下: http://www.zh ...

  5. discuz7.2 faq.php 注入漏洞分析

    写在前面的话:分析完整个漏洞,不得不感叹,发现漏洞的人真的好细心呀. 在分析整个漏洞之前,没看poc,然后就直接看faq.php 准备试试自己发现漏洞的能力,但是分析完一整个php,也是卡在 in() ...

  6. Scrapy命令行基本用法

    1.创建一个新项目: scrapy startproject myproject 2.在新项目中创建一个新的spider文件: scrapy genspider mydomain mydomain.c ...

  7. SAC E#1 - 一道难题 Tree

    题目背景 冴月麟和魏潇承是好朋友. 题目描述 冴月麟为了守护幻想乡,而制造了幻想乡的倒影,将真实的幻想乡封印了.任何人都无法进入真实的幻想乡了,但是她给前来救她的魏潇承留了一个线索. 她设置了一棵树( ...

  8. ●POJ 2007 Scrambled Polygon

    题链: http://poj.org/problem?id=2007 题解: 计算几何,极角排序 按样例来说,应该就是要把凸包上的i点按 第三像限-第四像限-第一像限-第二像限 的顺序输出. 按 叉积 ...

  9. solr6.6初探之查询篇

    关于搜索与查询,首先我们来看一张图: 这张图说明了solr查询原理: 1.当通过solr发起查询的时候,引擎会选择一个RequestHandler(从字面意思上来说就是请求处理器)来进行查询处理 2. ...

  10. 从零开始搭建口袋妖怪管理系统(2)-借助ngRoute实现详情页面跳转

    一.目标 上一次我们用Angular1.x完成了简单的口袋妖怪展示列表页面,现在我们想要了解口袋妖怪更多的信息,但是发现原有单行表格可能容纳不下口袋妖怪的所有信息,所以现在我们需要一个口袋妖怪详情界面 ...