无用的瞎bb:
距离上一篇博客其实已经过去很久了,本身没有懈怠但是鉴于实在太忙便一直没更新。
花了很长时间对公司产品做windows动态磁盘(ldm)和lvm的支持。也就是提供一个ldm或者lvm的镜像使用dokany对其进行虚拟重组,方便lvm相关信息的展示以及其lv分区中文件系统的进一步解析。
因为ldm是基于lvm的衍生品所以下面主要会讲关于lvm的故事,原理都是相通的。
0x01:lvm是啥?
lvm( Logical Volume Manager)是linux内核提供的逻辑卷管理工具。核心原理就是在硬盘和分区表之间增加一个抽象层(逻辑卷)。这个抽象层用于管理底层真实硬盘对上则表现为一个超大的逻辑卷。基础这种思想而扩展出来的能力就很多了。比如用一块硬盘创建出raid整列(RAID LOGICAL VOLUMES),动态伸缩逻辑分区,逻辑卷加密等等。当使用lvm时,有一个致命的缺点需要特别注意的:windows系统不支持lvm,所以任何形式的lvm分区在windows系统中是不识别的,与之相对应的,windows会使用LDM(Logical Disk Manager )管理多磁盘构成逻辑卷。
lvm的优点总结如下:
1. 将任意多个物理磁盘(或者分区)组合成一个大的逻辑磁盘。
2. 更好利用碎片部分,lv可能是由多个硬盘的片段构成
3. 允许创建具有raid功能的lv分区
4. 可以动态的创建,删除,伸缩lv卷的大小
0x02: lvm与lvm2
lvm目前有2个版本:lvm和lvm2。在目前大部分的linux发行版都是支持lvm的,而lvm2则需要linux kernel 2.6.9(事实上也已经被大部分主流发行版支持,所以我们现在日常所说的lvm多数都指lvm2,当然本篇文章也是面向lvm2的),详情见下表(大于等于以上版本均可):
linux发行版 | linux内核版本号 |
---|---|
Debian 4.0 | 2.6.18 |
Mint 2.0 | 2.6.17 |
Ubuntu 5.04 | 2.6.10 |
Fedora 3.0 | 2.6.9 |
Opensuse 9.3 | 2.6.11.4 |
Arch 0.7 | 2.6.10 |
Centos 4.8 | 2.6.9 |
我们可以通过 sudo lvm version
命令检查当前系统中lvm版本:
0x03:PV,VG,LV,PE
在理解lvm之前一定要理解一些基础概念,并懂得它们的继承层级关系:pv,vg,lv和pe.
PV(physical volume):物理卷在逻辑卷管理系统最底层,可为整个物理硬盘或实际物理硬盘(/dev/sdb)上的某个分区(/dev/sdb1)。在制作lvm的第一步就是将磁盘(分区)使用pvcreate命令变为PV分区,pvremove命令将PV转换回普通分区,表示lvm不再使用这部分空间。需要注意的是一个磁盘转变为PV后,并不是这个磁盘所有的空间都能使用。在描述PV的结构体(PhysicalVolume)中有个字段叫PEStart,它的单位是扇区。表示从硬盘或者分区起始的第PEStart个扇区是真正被使用的。
VG(volume group):卷组建立在物理卷上,一卷组中至少要包括一物理卷,卷组建立后可动态的添加卷到卷组中,一个逻辑卷管理系统工程中可有多个卷组。通过vgcreate创建VG,使用vgextend和vgremove控制物理卷的添加和删除。
LV(logical volume):逻辑卷建立在卷组基础上,卷组中未分配空间可用于建立新的逻辑卷,逻辑卷建立后可以动态扩展和缩小空间。
PE(physical extent):物理区域是物理卷中可用于分配的最小存储单元,物理区域大小在建立卷组时指定,一旦确定不能更改,同一卷组所有物理卷的物理区域大小需一致,新的pv加入到vg后,pe的大小自动更改为vg中定义的pe大小。注意PE的单位是扇区数。假设某个VG占用100个PE空间,每个PE大小是4096,每个扇区大小是512字节。则这个VG占用字节大小是 100*4096*512字节。
0x04: 核心数据结构
从上面的分层也可以看出来,lvm的数据结构主要有三个:PV,VG,LV.它们以文本(human readable)配置文件的形式存储在扇区上,关于更多的信息请仔细阅读一下libvslvm库的libvslvm_physical_volume_read_label函数。
该数据结构参考自libyal并略有修改:https://github.com/libyal/libvslvm
struct libvslvm_physical_volume_header
{
uint8_t identifier[39]; // 当前pv的uuid(vg允许有多个pv设备)
uint64_t volume_size;
};
typedef struct logical_volume_strip logical_volume_strip_t;
struct logical_volume_strip
{
char pv_name[128]; // "pv0"
uint64_t pe_start;
};
typedef struct logical_volume_segment logical_volume_segment_t;
struct logical_volume_segment
{
uint64_t start_extent;
uint64_t extent_count;
char type[16];
uint8_t stripe_count;
logical_volume_strip_t** stripes; // stripe_count个 strip
};
typedef struct logical_volume logical_volume_t;
struct logical_volume
{
char name[128];
char id[39]; // "wzkWht-tFcl-vR1S-50bC-gXcl-ATu2-2TKvkv"
char status[64];
uint8_t flags;
uint64_t creation_time; // unix time
char creation_host[16]; // "ubuntu"
uint8_t segment_count;
logical_volume_segment_t** segments;
};
typedef struct physical_volume physical_volumes_t;
struct physical_volume
{
char name[128]; // "pv0"
char id[39]; // "FKjNKr-Aq2l-507H-SjUe-o0iT-jIuG-Yez03z"
char device[128]; // "/dev/sdc1" 或者 "/dev/sdb"之类
uint8_t status[64];
uint8_t flags;
uint64_t dev_size; // sector count
uint64_t pe_start; // relative sector count from the begining of partition or disk
uint64_t pe_count; // total bytes size : pe_count * extent_size * sector_size
};
typedef struct volume_group volume_group_t;
struct volume_group
{
char name[128]; // max length
char id[39]; // UUID which occupies 38 bytes,the last char is '\0'
uint8_t seqno;
char format[8]; // often 'lvm2'
uint8_t status[64]; // LVM_STATUS
uint8_t flags; // unkown what it is
uint32_t extent_size;
uint32_t max_lv;
uint32_t max_pv;
uint32_t metadata_copies;
uint8_t pv_count;
physical_volumes_t** pvs; // 事实上这是一个长度为pv_count的指针数组
uint8_t lv_count;
logical_volume_t** lvs; // 事实上这是一个长度为lv_count的指针数组
};
0x05: 项目小结
之前只是交代了一下上下文。其实从这里才是真正的项目小结,总结一些lvm中的坑和项目上获得的其他经验:
1. 在physical_volume结构体中有一个id字段,它是一个标识唯一一个pv的UUID号,但是它不是以-
隔开的,大概是这个样子: 9LBcEB7PQTGIlLI0KxrtzrynjuSL983W
,但是在volume_header结构体中pv的UUID是以-
隔开的,比如9LBcEB-7PQT-GIlL-I0Kx-rtzr-ynju-SL983W
,事实上他们是相等的。
2. 当某个vg是有多个pv组成时,可以发现每个pv设备都可以读取到上述的一个配置文件,他们绝大部分内容都是相同的,除了 volume_header,表明当前是哪个pv设备。通过遍历vg下面每个lv的logical_volume_strip结构可以知道该vg依赖了哪些pv,然后再遍历volume_group中pvs成员可以得到该vg依赖的每个pv的uuid。
3. 要完成lvm的解析最好阅读一遍libvslvm这个开源库,libyal的代码写的很谨慎。
4. 通过lvm和ldm这个项目,把cgo玩的比较熟练了。
5. 英文文档的阅读是作为一个开发者的基本能力。
6. 事实上lvm远远比我现在完成功能更复杂,更强大的地方在于RAID还没进行研究,应该会在最近迭代进行。