Chinaunix首页 | 论坛 | 博客
  • 博客访问: 122301
  • 博文数量: 26
  • 博客积分: 2010
  • 博客等级: 大尉
  • 技术积分: 270
  • 用 户 组: 普通用户
  • 注册时间: 2008-08-15 10:54
文章分类

全部博文(26)

文章存档

2011年(1)

2010年(7)

2009年(4)

2008年(14)

我的朋友

分类:

2011-02-14 17:17:28

连续数据保护概述

CDP是Linux内核中基于块的连续数据保护模块,这个模块在数据块级别提供连续数据保护能力。连续数据保护是一种备份和恢复技术,它持续地捕获 所有I/O请求,并且为在这些请求打上时间戳标志。它将数据变化以及时间戳保存下来,以便恢复到过去的任意时刻。因此,这个模块支持数据的任意时刻映像。

在Linux CDP实现中,涉及三个设备:

  • 主机磁盘(Host Disk)设备;
  • CDP仓库(Repository)设备;
  • CDP元数据(Metadata)设备;

对主机磁盘设备数据块的在各个时刻所作的写操作都被记录下来,被顺序保存到CDP仓库设备中,同时对应的元数据也被保存在CDP元数据设备中。元数据包括以下信息:

struct metadata {
    int hrs, min, sec; 该数据块被写入主机磁盘设备的时间;
    unsigned int bisize; 该数据块的以字节为单位的长度;
    sector_t cdp_sector; CDP仓库设备中对应数据块的起始扇区编号;
    sector_t host_sector; 该数据块在主机磁盘设备中的起始扇区编号;
};

下图反映了主机磁盘设备和CDP仓库设备之间的关系。CDP仓库设备中按时间顺序保存了对主机磁盘设备的数据修改。A为主机磁盘设备上的一个扇区,该扇区在9:00和9:05分别进行了修改,它在CDP仓库设备中对应的扇区分别为A1和A2。

下图反映了CDP仓库设备和CDP元数据设备之间的关系,它们以写入顺序一一对应。CDP仓库设备中的一个元数据对应CDP元数据设备中一个I/O请求,实际上可能是多个扇区。具体扇区数由元数据中的bisize指定,而起始扇区位置由cdp_sector指定。

CDP仓库设备

全局变量maddr保存了下一个I/O请求在CDP仓库设备上执行的地址(起始扇区编号)。maddr的初值被定义为宏START_METADATA(0)。

unsigned int maddr = START_METADATA;

当一个写请求到来时,对应数据被写到CDP仓库设备中,这时所作的操作如下:

  • 将写入CDP仓库设备的数据块起始扇区编号设置为maddr;
  • 根据要写入主机磁盘设备的数据块的扇区数目增加maddr。

这时,我们要将这里写入的CDP仓库设备的数据块编号记录下来以便构造对应的元数据。

CDP元数据设备

全局变量taddr保存了下一个I/O请求对应的元数据在CDP元数据设备中保存的地址(起始扇区编号)。 taddr的初值被定义为宏START_METADATA(0)。

unsigned int taddr = START_METADATA;

当一个写请求到来时,对应的元数据被记录在CDP元数据设备中。

为了简单起见,在元数据设备上,一个扇区(512字节)只保存一个元数据信息(只有32字节),这样浪费了大量的存储空间,但对元数据设备的处理却非常简单:

  • 将写入CDP元数据设备的元数据起始扇区编号设置为taddr,长度为1个扇区;
  • 将taddr增1。

请求处理过程

请求处理过程是从make_request函数开始的。考虑到读请求的处理的相似性,甚至更为简单,我们这里只分析对写请求的处理过程。我们首先获 得当前的系统时间。之后,写请求bio结构(为说明方便,我们记为B)被分为三个写请求bio结构(分别为B0、B1和B2)。这三个bio结构的作用 是:

  • B0:将数据块写到主机磁盘设备;
  • B1:将数据块写到CDP仓库设备;
  • B2:将元数据写到CDP元数据设备。

同其它块设备驱动程序的实现一样。我们从B克隆产生B0、B1和B2。然后重定向它们要处理的设备,即bi_bdev域。另外一个大的变动是重新设置了bi_end_io域,用于在I/O请求完成之后进行善后处理。

为了处理善后,还将B0、B1和B2的bi_private指向同一个cdp_bio1结构。从这个结构,我们要能够回到对B的处理。

