本文以现代 Android 设备(GPT 分区)为例,详细讲解 Android 设备上每一个分区的作用、布局结构以及如何通过工具读取/修改分区表。
一、为什么要了解分区?
Android 设备底层是一颗 eMMC / UFS 存储芯片,它就像电脑硬盘一样被划分为多个独立区域,每个区域承担不同职责:
boot装的是 Kernel + ramdisksystem装的是 Android 框架代码userdata装的是用户的所有应用和数据recovery装的是恢复模式镜像
如果你没搞清楚分区结构,刷错地方轻则变砖,重则永久损坏。
二、MBR vs GPT
早期 Android 设备使用 MBR(Master Boot Record),最多只能划 4 个主分区,且只支持 2TB 以下设备。现代 Android 设备几乎全部转向 GPT (GUID Partition Table)。
GPT 结构
偏移 0 : Protective MBR (兼容老工具)
偏移 1 sector : GPT Header (主头)
偏移 2~33 sector: Partition Entries (128 项 × 128 字节)
...实际分区数据...
末尾倒数 33 sector: Backup Partition Entries
末尾倒数 1 sector : Backup GPT Header
GPT Header 结构(C 描述)
c
struct gpt_header {
uint8_t signature[8]; // "EFI PART"
uint32_t revision; // 0x00010000
uint32_t header_size; // 通常 92
uint32_t header_crc32; // 头部 CRC
uint32_t reserved;
uint64_t current_lba; // 当前 header 所在 LBA
uint64_t backup_lba; // 备份 header 所在 LBA
uint64_t first_usable_lba; // 第一个可用 LBA
uint64_t last_usable_lba; // 最后一个可用 LBA
uint8_t disk_guid[16]; // 磁盘 GUID
uint64_t partition_entry_lba; // 分区表起始 LBA
uint32_t num_partition_entries; // 通常 128
uint32_t partition_entry_size; // 通常 128
uint32_t partition_entries_crc32;
} __attribute__((packed));
Partition Entry 结构
c
struct gpt_entry {
uint8_t partition_type_guid[16]; // 分区类型 GUID
uint8_t unique_partition_guid[16]; // 分区唯一 GUID
uint64_t starting_lba; // 起始 LBA
uint64_t ending_lba; // 结束 LBA
uint64_t attributes; // 属性
uint16_t partition_name[36]; // UTF-16 分区名 (最多36字符)
} __attribute__((packed));
三、典型 Android 设备的分区列表
以一台高通平台旗舰机为例(2023年款,使用 Dynamic Partition + A/B):
分区名 | 大小 | 作用
-------------------|----------|----------------------------------
xbl_a / xbl_b | 3.5 MB | eXtensible Bootloader (BootROM 之后的固件)
xbl_config_a/b | 128 KB | xbl 配置
abl_a / abl_b | 2 MB | Android Boot Loader (基于 UEFI 的 LK)
aop_a / aop_b | 256 KB | Always-On Processor 固件
tz_a / tz_b | 4 MB | TrustZone 镜像 (TEE 内核)
hyp_a / hyp_b | 256 KB | Hypervisor 镜像
modem_a / modem_b | 130 MB | 基带固件
dsp_a / dsp_b | 8 MB | DSP 固件
boot_a / boot_b | 100 MB | Linux Kernel + Generic Ramdisk
init_boot_a/b | 8 MB | Init 阶段 ramdisk (Android 13+)
vendor_boot_a/b | 32 MB | Vendor 的 ramdisk + DTB
recovery (旧) | 64 MB | Recovery 镜像 (A/B 设备已无独立分区)
dtbo_a / dtbo_b | 8 MB | Device Tree Blob Overlay
vbmeta_a / vbmeta_b| 64 KB | AVB 校验元数据
super | 9 GB | Dynamic Partition (含 system, vendor, product 等)
metadata | 16 MB | 加密元数据
misc | 1 MB | Bootloader Control Block (BCB)
persist | 32 MB | 厂商持久化数据
userdata | 剩余空间 | 用户数据
四、关键分区详解
1. boot 分区 --- Android Boot Image
boot 分区里装的是 Android Boot Image,有自己的头部格式:
c
// Android Boot Image v3 (Android 11+)
struct boot_img_hdr_v3 {
uint8_t magic[8]; // "ANDROID!"
uint32_t kernel_size;
uint32_t ramdisk_size;
uint32_t os_version;
uint32_t header_size;
uint32_t reserved[4];
uint32_t header_version; // 3
char cmdline[1536];
};
/*
* 布局:
* +-----------------+ 0
* | boot header | 4096 字节
* +-----------------+
* | kernel | n 个 page,n = (kernel_size+4095)/4096
* +-----------------+
* | ramdisk | m 个 page
* +-----------------+
*/
可以使用 mkbootimg 工具打包:
bash
mkbootimg \
--kernel out/Image.gz \
--ramdisk out/ramdisk.cpio.gz \
--header_version 3 \
--os_version 13.0.0 \
--os_patch_level 2024-01 \
-o boot.img
2. super 分区 --- Dynamic Partition
Android 10 引入 Dynamic Partition ,把 system / vendor / product / system_ext 都合并到一个 super 分区里,内部用 LVM 类似的元数据来管理。
好处是:OEM 不用提前给每个分区分死固定大小,可以动态调整。
super 分区
+--------------------------------------+
| super metadata (geometry + tables) | 4 KB × 2 (主+备份)
+--------------------------------------+
| system_a (3 GB) |
+--------------------------------------+
| vendor_a (500 MB) |
+--------------------------------------+
| product_a (1 GB) |
+--------------------------------------+
| system_ext_a (200 MB) |
+--------------------------------------+
| ... b slot ... |
+--------------------------------------+
查看 super 中的逻辑分区:
bash
adb shell su -c "lpdump /dev/block/by-name/super"
输出示例:
Slot 0:
Metadata version: 10.2
Metadata size: 4096 bytes
Metadata max size: 65536 bytes
Metadata slot count: 3
Partition table:
------------------------
Name: system_a
Group: main_a
Attributes: readonly
Extents:
0 .. 6291455 linear super 2048
Size: 3221225472
------------------------
Name: vendor_a
...
3. misc 分区 --- Bootloader 通信
misc 是 bootloader 与 Android 系统通信的关键分区,结构定义:
c
// bootable/recovery/bootloader_message/include/bootloader_message/bootloader_message.h
struct bootloader_message {
char command[32]; // "boot-recovery" / "bootonce-bootloader"
char status[32];
char recovery[768]; // recovery 命令
char stage[32]; // 多阶段恢复
char reserved[1184];
};
写入 misc 来触发 recovery:
c
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
int set_bootloader_message(const char *cmd) {
struct bootloader_message bcb = {0};
strncpy(bcb.command, cmd, sizeof(bcb.command)-1);
int fd = open("/dev/block/by-name/misc", O_WRONLY);
if (fd < 0) return -1;
ssize_t n = write(fd, &bcb, sizeof(bcb));
fsync(fd);
close(fd);
return (n == sizeof(bcb)) ? 0 : -1;
}
int main(void) {
return set_bootloader_message("boot-recovery");
}
4. vbmeta 分区 --- AVB 元数据
vbmeta 存放 Android Verified Boot 的签名链,验证 boot/system/vendor 等分区是否被篡改。
vbmeta 结构:
+------------------------+
| AvbVBMetaImageHeader | 256 字节
+------------------------+
| Authentication Data | 公钥签名 (RSA / Ed25519)
+------------------------+
| Auxiliary Data | 含 hash descriptor / chain partition / property
+------------------------+
每个被保护的分区(如 boot)的 hash descriptor 描述了它的预期 sha256 值,Bootloader 在启动时会逐一校验。
5. metadata 分区
存放 File-Based Encryption (FBE) 的加密元数据,在 userdata 解密前就要被访问。
五、读取并打印分区表(C 实现)
下面是一个简单的 C 程序,从块设备读取 GPT 并打印所有分区:
c
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#define LBA_SIZE 4096 // UFS 通常 4K,eMMC 512B
struct gpt_header {
uint8_t signature[8];
uint32_t revision;
uint32_t header_size;
uint32_t header_crc32;
uint32_t reserved;
uint64_t current_lba;
uint64_t backup_lba;
uint64_t first_usable_lba;
uint64_t last_usable_lba;
uint8_t disk_guid[16];
uint64_t partition_entry_lba;
uint32_t num_partition_entries;
uint32_t partition_entry_size;
uint32_t partition_entries_crc32;
} __attribute__((packed));
struct gpt_entry {
uint8_t partition_type_guid[16];
uint8_t unique_partition_guid[16];
uint64_t starting_lba;
uint64_t ending_lba;
uint64_t attributes;
uint16_t partition_name[36];
} __attribute__((packed));
static void utf16_to_ascii(const uint16_t *u16, char *out, size_t n) {
for (size_t i = 0; i < n; i++) {
out[i] = (u16[i] < 128) ? (char)u16[i] : '?';
if (u16[i] == 0) break;
}
out[n-1] = 0;
}
int main(int argc, char *argv[]) {
if (argc < 2) {
fprintf(stderr, "Usage: %s /dev/block/sdaX\n", argv[0]);
return 1;
}
int fd = open(argv[1], O_RDONLY);
if (fd < 0) { perror("open"); return 1; }
// 跳过 protective MBR,读 GPT header
struct gpt_header hdr;
lseek(fd, LBA_SIZE * 1, SEEK_SET);
if (read(fd, &hdr, sizeof(hdr)) != sizeof(hdr)) {
perror("read header");
return 1;
}
if (memcmp(hdr.signature, "EFI PART", 8) != 0) {
fprintf(stderr, "Not a GPT disk\n");
return 1;
}
printf("GPT found: %u partition slots, entry size %u\n",
hdr.num_partition_entries, hdr.partition_entry_size);
// 读取所有 partition entry
size_t total = (size_t)hdr.num_partition_entries * hdr.partition_entry_size;
uint8_t *buf = malloc(total);
lseek(fd, LBA_SIZE * hdr.partition_entry_lba, SEEK_SET);
if (read(fd, buf, total) != (ssize_t)total) {
perror("read entries");
return 1;
}
printf("\n%-3s %-20s %14s %14s\n", "No.", "Name", "Start (KB)", "Size (KB)");
for (uint32_t i = 0; i < hdr.num_partition_entries; i++) {
struct gpt_entry *e = (struct gpt_entry *)(buf + i * hdr.partition_entry_size);
// 全 0 表示未使用
int empty = 1;
for (int j = 0; j < 16; j++) if (e->partition_type_guid[j]) { empty = 0; break; }
if (empty) continue;
char name[40];
utf16_to_ascii(e->partition_name, name, sizeof(name));
uint64_t size = (e->ending_lba - e->starting_lba + 1) * LBA_SIZE / 1024;
uint64_t start = e->starting_lba * LBA_SIZE / 1024;
printf("%-3u %-20s %14lu %14lu\n", i, name, start, size);
}
free(buf);
close(fd);
return 0;
}
编译并运行(需要 root):
bash
aarch64-linux-android-gcc gpt_dump.c -o gpt_dump
adb push gpt_dump /data/local/tmp/
adb shell su -c "/data/local/tmp/gpt_dump /dev/block/sda"
六、Android 上查看分区的实用命令
bash
# 列出所有 by-name 分区(最常用)
adb shell ls -l /dev/block/by-name/
# 查看分区大小
adb shell cat /proc/partitions
# 备份某个分区
adb shell su -c "dd if=/dev/block/by-name/boot of=/sdcard/boot.img bs=4M"
# 通过 lpdump 看 super 分区内部
adb shell su -c "lpdump"
# 查看分区表(需要 root + gdisk/sgdisk)
adb shell su -c "sgdisk -p /dev/block/sda"
七、常见的踩坑总结
- A/B 设备没有 recovery 分区 :
recovery.img被合并进boot.img的 ramdisk 中,通过androidboot.force_normal_boot=1或 BCB 切换 - dtbo 必须和 kernel 匹配:Device Tree 不匹配的话设备会反复重启
- vbmeta 不能随便擦 :擦了之后 AVB 会报错,需要
fastboot --disable-verity --disable-verification flash vbmeta - super 分区刷写要走 fastbootd :
fastboot reboot fastboot进入 userspace fastboot 才能刷 system / vendor
八、总结
掌握分区表是 Android 底层工作的基本功:
- GPT 是现代设备的标准分区格式,理解 header 和 entry 结构能让你直接读出分区表
- A/B 设备的双槽分区让 OTA 升级可以无缝完成
- Dynamic Partition (super) 让分区大小动态可变
- misc / vbmeta / metadata 等小分区虽然不起眼,但承载关键功能
后续会专门讲 A/B 槽位切换和 AVB 验证,欢迎追更。