3、动态分区布局:LPDDR、super分区内部结构、分区表解析

好,咱们接着聊动态分区。上一章我们把动态分区的整体架构讲清楚了,这一章要深入到底层——LPDDR、super 分区的内部结构,以及分区表到底是怎么解析的。这三个东西,说白了就是动态分区能跑起来的「地基」。

我个人习惯把动态分区比作一栋楼:super 分区是整栋楼的框架,LPDDR 是楼里的承重墙,分区表就是每层楼的户型图。你想想看,没有户型图,你连哪个房间是厨房、哪个是卧室都搞不清楚,更别提装修了。

3.1 LPDDR 是什么?为什么它这么重要?

LPDDR,全称是 Logical Partition Descriptor,逻辑分区描述符。嗯,这个名字有点绕,我换个说法——它就是动态分区的「身份证」。

每个动态分区在 super 分区里都有一个 LPDDR。这个 LPDDR 记录了分区的名字、大小、属性、在 super 里的偏移量等等。没有 LPDDR,系统根本不知道 super 分区里有哪些逻辑分区。

核心要点:LPDDR 是动态分区和传统分区最大的区别之一。传统分区表(比如 GPT)是写在磁盘头部的,而 LPDDR 是写在 super 分区内部的 metadata 区域里。

我在项目中遇到过一个问题:某款手机 OTA 升级后,系统死活起不来。查了半天,发现是 LPDDR 里的分区大小字段被写错了,导致系统在挂载 system 分区时读取了错误的数据。从那以后,我每次做 OTA 都会额外校验一遍 LPDDR 的 CRC。

3.2 Super 分区的内部结构

super 分区不是一个普通的分区。它内部还嵌套了多个逻辑分区,比如 system、product、vendor 等等。它的结构大致是这样的:

Super 分区布局(简化版):
+------------------------------------------+
|  Metadata 区域(LPDDR 列表 + 头部信息)    |
+------------------------------------------+
|  system 逻辑分区数据                       |
+------------------------------------------+
|  product 逻辑分区数据                      |
+------------------------------------------+
|  vendor 逻辑分区数据                       |
+------------------------------------------+
|  ... 其他逻辑分区                          |
+------------------------------------------+

你可能会问:为什么要把 metadata 放在 super 分区内部,而不是像 GPT 那样放在磁盘开头?

原因其实很简单:灵活性。GPT 的分区表是固定的,一旦写好就很难改。而动态分区的 metadata 可以随时更新——比如 OTA 升级时,我们可以调整分区大小、新增或删除分区,只需要更新 metadata 里的 LPDDR 列表就行了。

小技巧:在调试动态分区问题时,可以用 lpdump 工具查看 super 分区里的 metadata 内容。命令是 lpdump /dev/block/by-name/super,它会打印出所有 LPDDR 的详细信息。

3.3 分区表解析:从 LPDDR 到挂载点

分区表解析,说白了就是系统怎么从 super 分区里找到每个逻辑分区的数据。这个过程大致分三步:

  1. 读取 metadata:系统启动时,会先读取 super 分区头部的 metadata 区域,拿到 LPDDR 列表。
  2. 解析 LPDDR:每个 LPDDR 里包含了分区的名称、大小、偏移量、属性等信息。系统根据这些信息,计算出每个逻辑分区在 super 分区里的实际位置。
  3. 创建设备节点:系统在 /dev/block/by-name/ 下创建对应的设备节点,比如 /dev/block/by-name/system,然后挂载到对应的挂载点。

嗯,这里要注意:LPDDR 里的偏移量是相对于 super 分区起始位置的,不是绝对地址。所以解析时,系统需要先知道 super 分区本身的起始地址。

3.4 LPDDR 的数据结构

LPDDR 的数据结构在 AOSP 源码里有定义,我贴一段关键代码:

