关于Device Mapper

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

Device mapper在内核中向外提供了一个从逻辑设备到物理设备的映射架构,它包含三个重要的对象概念,Mapped Device、Mapping Table、Target device。其中Target device表示的是mapped device所映射的物理空间段,对mapped device所表示的逻辑设备来说,就是该逻辑设备映射到的一个物理设备。

关于Device Mapper框架的详细介绍可以参考经典博客:https://www.ibm.com/developerworks/cn/linux/l-devmapper/#resources

自定义target device

关于如何编写自定义的target device来表示新的块设备,在google上看到一篇博客写得比较清楚,故借来参考。
原文:https://gauravmmh1.medium.com/writing-your-own-device-mapper-target-539689d19a89

翻译:编写自己的device mappper target

每个存储系统/文件系统/管理员都需要以自己的方式查看基础数据设备。
例如:

  1. Linux机器上有四个数据磁盘,系统希望在其上创建镜像。
  2. 将它们串联起来,并将它们视为一个大型设备。
  3. 如果有一个磁盘,则特定系统可能希望对将要存储在该磁盘上的所有数据进行加密。

所有这些系统都希望在现有基础块设备的顶部放置一个逻辑块设备,并在对该层进行特定的处理(加密/解密)后,将该逻辑层上的请求映射到基础层。 此功能由Linux内核中的Device Mapper提供。

可以将Device Mapper定义为,通过创建块设备的虚拟层并将其映射到现有块设备来在存储堆栈中添加所需功能的通用方法

它创建了块设备的虚拟层,这些层可以在现有基础块设备的基础上做不同的事情,例如条带化,串联,镜像,快照等。Device Mapper是模块化内核驱动程序,为卷管理提供了通用框架。它已在内核2.6版中引入。 LVM2和EVMS 2.x工具使用Device Mapper。 LVM(逻辑卷管理器)是允许在Linux系统上创建和管理分区的工具。 LVM的第一个版本在内核2.4中,其中没有Device Mapper的概念。 因此,逻辑层的所有管理都是LVM的责任。 但是,由于内核2.6中的Device Mapper的概念,逻辑层管理由Device Mapper完成,并且LVM2的代码已大大简化。 这也为内核带来了模块化和可重用性。

device mapper target 的概念

如上所述,我们可以通过Device Mapper创建各种逻辑层以执行所需的功能。每个这种类型的层都是通过为该层定义“一个device mapper target”来创建的。设备映射器层的虚拟层与该层的DM(Device Mapper)target之间存在一一对应关系。

特定的DM target包含执行虚拟层打算执行的功能的任务的代码。例如,可以编写DM target以在现有块设备上实现镜像。 此DM target将虚拟层显示到上层,该虚拟层执行镜像任务。 当前,已经通过七个设备映射器目标将八个此类功能添加到了设备映射器。 设备映射器目标如下:

  1. Linear
    通过此DM target,我们可以串联多个磁盘,以将其视为单个大设备,或将一部分磁盘作为单个逻辑磁盘查看。 因此,它在现有块设备的顶部创建了线性逻辑设备。
  2. RAID-0 / Striped
    条带化DM target旨在处理跨物理卷的条带化,即执行众所周知的RAID-0功能。
  3. RAID-1 / Mirrored RAID
    镜像的DM target旨在处理多个磁盘上的镜像。 它通过创建和维护数量均包含相同数据的数据镜像来执行最著名的RAID级功能之一,从而通过负载平衡提高可靠性和操作速度。
  4. Snapshot
    此DM target执行快照功能,并允许访问所有文件的旧版本以及最新版本。
  5. DM-Crypt
    dm-crypt设备映射器目标执行通过对存储在磁盘上的所有数据进行加密和解密来提供安全性的任务。
  6. Multipath
    为了提供更高的访问磁盘可靠性,此DM target提供了多路径功能,以便在磁盘路径失败的情况下,可以通过备用路径访问磁盘上的数据。
  7. Zero
    Zero DM target将磁盘上所有操作的所有数据返回为零。 通常,它用于测试并填补新逻辑设备中的空白。
  8. Error
    Error DM target导致对映射磁盘的任何I / O失败。 这对于定义新逻辑设备中的间隙也很有用。

