深入解析MTK平台scatter.txt分区表:从ptgen工具链到实战定制

发布时间:2026/8/1 4:39:59
深入解析MTK平台scatter.txt分区表:从ptgen工具链到实战定制 1. 项目概述从零理解MTK分区表的生成脉络在MTK联发科平台的Android设备开发中scatter.txt文件是一个绕不开的核心配置文件。无论是进行固件烧录、系统升级还是深度定制分区布局都离不开它。很多刚接触MTK平台的工程师往往直接从SDK里拿到一个现成的scatter.txt文件就开始用对其背后的生成逻辑和细节调整却知之甚少。这就好比开车只会用自动挡一旦遇到复杂路况需要手动干预就束手无策。今天我就结合自己多年在MTK平台“摸爬滚打”的经验深入拆解scatter.txt的生成过程让你不仅知其然更能知其所以然真正掌握分区表配置的主动权。简单来说scatter.txt文件定义了设备上各个分区如boot、system、userdata等在闪存通常是eMMC或UFS中的物理位置、大小和属性。MTK提供了一套名为ptgenPartition Table Generator的工具链用于根据项目需求自动生成这个文件。这个过程并非简单的文本拼接而是涉及硬件配置、产品定义、内存布局规划等一系列复杂决策的自动化体现。理解这个过程对于解决“分区空间不足”、“升级失败”、“无法烧录”等常见问题至关重要。2. scatter.txt文件结构与核心字段解析在深入生成过程之前我们必须先彻底读懂scatter.txt文件本身。一个典型的MTK平台scatter.txt文件内容看起来可能有些复杂但结构非常清晰。2.1 文件头与全局配置文件开头通常是几行注释和全局配置。最重要的是general部分它定义了整个配置表的版本和工具信息。例如############################################################################################################ # # General Setting # ############################################################################################################ - general: MTK_PLATFORM_CFG info: - config_version: V1.1.2 - platform: MT6789 - project: k69v1_64 - storage: EMMC - boot_channel: MSDC_0 - block_size: 0x20000这里的block_size: 0x20000即128KB是eMMC闪存的擦除块大小是后续所有分区起始地址对齐的基准。storage: EMMC指明了存储介质类型如果是UFS设备这里会不同其块大小和分区属性也会有差异。这个全局信息是后续所有分区定义的基石。2.2 分区条目详解文件的主体是一个个分区定义。每个分区条目都包含一组关键的属性字段我以一个boot分区为例进行拆解- partition_index: SYS0 partition_name: boot file_name: boot.img is_download: true type: NORMAL_ROM linear_start_addr: 0x000000000 physical_start_addr: 0x000000000 partition_size: 0x00600000 region: EMMC_USER storage: HW_STORAGE_EMMC boundary_check: true is_reserved: false operation_type: UPDATE reserve: 0x00这些字段每一个都至关重要partition_name 分区的逻辑名称如boot,recovery,system等与编译输出的镜像文件名通常对应。file_name 烧录时使用的实际镜像文件名。boot.img就是Android标准的启动镜像。is_download 该分区是否需要通过下载工具如SP Flash Tool烧录。对于system、vendor等大型分区通常为true而像protect1、protect2这类存放安全密钥、设备信息的隐藏分区通常会设为false禁止随意擦写。linear_start_addr与physical_start_addr 在绝大多数eMMC配置中这两个地址是相同的代表分区在闪存线性地址空间中的起始偏移。地址必须按block_size对齐否则会导致烧录失败。0x000000000表示从闪存用户数据区的绝对起始位置开始。partition_size 分区的大小。0x00600000即6MB这是boot分区的典型大小需要能容纳内核kernel、设备树dtb、初始内存磁盘ramdisk等所有内容。region 指定分区所属的闪存区域。EMMC_USER是最常见的用户数据区。对于带有RPMBReplay Protected Memory Block或引导分区boot1/boot2的设备这里会有不同的值。boundary_check 是否进行边界检查。强烈建议始终设为true工具会在烧录前检查分区是否重叠或超出存储边界这是防止“砖机”的重要安全阀。operation_type 操作类型。UPDATE表示该分区可通过OTA升级。有些关键分区如preloader可能会设为INVISIBLE或PROTECTED。注意 在手动修改或调试scatter.txt时最常出错的点就是地址对齐和分区重叠。务必确保每个分区的linear_start_addr是block_size通常是0x20000的整数倍并且一个分区的结束地址start_addr size必须等于下一个分区的开始地址中间不能有间隙也不能重叠。一个快速检查的方法是使用Python或Excel简单计算一下。3. 生成工具链ptgen的工作流程与输入文件MTK使用一套名为ptgen的Perl脚本工具链来生成scatter.txt。它的核心思想是“配置驱动生成”。我们不需要直接编写复杂的scatter.txt而是通过修改几个结构更清晰的输入文件然后让工具自动合成最终文件。这套工具链通常位于SDK的device/mediatek/build/build/tools/ptgen目录下。3.1 核心输入文件MTxxxx_Android_scatter.txt.emm这是生成过程的蓝图文件。注意它的后缀是.emm对于eMMC或.ufs对于UFS它是一个模板文件。在这个文件中分区的定义使用了更抽象的变量和条件判断。例如你可能会看到这样的片段%if $PART_TABLE_FORCE_NEXT_ALIGN eq “yes”% partition_size: %$SIZE_OF_BOOTIMG% %else% partition_size: %$BOOTIMG_SIZE% %endif%以及%if $EMMC_SUPPORT eq “yes”% region: EMMC_USER %elsif $UFS_SUPPORT eq “yes”% region: UFS_LU0 %endif%这个模板文件本身并不直接使用而是由ptgen脚本读取并结合其他配置文件中的具体变量值如$SIZE_OF_BOOTIMG、$EMMC_SUPPORT渲染生成最终的、所有变量都被替换为具体值的scatter.txt。3.2 项目配置文件ProjectConfig.mk这是产品定义的灵魂位于device/[vendor]/[project]/ProjectConfig.mk。在这里硬件工程师和系统架构师会定义与分区相关的关键宏。这些宏的值会作为变量传递给ptgen工具。相关的配置项非常多我列举几个最核心的# 存储类型 MTK_EMMC_SUPPORTyes # MTK_UFS_SUPPORTyes # 闪存总大小 (单位: MB) MTK_EMMC_SIZE0xE8000000 # 3.6GB # 关键分区大小定义 BOOTIMG_SIZE6M SYSTEM_IMAGE_SIZE0x60000000 # 1.5GB USERDATA_IMAGE_SIZE0x100000000 # 4GB # 是否启用OTA升级 MTK_OTA_SUPPORTyes # OTA升级时使用的非活动系统分区大小 MTK_AB_OTA_UPDATERno MTK_OTA_ROM_SIZE0x60000000 # 是否启用安全启动相关分区 MTK_SECURITY_SW_SUPPORTyes MTK_SEC_FASTBOOT_UNLOCK_SUPPORTyesptgen脚本会解析这个mk文件提取出所有这些以MTK_或特定前缀开头的配置项将它们转化为模板引擎可用的变量。例如BOOTIMG_SIZE6M会被处理并可能在模板中用于计算十六进制的分区大小0x600000。3.3 内存布局定义文件除了大小分区的布局顺序也至关重要。哪些分区在前哪些在后是由一个内存布局定义文件决定的。这个文件可能是一个独立的文本文件如partition_layout_xxx.csv也可能直接内嵌在ptgen的Perl代码逻辑中。它定义了一个有序的分区列表例如preloader, pgpt, proinfo, nvram, protect1, protect2, ... boot, recovery, secro, ... system, vendor, product, userdata, cache, ... , sgpt这个顺序决定了最终scatter.txt中分区条目的排列顺序也直接决定了每个分区的起始地址是如何累加计算的。ptgen会按照这个列表根据每个分区定义的大小依次计算它们的起始和结束地址。3.4 生成过程的串联整个生成过程通常在编译系统的某个阶段被触发例如在执行source build/envsetup.sh和lunch选择了项目之后编译系统会准备环境变量。当执行make命令时构建系统会读取ProjectConfig.mk生成一个包含所有配置变量的中间环境。调用ptgenPerl脚本并将配置变量传递给它。ptgen脚本读取内存布局定义确定分区种类和顺序。ptgen根据存储类型eMMC/UFS选择对应的模板文件.emm或.ufs。脚本遍历内存布局中的每个分区名从配置变量中查找该分区的大小例如boot分区找BOOTIMG_SIZE。如果找不到明确配置则使用该分区类型的默认大小。根据当前累计的地址和分区大小计算该分区的linear_start_addr。将分区名、计算出的地址、大小以及其他属性从模板逻辑和配置变量中获取填充到模板对应的位置。渲染整个模板将所有%$VAR%替换为实际值生成最终的scatter.txt文件通常输出在out/target/product/[project]/目录下。4. 自定义分区从需求到实现的实战调整理解了自动生成流程我们就能在需要时进行精准干预。最常见的需求就是调整分区大小或新增自定义分区。4.1 调整现有分区大小假设产品需求变更system分区需要从默认的1.5GB扩大到2GB以容纳更多的预装应用。修改ProjectConfig.mk 找到SYSTEM_IMAGE_SIZE这一行将其值修改为0x800000002GB的十六进制表示。计算方法是2GB 2 * 1024 * 1024 * 1024 2147483648字节 0x80000000。SYSTEM_IMAGE_SIZE0x80000000考虑连锁反应system分区之后通常是vendor、userdata等分区。system分区扩大后其结束地址会后移这会挤压后续所有分区的空间。你必须确保后续分区有足够的空间可以向后“推移”。最终所有分区的总大小不能超过MTK_EMMC_SIZE定义的闪存总容量。通常userdata分区是最后一个大分区且大小可能被设置为“剩余所有空间”。在这种情况下扩大system会自动压缩userdata的空间。你需要评估userdata剩余空间是否仍能满足需求。重新生成 执行一次完整的编译或至少触发scatter.txt重新生成的目标如make scatterimage检查新生成的scatter.txt中system分区大小是否已变为0x80000000并核对后续分区的起始地址是否正确更新。4.2 新增一个自定义分区假设我们需要增加一个名为oem的分区用于存放客户定制的一些只读数据大小为128MB。确定分区位置 这是最关键的一步。你需要决定把oem分区放在哪里。常见的做法是放在system和userdata之间或者放在vendor之后。假设我们决定放在vendor和userdata之间。修改内存布局定义 找到定义分区顺序的文件可能是ptgen目录下的一个Perl数组或CSV文件在vendor和userdata之间插入oem。... vendor, product, oem, userdata, cache ...在ProjectConfig.mk中定义大小 添加一行配置。OEM_IMAGE_SIZE0x8000000 # 128MB在模板文件中添加分区定义 找到MTxxxx_Android_scatter.txt.emm模板文件在合适的位置通常在其他分区定义块附近添加oem分区的模板块。你需要参考其他分区的格式定义其partition_name,file_name,type,region,operation_type等属性。例如可以模仿product分区的定义。%if $NEED_OEM_PARTITION eq yes% - partition_index: SYSxx # 分配一个未使用的索引号 partition_name: oem file_name: oem.img is_download: true type: NORMAL_ROM linear_start_addr: %$OEM_PARTITION_START% # ptgen会自动计算这个地址 physical_start_addr: %$OEM_PARTITION_START% partition_size: %$OEM_IMAGE_SIZE% region: EMMC_USER storage: HW_STORAGE_EMMC boundary_check: true is_reserved: false operation_type: INVISIBLE # 或 PROTECTED根据需求定 reserve: 0x00 %endif%注意这里用了一个条件判断$NEED_OEM_PARTITION你需要在ProjectConfig.mk中也定义这个变量来控制是否生成此分区。让ptgen识别新分区 可能需要修改ptgen的Perl脚本让它知道在计算地址时遇到oem分区时去读取OEM_IMAGE_SIZE这个变量并将其纳入地址累计计算。这一步的难度因SDK版本和ptgen实现而异有时只需要在布局文件中添加名字即可工具会自动查找同名_SIZE变量。生成与验证 重新生成scatter.txt检查oem分区是否出现其起始地址、大小是否正确且没有引起其他分区地址错误或重叠。实操心得 新增分区是高风险操作务必在编译后仔细核对生成的scatter.txt。一个有效的验证方法是写一个简单的脚本读取scatter.txt按顺序打印每个分区的名称、起始地址、结束地址和大小人工检查地址是否连续且无重叠。首次尝试时强烈建议在一个虚拟的或备份的设备上进行烧录测试避免真机变砖。5. 高级议题AB分区、动态分区与安全启动随着Android系统演进分区方案也变得更加复杂ptgen也需要处理这些新特性。5.1 AB系统分区A/B Seamless Update为了支持无缝OTA更新Android引入了A/B分区方案。这意味着会有两套boot、system、vendor等分区分别标记为slot_a和slot_b。在scatter.txt中这会体现为分区名的后缀。配置 在ProjectConfig.mk中需要设置MTK_AB_OTA_SUPPORTyes。生成逻辑ptgen在生成时会对指定列表中的分区如boot,system,vendor创建两份。它们的partition_name会变为boot_a/boot_bfile_name可能对应boot.img当前活动槽和boot_other.img。两套分区的大小必须完全一致且紧密排列。这要求ptgen在计算地址时进行双倍的空间分配和更复杂的布局规划。5.2 动态分区Dynamic PartitionsAndroid 10以后引入了动态分区特别是将system、vendor、product等只读分区合并到一个super分区中在设备启动时动态创建逻辑分区。这彻底改变了分区表的静态特性。对scatter.txt的影响 在启用动态分区后scatter.txt中不再有独立的system、vendor等分区条目取而代之的是一个巨大的super分区。scatter.txt的定义变得相对简单但额外的super分区镜像super.img内部包含了一个名为super分区表的元数据描述了system、vendor等逻辑分区在super内部的布局。ptgen的适配 此时ptgen的角色有所变化。它仍然生成基础的scatter.txt主要包含boot、dtbo、vbmeta、super等。而super分区内部逻辑分区的大小和布局则由另一个配置文件如dynamic_partitions_list.txt和lpmake工具在编译super.img时决定。ptgen需要与这套新机制协同工作。5.3 安全启动相关分区MTK平台的安全启动Secure Boot会引入一系列受保护的分区例如proinfo 存放设备出厂信息如IMEI、SN等。protect1/protect2 存放安全相关的密钥、设备锁状态等。secro 存放只读的安全相关数据。vbmeta 用于验证启动镜像的完整性Android Verified Boot。在scatter.txt中这些分区的is_download属性通常为falseoperation_type可能是PROTECTED或INVISIBLE以防止通过常规下载工具被擦写。ptgen在生成时会根据MTK_SECURITY_SW_SUPPORT等配置开关决定是否包含以及如何配置这些分区。它们的起始地址通常紧挨着pgpt主GPT头之后处于闪存最前端受保护的区域内。6. 常见问题排查与调试技巧实录在实际开发和维护中遇到scatter.txt相关的问题在所难免。下面是我总结的一些典型问题及其排查思路。6.1 烧录失败“S_BROM_CMD_STARTCMD_FAIL”这是SP Flash Tool烧录时最常见的错误之一通常与分区表有关。可能原因1scatter.txt与设备不匹配。你使用的scatter.txt文件不是针对当前设备型号和闪存容量生成的。解决 务必使用与设备完全对应的原厂或自己编译生成的scatter.txt。可能原因2地址不对齐。某个分区的linear_start_addr不是block_size如0x20000的整数倍。解决 用计算器检查scatter.txt中每个分区的起始地址。可以使用这个Python脚本快速验证block_size 0x20000 with open(scatter.txt, r) as f: lines f.readlines() for i, line in enumerate(lines): if linear_start_addr: in line: addr_str line.split(:)[1].strip() addr int(addr_str, 16) if addr % block_size ! 0: print(fLine {i1}: Address {addr_str} is not aligned to {hex(block_size)}!)可能原因3分区重叠或间隙。一个分区的结束地址超过了下一个分区的开始地址或者两者之间有空隙虽然空隙不一定导致失败但浪费空间。解决 编写或使用工具脚本按顺序计算并打印每个分区的结束地址与下一个分区的起始地址对比。6.2 系统升级失败OTA包验证错误或空间不足可能原因1分区大小不符。OTA升级包在安装时会检查目标分区的大小是否足够。如果设备上的system分区小于OTA包中system镜像的预期大小升级会失败。解决 检查设备当前scatter.txt中system分区的大小并与编译OTA包时使用的system镜像大小对比。确保设备分区表版本与OTA包匹配。可能原因2动态分区大小未更新。对于动态分区即使scatter.txt中的super分区大小足够但super内部的逻辑分区如system大小定义在dynamic_partitions_list.txt中可能不足。解决 需要更新设备上的super分区表元数据这通常包含在OTA包中但需要确保刷写过程正确。6.3 编译错误ptgen执行失败可能原因1ProjectConfig.mk中存在语法错误或未定义的变量引用。例如在模板中引用了$CUSTOM_SIZE但ProjectConfig.mk中没有定义CUSTOM_SIZE。解决 仔细检查ptgen工具输出的错误信息它会指出在哪一行遇到了未定义的变量。去ProjectConfig.mk中补全定义或检查变量名拼写。可能原因2分区布局文件中的分区名在模板中找不到定义。解决 确保内存布局文件中列出的每一个分区名在模板文件.emm中都有对应的定义块。如果新增了分区必须在模板中添加。6.4 调试技巧手动验证与修改技巧1使用十六进制编辑器查看闪存头部。通过读取设备闪存最开始的几个扇区可以查看实际的GPT分区表并与scatter.txt进行比对。这能最直接地确认分区表是否已正确烧录。但此操作有风险需谨慎。技巧2反推计算。当你拿到一个现成的scatter.txt但不确定其布局是否合理时可以手动从第一个分区通常是preloader开始将其分区大小累加起来看总和是否接近闪存总容量MTK_EMMC_SIZE。这能帮你快速理解整体布局。技巧3制作对比工具。写一个简单的脚本比较两个不同版本或不同配置生成的scatter.txt文件高亮显示有差异的分区大小和地址。这在追踪分区布局变更时非常有用。理解scatter.txt的生成过程本质上是在理解MTK平台设备存储空间的规划逻辑。它连接了硬件规格闪存大小、产品需求分区大小、系统特性A/B、动态分区和烧录升级工具。掌握了这套流程你就拥有了在系统层面进行深度定制的钥匙无论是解决棘手的空间不足问题还是实现特殊的产品功能需求都能做到心中有数手中有策。