struct cdp_bio {
    struct bio *master_bio; 原来的bio,通过这个域我们可以从B0、B1、B2找到B
    struct bio *bios[3]; 如果IO为WRITE,这个指针数组分别指向B0、B1、B2,为何需要这个域?
    atomic_t remaining; 这里一个计数器,我们后面将解释。
    unsigned long state; 在I/O完成方法中使用
};

善后工作的主要目的是:在B0、B1和B2都执行完成后,回去执行B,为此,我们需要一个“have we finished”计数器,这就是原子整型变量remaining。在构造B0、B1、B2时分别递增,同时在B0、B1和B2的I/O完成方法中递减, 最后根据该值是否递减到0,来判断B0、B1和B2是否都已经执行完毕。为了防止B0在构造后,在B1和B2构造之前就执行到B0的I/O完成方法,从而 使得remaining变成0,这种错误情况。我们没有将remaining的初值设置为0,而是设为1。并在B0、B1、B2都构造完成执行递减一次。

B0、B1、B2都执行完成之后,进行如下的处理:

  • 调用B的善后处理函数;
  • 释放期间分配的数据结构;
  • 向上层buffer cache返回成功/错误码。

另一个需要说明的是对B2的构造,这个bio结构需要处理的是元数据。时间戳已经在进入make_request时获得了保存,而对主机磁盘设备操作的起始扇区和长度从B中可以获得,对应的CDP仓库和CDP元数据的起始地址分别保存在全局变量maddr和taddr中。

数据恢复过程

我们可以将数据恢复到以前的任意时刻。CDP实现代码中提供了一个blk_ioctl函数,用户空间以GET_TIME为参数调用该函数,将主机磁盘设备中的数据恢复到指定的时间点。恢复的过程分为两步:

1. 顺序读取CDP元数据设备的所有扇区,构造一个从主机磁盘设备数据块到CDP仓库设备的(在这个时间点之前)更新数据块的映射。其结果保存在以mt_home为首的(映射表)链表中。

这里需要构造taddr个对CDP元数据设备的读请求,每个请求读取一个扇区。在这些请求的I/O完成方法中,从读到的数据中构造元数据,并递减计数器count。

如果元数据中的时间戳早于或等于指定的恢复时间点,则需要添加或修改mt_home链表的元数据结构。需要说明的是,这些项是以 host_sector为关键字索引的,因此添加或修改取决于前面是否出现对同一个host_sector的修改。我们以顺序方式读取的过程中,可以保证 host_sector(在指定的恢复时间点之前)的最新修改cdp_sector会出现在这个链表中。

由于计数器count为taddr,如果它递减为0,说明CDP元数据设备中的所有数据均已读出并处理,这时我们可以继续往后面执行。

2. 从CDP仓库设备中读取这些更新的数据块,构造以mt_bi_home为首的链表。

同上面的处理类似,我们需要为mt_home链表中的每一项构造对CDP仓库设备的读请求,每个请求在CDP仓库设备的起始编号取决于 cdp_sector域,长度则根据bisize而定。这个请求读出的数据需要被写入到主机磁盘设备中,为此我们在读请求I/O完成函数中,构造一个对应 的往主机磁盘设备的写请求bio,该写请求的起始编号取决于host_sector域,长度根据bisize而定,而要写入的数据是刚刚从CDP仓库设备 中读出的数据。另外,在读请求I/O完成函数中,还要递减一个计数器,当该计数器递减到0时,说明我们已经全部处理了mt_home链表中的项,这时得到 一个以mr_bio_home为首,每项中都指向一个bio结构的链表。

struct list_head mt_home; //BIO更新链表
struct most_recent_blocks { //BIO更新表项
    struct bio *mrbio;
    struct list_head list;
};

3. 将mt_bi_home链表的数据块都恢复到主机磁盘设备中。

这个操作相对比较简单,我们只需要在主机磁盘设备上执行mt_bi_home链表的每一个bio请求项即可。当然,我们要在这些请求项的I/O完成方法中做善后处理,即如果所有请求项都已经执行完毕,则释放mt_home链表和mt_bi_home链表。

另一点要引起注意的是,上面的第2步操作和第3步操作采用tasklet实现,不知作何考虑?是否是因为它们在I/O完成方法中处理。

这个模块是如何使用的?

参考文献

1. Block Based CDP module for linux kernel,

阅读(3007) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~