LVM元数据分析

LVM元数据分析


LVM数据布局

在每个LVM物理卷(PV)上都包含三个部分:LVM标签、元数据区域、数据区域。

LVM关键数据分布

LVM标签

LVM标签占用一个扇区(512字节),可以位于分区开始的四个扇区中的任何一个,默认情况下在第二个扇区,LVM标签主要包含以下部分:

  • UUID -- 一个随机定长字符串,可以唯一标示一个PV,且不受系统启动顺序的影响。
  • 数据区域位置 -- 以NULL结尾的列表,每一项包含以字节为单位的“偏移”和“大小”。
  • 元数据区域位置 -- 以NULL结尾的列表,每一项包含以字节为单位的“偏移”和“大小”。

LVM标签最多可以包含15个区域,默认情况下有3个,1个数据区域加上2个元数据区域。

元数据区域

  • 开始有一个元数据头,接着是一个循环缓冲区,使用ASCII格式以追加的方式保存卷组信息。
  • 每个物理卷可以包含0、1和2份完全相同的元数据,可以在创建PV时通过参数指定,之后也可以进行修改,默认值为1。如果有2份,则第二份在分区的结尾。
  • 位于同一个VG中的所有PV上的元数据完全相同,可以设置只在部分分区上有元数据,部分分区没有元数据。

数据区域

以PE为单位进行数据空间分配。

关键数据结构

LVM标签头结构

$ cat lib/label/label.h
...

/* On disk - 32 bytes */
struct label_header {
	int8_t id[8];		/* LABELONE */
	uint64_t sector_xl;	/* Sector number of this label */
	uint32_t crc_xl;	/* From next field to end of sector */
	uint32_t offset_xl;	/* Offset from start of struct to contents */
	int8_t type[8];		/* LVM2 001 */
} __attribute__ ((packed));

...
  • id - 标签标识字符串,必须是“LABELONE”。
  • sector_xl - 标签所处的扇区号,一般都是1。
  • crc_xl - 从offset_xl开始到扇区结尾的数据的CRC校验值。
  • offset_xl - 标签正文起始位置偏移(从标签开始位置,以字节为单位进行计算,一般是32,也就是是label_header的大小)。
  • type - 标签类型,一般都是“LVM2 001”。

接下来就是从offset_xl偏移处开始的是标签正文,包含UUID以NULL结尾的数据区域位置和元数据区域位置列表等。

LVM标签正文结构

PV头结构:

$ cat lib/format_text/layout.h
...

struct pv_header {
	int8_t pv_uuid[ID_LEN];
	
	/* This size can be overridden if PV belongs to a VG */
	uint64_t device_size_xl;	/* Bytes */
	
	/* NULL-terminated list of data areas followed by */
	/* NULL-terminated list of metadata area headers */
	struct disk_locn disk_areas_xl[0];	/* Two lists */
} __attribute__ ((packed));

...
  • pv_uuid - PV的UUID。
  • device_size_xl - PV的大小(以字节为单位),如果属于一个VG,则会被覆盖。
  • disk_areas_xl - 数据和元数据区域列表。

扩展PV头结构:

$ cat lib/format_text/layout.h
...

struct pv_header_extension {
   uint32_t version;
   uint32_t flags;
   /* NULL-terminated list of bootloader areas */
   struct disk_locn bootloader_areas_xl[0];
} __attribute__ ((packed));

...
  • version - 版本。
  • flags - 标志。
  • bootloader_areas_xl - 启动区域列表数组。

区域描述结构:

$ cat lib/format_text/format-text.h
...

/* On disk */
struct disk_locn {
	uint64_t offset;	/* Offset in bytes to start sector */
	uint64_t size;		/* Bytes */
} __attribute__ ((packed));

...
  • offset - 数据或元数据区域的起始位置的字节偏移,从整个分区的第0个扇区开始计算。
  • size - 区域的大小,以字节为单位,0表示剩余所有空间。

元数据区域头结构

$ cat lib/format_text/layout.h
...

/* On disk */
/* Structure size limited to one sector */
struct mda_header {
	uint32_t checksum_xl;	/* Checksum of rest of mda_header */
	int8_t magic[16];	/* To aid scans for metadata */
	uint32_t version;
	uint64_t start;		/* Absolute start byte of mda_header */
	uint64_t size;		/* Size of metadata area */