可以将这种设备映射器目标(Device Mapper target)作为模块插入内核,并根据用户的意愿将其删除。 也可以通过创建补丁将其插入内核。 设备映射器创建块设备的逻辑层,并将此逻辑层上的所有I / O请求映射到基础的现有块设备。 对于此类映射,设备映射器使用称为设备映射器表的数据结构。 该表告诉我们逻辑层的每个扇区(大小为512字节)如何映射到基础磁盘上的扇区。 因此,每个目标都通过使用其对应的设备映射器表进行I / O映射来实现其功能。

因此,设备映射器目标(Device Mapper target)表示一种块设备。“Device Mapper提供了定义块设备类型的功能”,这就是为什么它是通用层的原因。

当我们要提供一种具有某些高级功能(例如快照,重复数据删除)的新型块设备时。 我们创建新的设备映射器目标,然后将新功能的逻辑写入该设备映射器目标。 我们可以创建该设备映射器目标的块设备。 (即新型设备)

让我们开始进行编码…

我们的DM target将是内核模块。 假设我们将dm target称为“ basic_target”。 我们将创建2个文件。

  1. basic_target.c —— 该文件将具有DM target的所有代码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/init.h>
#include <linux/bio.h>
#include <linux/device-mapper.h>

/* This is a structure stores information about the underlying device
* Param:
* dev : Underlying device
* start: Starting sector number of the device
*/
struct my_dm_target {
struct dm_dev *dev;
sector_t start;
};

/* This is map function of basic target. This function gets called whenever you get a new bio
* request.The working of map function is to map a particular bio request to the underlying device.
* The request that we receive is submitted to out device so bio->bi_bdev points to our device.
* We should point to the bio-> bi_dev field to bdev of underlying device. Here in this function,
* we can have other processing like changing sector number of bio request, splitting bio etc.
*
* Param :
* ti : It is the dm_target structure representing our basic target
* bio : The block I/O request from upper layer
* map_context : Its mapping context of target.
*
* Return values from target map function:
* DM_MAPIO_SUBMITTED : Your target has submitted the bio request to underlying request.
* DM_MAPIO_REMAPPED : Bio request is remapped, Device mapper should submit bio.
* DM_MAPIO_REQUEUE : Some problem has happened with the mapping of bio, So requeue the
* bio request. So the bio will be submitted to the map function.
*/
static int basic_target_map(struct dm_target *ti, struct bio *bio,union map_info *map_context)
{
struct my_dm_target *mdt = (struct my_dm_target *) ti->private;
printk(KERN_CRIT "\n<<in function basic_target_map \n");

bio->bi_bdev = mdt->dev->bdev;

if((bio->bi_rw & WRITE) == WRITE)
printk(KERN_CRIT "\n basic_target_map : bio is a write request.... \n");
else
printk(KERN_CRIT "\n basic_target_map : bio is a read request.... \n");
submit_bio(bio->bi_rw,bio);


printk(KERN_CRIT "\n>>out function basic_target_map \n");
return DM_MAPIO_SUBMITTED;
}