// 来自 system/core/fs_mgr/liblp/include/liblp/liblp.h
struct LpMetadataPartition {
    // 分区名称,比如 "system"
    char name[36];
    // 分区属性,比如可读写、可升级等
    uint32_t attributes;
    // 分区在 super 里的起始偏移量(以扇区为单位)
    uint64_t first_extent;
    // 分区大小(以扇区为单位)
    uint64_t num_sectors;
    // 分区组名,比如 "default"
    char group_name[36];
};

你看,这个结构体很简洁。但实际项目中,LPDDR 还有更多字段,比如 extent 列表——因为一个逻辑分区可能不是连续的,它可能被拆成多个 extent 分布在 super 分区里。

避坑指南:我曾经遇到过一个问题:某个逻辑分区被拆成了 3 个 extent,但 OTA 脚本只更新了第一个 extent 的偏移量,导致系统在读取后续数据时读到了错误的位置。所以,如果你要手动修改 LPDDR,一定要确保所有 extent 都正确更新。

3.5 动态分区的 metadata 版本管理

动态分区的 metadata 是有版本号的。每次 OTA 升级,metadata 的版本号都会递增。这样做的好处是:如果升级失败,系统可以回滚到旧版本的 metadata,保证系统还能启动。

我记得有一次,某款设备在 OTA 升级时断电了,导致 metadata 写了一半。重启后,系统检测到 metadata 的 CRC 校验失败,自动回滚到了上一个版本,设备成功启动。嗯,这就是版本管理的好处。

3.6 一张图看懂动态分区布局

说了这么多,不如一张图来得直观。下面是我画的动态分区布局图,涵盖了 LPDDR、super 分区和分区表解析的核心逻辑:

动态分区布局与分区表解析 Super 分区 Metadata 区域 LPDDR 列表 + 头部信息 system 逻辑分区 product 逻辑分区 vendor 逻辑分区 其他逻辑分区... LPDDR 解析流程 步骤 1:读取 Metadata 从 super 分区头部读取 LPDDR 列表 步骤 2:解析 LPDDR 获取分区名称、偏移量、大小 步骤 3:创建设备节点 在 /dev/block/by-name/ 下创建 步骤 4:挂载分区 挂载到 /system、/product 等 读取

这张图左边是 super 分区的物理布局,右边是 LPDDR 的解析流程。你可以看到,系统从 super 分区读取 metadata,解析出每个逻辑分区的信息,然后创建对应的设备节点并挂载。

3.7 分区表解析的代码示例

最后,我贴一段实际解析 LPDDR 的代码片段。这是我在调试时常用的工具函数:

// 解析 LPDDR 并打印分区信息
void dump_lpddr(const char* super_path) {
    // 打开 super 分区
    int fd = open(super_path, O_RDONLY);
    if (fd < 0) {
        printf("无法打开 super 分区: %s\n", strerror(errno));
        return;
    }
    
    // 读取 metadata(简化版,实际需要解析头部)
    LpMetadataHeader header;
    read(fd, &header, sizeof(header));
    
    // 读取 LPDDR 列表
    LpMetadataPartition partitions[32];
    lseek(fd, header.partitions_offset, SEEK_SET);
    read(fd, partitions, header.partitions_count * sizeof(LpMetadataPartition));
    
    // 打印每个分区
    for (int i = 0; i < header.partitions_count; i++) {
        printf("分区 %d: %s\n", i, partitions[i].name);
        printf("  偏移量: %llu 扇区\n", partitions[i].first_extent);
        printf("  大小: %llu 扇区\n", partitions[i].num_sectors);
        printf("  属性: 0x%x\n", partitions[i].attributes);
    }
    
    close(fd);
}

这段代码虽然简化了,但核心逻辑都在。实际项目中,你还需要处理 extent 列表、CRC 校验、版本兼容等问题。不过,理解了上面的内容,动态分区的布局和解析对你来说应该不再是难题了。

总结一下:LPDDR 是动态分区的身份证,super 分区是逻辑分区的容器,分区表解析就是系统从 super 分区里找到每个逻辑分区的过程。这三者缺一不可,共同构成了动态分区的基础设施。


公众号:蓝海资料掘金营,微信deep3321