磁盘分区分析
先创建一个100M的空的镜像文件
dd if=/dev/zero of=hd.img bs=1M count=100
再在这个文件上创建一个扩展分区和一个主分区。同时在扩展分区上创建三个逻辑分区。
fdisk hd.img << EOF
n
e
1
70000
n
p
2
100000
n
l
20000
n
l
30000
n
l
40000
n
l
50000
n
l
60000
n
l
p
w
EOF
使用fdisk hd.img
可以看到其分区结果如下
Device Boot Start End Sectors Size Id Type
hd.img1 2048 70000 67953 33.2M 5 Extended
hd.img2 71680 100000 28321 13.8M 83 Linux
hd.img5 4096 20000 15905 7.8M 83 Linux
hd.img6 22528 30000 7473 3.6M 83 Linux
hd.img7 32768 40000 7233 3.5M 83 Linux
hd.img8 43008 50000 6993 3.4M 83 Linux
hd.img9 53248 60000 6753 3.3M 83 Linux
hd.img10 63488 70000 6513 3.2M 83 Linux
再使用hexdump hd.img
查看镜像文件的数据
0000000 0000 0000 0000 0000 0000 0000 0000 0000
*
00001b0 0000 0000 0000 0000 f580 662a 0000 2000
00001c0 0021 5b05 0408 0800 0000 0971 0001 7500 MBR扩展分区1分区表项
00001d0 0432 3983 0614 1800 0001 6ea1 0000 0000 MBR主分区分区表项
00001e0 0000 0000 0000 0000 0000 0000 0000 0000
00001f0 0000 0000 0000 0000 0000 0000 0000 aa55
0000200 0000 0000 0000 0000 0000 0000 0000 0000
* 扩展分区1起始位置: 0x0100000
01001b0 0000 0000 0000 0000 0000 0000 0000 4100
01001c0 0002 3e83 011e 0800 0000 3e21 0000 4600 逻辑分区1分区表项。偏移: 0x0800
01001d0 0106 dd05 010d 4800 0000 2531 0000 0000 扩展分区2分区表项。偏移: 0x4800
01001e0 0000 0000 0000 0000 0000 0000 0000 0000
01001f0 0000 0000 0000 0000 0000 0000 0000 aa55
0100200 0000 0000 0000 0000 0000 0000 0000 0000
* 扩展分区2起始位置: 0x0a00000
0a001b0 0000 0000 0000 0000 0000 0000 0000 6600
0a001c0 0126 dd83 010d 0800 0000 1d31 0000 e800 逻辑分区2分区表项。偏移: 0x0800
0a001d0 0128 7c05 023b 7000 0000 2441 0000 0000 扩展分区3分区表项。偏移: 0x7000
0a001e0 0000 0000 0000 0000 0000 0000 0000 0000
0a001f0 0000 0000 0000 0000 0000 0000 0000 aa55
0a00200 0000 0000 0000 0000 0000 0000 0000 0000
* 扩展分区3起始位置: 0x0f00000
0f001b0 0000 0000 0000 0000 0000 0000 0000 0a00
0f001c0 0209 7c83 023b 0800 0000 1c41 0000 8c00 逻辑分区3分区表项。偏移: 0x0800
0f001d0 020b 1c05 032a 9800 0000 2351 0000 0000 扩展分区4分区表项。偏移: 0x9800
0f001e0 0000 0000 0000 0000 0000 0000 0000 0000
0f001f0 0000 0000 0000 0000 0000 0000 0000 aa55
0f00200 0000 0000 0000 0000 0000 0000 0000 0000
* 扩展分区4起始位置: 0x1400000
14001b0 0000 0000 0000 0000 0000 0000 0000 ac00
14001c0 022b 1c83 032a 0800 0000 1b51 0000 2f00 逻辑分区4分区表项。偏移: 0x0800
14001d0 032d bb05 0319 c000 0000 2261 0000 0000 扩展分区5分区表项。偏移: 0xc000
14001e0 0000 0000 0000 0000 0000 0000 0000 0000
14001f0 0000 0000 0000 0000 0000 0000 0000 aa55
1400200 0000 0000 0000 0000 0000 0000 0000 0000
* 扩展分区5起始位置: 0x1900000
19001b0 0000 0000 0000 0000 0000 0000 0000 5000
19001c0 030e bb83 0319 0800 0000 1a61 0000 d200 逻辑分区5分区表项。偏移: 0x0800
19001d0 0310 5b05 0408 e800 0000 2171 0000 0000 扩展分区6分区表项。偏移: 0xe800
19001e0 0000 0000 0000 0000 0000 0000 0000 0000
19001f0 0000 0000 0000 0000 0000 0000 0000 aa55
1900200 0000 0000 0000 0000 0000 0000 0000 0000
* 扩展分区6起始位置: 0x1e00000
1e001b0 0000 0000 0000 0000 0000 0000 0000 f200
1e001c0 0330 5b83 0408 0800 0000 1971 0000 0000 逻辑分区6分区表项。偏移: 0x0800
1e001d0 0000 0000 0000 0000 0000 0000 0000 0000 此处无扩展分区分区表项
*
1e001f0 0000 0000 0000 0000 0000 0000 0000 aa55
1e00200 0000 0000 0000 0000 0000 0000 0000 0000
*
6400000
这个表里的 逻辑分区n
对应 fdisk
输出的 hd.img(n+4)
,例如逻辑分区1
对应hd.img5
在MBR里有两条记录,偏移0x1BE
处。
扩展分区,类型0x05
,偏移0x00000800 == 2048
,长度0x00010971 == 67953
2000 00001c0 0021 5b05 0408 0800 0000 0971 0001
主分区,类型0x83
,偏移0x00011800 == 71680
,长度0x00006ea1 == 28321
7500 00001d0 0432 3983 0614 1800 0001 6ea1 0000
扩展分区
MBR中的扩展分区1的分区表项记录的扩展分区1的偏移位置为0x0800
,的确可以在0x0100000
(0x0800*512=0x0100000
)起始的扇区内找到在其内部的分区分配的表项记录。接着看扩展分区1里记录的扩展分区2的分区表项,其记录的偏移位置为0x4800
,按理我们可以在0x4800*512=0x900000
处的扇区找到扩展分区2的分区分配的表项,但实际没有。其位置在0x0a00000
处,这是因为,扩展分区2的偏移还要基于MBR中的扩展分区1的已有的偏移0x0800
,也就是应该按0x0800+0x4800
算,经过计算(0x0800+0x4800)*512
确实为0xa00000
,也就是现在看到的偏移地址。
那么扩展分区2分区表里记录的扩展分区3的分区表项显示,其偏移为0x7000
,通过hexdup
的结果,不难算出扩展分区3的实际开始位置为0x0f00000/512=0x7800
,恰好比其偏移多了0x7800-0x7000=0x0800
。
后续的所有扩展分区皆是如此,其起始扇区都是该扩展分区的分区表项里记录的偏移 加上 在MBR中的扩展分区1表项的偏移。
扩展分区名 | 分区表项记录偏移 | 实际分区起始偏移计算 | 实际分区起始偏移 | 实际分区起始地址 | 所含逻辑分区 |
---|---|---|---|---|---|
扩展分区1 | 0x0800 | 0x0800 + 0x0000 | 0x0800 : 2048 | 0x0100000 | 逻辑分区1 |
扩展分区2 | 0x4800 | 0x0800 + 0x4800 | 0x5000 : 20480 | 0x0a00000 | 逻辑分区2 |
扩展分区3 | 0x7000 | 0x0800 + 0x7000 | 0x7800 : 30720 | 0x0f00000 | 逻辑分区3 |
扩展分区4 | 0x9800 | 0x0800 + 0x9800 | 0xa000 : 40960 | 0x1400000 | 逻辑分区4 |
扩展分区5 | 0xc000 | 0x0800 + 0xc000 | 0xc800 : 51200 | 0x1900000 | 逻辑分区5 |
扩展分区6 | 0xe800 | 0x0800 + 0xe800 | 0xf000 : 61440 | 0x1e00000 | 逻辑分区6 |
通过这个表可以总结出来:
扩展分区的地址1 = MBR中扩展分区1中的偏移
扩展分区的地址n = (MBR中扩展分区1中的偏移 + 本分区表项中记录的偏移) * 512 (当n>1时)
逻辑分区
通过观察hexdump hd.img
的输出,其所有逻辑分区的偏移都是0x0800
。
0100000+0x1be: 4100 0002 3e83 011e 0800 0000 3e21 0000 逻辑分区1分区表项
0a00000+0x1be: 6600 0126 dd83 010d 0800 0000 1d31 0000 逻辑分区2分区表项
0f00000+0x1be: 0a00 0209 7c83 023b 0800 0000 1c41 0000 逻辑分区3分区表项
1400000+0x1be: ac00 022b 1c83 032a 0800 0000 1b51 0000 逻辑分区4分区表项
1900000+0x1be: 5000 030e bb83 0319 0800 0000 1a61 0000 逻辑分区5分区表项
1e00000+0x1be: f200 0330 5b83 0408 0800 0000 1971 0000 逻辑分区6分区表项
逻辑分区名 | 实际偏移计算 | 实际分区起始偏移 | 设备名 |
---|---|---|---|
逻辑分区1 | 0x0800 + 0x0800 | 0x1000 : 4096 | hd.img5 |
逻辑分区2 | 0x5000 + 0x0800 | 0x5800 : 22528 | hd.img6 |
逻辑分区3 | 0x7800 + 0x0800 | 0x8000 : 32768 | hd.img7 |
逻辑分区4 | 0xa000 + 0x0800 | 0xa800 : 43008 | hd.img8 |
逻辑分区5 | 0xc800 + 0x0800 | 0xd000 : 53248 | hd.img9 |
逻辑分区6 | 0xf000 + 0x0800 | 0xf800 : 63488 | hd.img10 |
可以跟fdisk
结果对比起来看
Device Boot Start End Sectors Size Id Type
hd.img1 2048 70000 67953 33.2M 5 Extended
hd.img2 71680 100000 28321 13.8M 83 Linux
hd.img5 4096 20000 15905 7.8M 83 Linux
hd.img6 22528 30000 7473 3.6M 83 Linux
hd.img7 32768 40000 7233 3.5M 83 Linux
hd.img8 43008 50000 6993 3.4M 83 Linux
hd.img9 53248 60000 6753 3.3M 83 Linux
hd.img10 63488 70000 6513 3.2M 83 Linux
分析代码
#include
#include
#define MBR_OFFSET 446
#define PARTITION_ENTRY_SIZE 16
#define NUM_PRIMARY_PARTITIONS 4
#pragma pack(push, 1)
typedef struct {
uint8_t status;
uint8_t chs_first[3];
uint8_t type;
uint8_t chs_last[3];
uint32_t offset_lba;
uint32_t num_sectors;
} PartitionEntry;
#pragma pack(pop)
void read_partition_table(FILE *file, uint32_t ext_lba, uint32_t partition_table_lba, int depth) {
fseek(file, ((partition_table_lba)*512) + MBR_OFFSET, SEEK_SET);
PartitionEntry entries[NUM_PRIMARY_PARTITIONS];
fread(entries, sizeof(PartitionEntry), NUM_PRIMARY_PARTITIONS, file);
for (int i = 0; i < NUM_PRIMARY_PARTITIONS; ++i) {
PartitionEntry entry = entries[i];
if (entry.type == 0) {
continue;
}
uint32_t offset_lba = 0;
if (entry.type == 0x05) {
ext_lba = ext_lba != 0 ? ext_lba : entry.offset_lba;
uint32_t offset = depth == 0 ? partition_table_lba : entry.offset_lba;
offset_lba = ext_lba + offset;
} else {
offset_lba = partition_table_lba + entry.offset_lba;
}
printf("%*s>%uP[%u]: type=0x%02X, start=%x, size=%u end %u\n", depth * 3, "", depth, i + 1, entry.type,
offset_lba, entry.num_sectors, offset_lba + entry.num_sectors - 1);
if (entry.type == 0x05) {
read_partition_table(file, ext_lba, offset_lba, depth + 1);
}
}
}
int main(int argc, char *argv[]) {
if (argc != 2) {
printf("Usage: %s \n", argv[0]);
return 1;
}
FILE *file = fopen(argv[1], "rb");
if (!file) {
perror("Error opening file");
return 1;
}
printf("Reading partition information from %s\n", argv[1]);
read_partition_table(file, 0, 0, 0);
fclose(file);
return 0;
}
在镜像文件指定分区创建EXT2文件系统
以下命令都需要root权限
假设要安装到hd.img7
分区
# -P 或 --partscan:在关联文件后执行分区扫描。这对于处理磁盘镜像文件中的分区非常有用
losetup -P -f --show hd.img
# 需要观察这个命令的输出,假设输出`/dev/loop0`
# 使用kpartx将镜像文件虚拟为块设备
kpartx -av /dev/loop0
# 此时应该在/`/dev/mapper`目录下生成了`hd.img`分区对应的设备文件`loop0p*`
# 创建文件系统
mke2fs /dev/mapper/loop0p7
# 卸载分区
kpartx -d /dev/loop0
# 卸载虚拟设备
losetup -d /dev/loop0
此时hd.img7
分区下就已经安装了ext2
文件系统。在卸载前也可以通过mount
命令把它挂载到某个目录。
需要补充说明一下losetup
这个命令,如果加了-P
参数,新版本Linux是会直接在/dev
目录下出现loop0p*
文件的,就不用再用kpartx
命令了。但有的系统虽然losetup
有这个参数,但实际使用并没有在/dev
目录下生成loop0p*
文件。
另外,就算可以直接在/dev
目录下生成loop0p*
文件,也一样可以用kpartx
命令,然后使用/dev/mapper
目录下的文件。
经过测试
支持的有Rocky Linux 9.2
不支持的有 Fedora 34
、Fedora 38