/* This is Constructor Function of basic target
* Constructor gets called when we create some device of type 'basic_target'.
* So it will get called when we execute command 'dmsetup create'
* This function gets called for each device over which you want to create basic
* target. Here it is just a basic target so it will take only one device so it
* will get called once.
*/
static int
basic_target_ctr(struct dm_target *ti,unsigned int argc,char **argv)
{
struct my_dm_target *mdt;
unsigned long long start;

printk(KERN_CRIT "\n >>in function basic_target_ctr \n");

if (argc != 2) {
printk(KERN_CRIT "\n Invalid no.of arguments.\n");
ti->error = "Invalid argument count";
return -EINVAL;
}

mdt = kmalloc(sizeof(struct my_dm_target), GFP_KERNEL);

if(mdt==NULL)
{
printk(KERN_CRIT "\n Mdt is null\n");
ti->error = "dm-basic_target: Cannot allocate linear context";
return -ENOMEM;
}

if(sscanf(argv[1], "%llu", &start)!=1)
{
ti->error = "dm-basic_target: Invalid device sector";
goto bad;
}

mdt->start=(sector_t)start;

/* dm_get_table_mode
* Gives out you the Permissions of device mapper table.
* This table is nothing but the table which gets created
* when we execute dmsetup create. This is one of the
* Data structure used by device mapper for keeping track of its devices.
*
* dm_get_device
* The function sets the mdt->dev field to underlying device dev structure.
*/
if (dm_get_device(ti, argv[0], dm_table_get_mode(ti->table), &mdt->dev)) {
ti->error = "dm-basic_target: Device lookup failed";
goto bad;
}

ti->private = mdt;


printk(KERN_CRIT "\n>>out function basic_target_ctr \n");
return 0;

bad:
kfree(mdt);
printk(KERN_CRIT "\n>>out function basic_target_ctr with errorrrrrrrrrr \n");
return -EINVAL;
}

/*
* This is destruction function
* This gets called when we remove a device of type basic target. The function gets
* called per device.
*/
static void basic_target_dtr(struct dm_target *ti)
{
struct my_dm_target *mdt = (struct my_dm_target *) ti->private;
printk(KERN_CRIT "\n<<in function basic_target_dtr \n");
dm_put_device(ti, mdt->dev);
kfree(mdt);
printk(KERN_CRIT "\n>>out function basic_target_dtr \n");
}

/*
* This structure is fops for basic target.
*/
static struct target_type basic_target = {

.name = "basic_target",
.version = {1,0,0},
.module = THIS_MODULE,
.ctr = basic_target_ctr,
.dtr = basic_target_dtr,
.map = basic_target_map,
};

/*-------------------------------------------Module Functions ---------------------------------*/

static int init_basic_target(void)
{
int result;
result = dm_register_target(&basic_target);
if(result < 0)
printk(KERN_CRIT "\n Error in registering target \n");
return 0;
}

static void cleanup_basic_target(void)
{
dm_unregister_target(&basic_target);
}

module_init(init_basic_target);
module_exit(cleanup_basic_target);_
MODULE_LICENSE("GPL");
  1. Makefile —— 该文件将用于编译basic target模块
1
2
3
4
5
6
7
obj-m +=basic_target.o

all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
编译

编译basic_target模块的“ make”命令。(在Makefile所在目录)

1
make

这将生成一个名为“ basic_target.ko”的内核模块可执行文件。

加载basic target模块

在开始使用它之前,我们需要在内核中加载已编译的Linux内核模块。 要插入模块,请运行以下命令。

1
insmod basic_target.ko

此命令将模块加载到内核中,内核现在可以开始使用它了。 要验证模块是否已成功加载,请运行以下命令。

1
lsmod | grep basic_target

如果在“ lsmod”输出中看到basic_target,则说明模块已成功加载。

配置

让我们为设备创建一个2GB的临时文件。(dd 命令的使用可以查看我的上一个博客)。

1
dd if=/dev/zero of=/tmp/disk1 bs=512 count=20000

将循环设备附加到此文件

1
losetup /dev/loop6 /tmp/disk1

然后创建 basic_target 设备

1
echo 0 20000 basic_target /dev/loop6 0|dmsetup create my_basic_target_device

您可以在/ var / log / messages中看到构造函数被调用(或者使用命令dmesg)。
这样就在“ / dev / mapper / my_basic_target_device”上创建了一个新设备。

测试

我们可以在my_basic_target_device上创建文件系统,但这将触发设备上的许多IO并填写日志。
因此,让我们尝试使用dd命令在设备上写入1扇区。

1
dd if=/dev/zero of=/dev/mapper/my_basic_target_device bs=512 seek=10 count=1

现在,您应该看到对设备的写入请求,其起始扇区为10,大小为512字节。

恭喜你! 您现在了解了如何编写和测试自己的设备映射器目标。

Cheers!