ZYB ARTICLES REPOS

磁盘分区分析

先创建一个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,的确可以在0x01000000x0800*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 34Fedora 38