	struct raw_locn raw_locns[0];	/* NULL-terminated list */
} __attribute__ ((packed));

...
  • checksum_xl - 该结构所在扇区除checksum_xl外所有数据的的CRC校验。
  • magic - 魔数,必须是"\040\114\126\115\062\040\170\133\065\101\045\162\060\116\052\076"。
  • version - 版本号, 必须是1。
  • start - 元数据区域的起始位置的字节偏移,从整个分区的第0个扇区开始计算。
  • size - 区域的大小,以字节为单位,0表示剩余所有空间。

元数据区域原始位置结构

由于元数据区域是一个用ASCII格式描述VG信息的循环缓冲区,所以又使用raw_locn来定位当前正在使用的VG信息的位置。

$ cat lib/format_text/layout.h
...

/* On disk */
struct raw_locn {
	uint64_t offset;	/* Offset in bytes to start sector */
	uint64_t size;		/* Bytes */
	uint32_t checksum;
	uint32_t flags;
} __attribute__ ((packed));

...
  • offset - 从该扇区开始的偏移地址。
  • size - 字节大小。
  • checksum - CRC校验。
  • flags - 标志。

源码分析

LVM命令执行环境初始化

$ tools/lvmcmdline.c
...

int lvm_run_command(struct cmd_context *cmd, int argc, char **argv)
{

...

	if (lvmetad_used() && !(cmd->command->flags & NO_LVMETAD_AUTOSCAN)) {
		if (cmd->include_foreign_vgs || !lvmetad_token_matches(cmd)) {
			if (lvmetad_used() && !lvmetad_pvscan_all_devs(cmd, cmd->include_foreign_vgs ? 1 : 0)) {
				log_warn("WARNING: Not using lvmetad because cache update failed.");
				lvmetad_make_unused(cmd);
			}
		}

...

}

...
$ cat lib/cache/lvmetad.c
...

int lvmetad_pvscan_all_devs(struct cmd_context *cmd, int do_wait)
{
...

	while ((dev = dev_iter_get(iter))) {
		if (sigint_caught()) {
			ret = 0;
			stack;
			break;
		}
	
		if (!lvmetad_pvscan_single(cmd, dev, NULL, NULL)) {
			ret = 0;
			stack;
			break;
		}
	}

...
	
	if (!_token_update(NULL)) {
		log_error("Failed to update lvmetad token after device scan.");
		return 0;
	}

...

}

...

int lvmetad_pvscan_single(struct cmd_context *cmd, struct device *dev,
			  struct dm_list *found_vgnames,
			  struct dm_list *changed_vgnames)
{
	struct label *label;
	struct lvmcache_info *info;
	struct _lvmetad_pvscan_baton baton;
	/* Create a dummy instance. */
	struct format_instance_ctx fic = { .type = 0 };
	
	if (!lvmetad_used()) {
		log_error("Cannot proceed since lvmetad is not active.");
		return 0;
	}
	
	// 读取标签
	if (!label_read(dev, &label, 0)) {
		log_print_unless_silent("No PV label found on %s.", dev_name(dev));
		if (!lvmetad_pv_gone_by_dev(dev))
			goto_bad;
		return 1;
	}
	
	info = (struct lvmcache_info *) label->info;
	
	baton.vg = NULL;
	baton.fid = lvmcache_fmt(info)->ops->create_instance(lvmcache_fmt(info), &fic);
	
	if (!baton.fid)
		goto_bad;
	
	if (baton.fid->fmt->features & FMT_OBSOLETE) {
		lvmcache_fmt(info)->ops->destroy_instance(baton.fid);
		log_warn("WARNING: Disabling lvmetad cache which does not support obsolete (lvm1) metadata.");
		lvmetad_set_disabled(cmd, LVMETAD_DISABLE_REASON_LVM1);
		_found_lvm1_metadata = 1;
		/*
		 * return 1 (success) so that we'll continue to populate lvmetad
		 * instead of leaving the update incomplete.
		 */
		return 1;
	}
	
	// 遍历每个Metadata Area,并调用_lvmetad_pvscan_single函数进行处理
	lvmcache_foreach_mda(info, _lvmetad_pvscan_single, &baton);
	
	if (!baton.vg)
		lvmcache_fmt(info)->ops->destroy_instance(baton.fid);
	
	if (!lvmetad_pv_found(cmd,
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值