ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

MTK Android启动流程深度解析:从Pre-loader到Kernel全链路

MTK Android启动流程深度解析:从Pre-loader到Kernel全链路 1. 链路全景概览一次开机背后发生了什么做MTK平台开发这些年被问得最多的一件事就是手机按了电源键之后到Android桌面亮起来中间到底跑了些什么很多人手里有代码也知道有Pre-loader、LK、Kernel这些概念但真要让他们把完整链路讲清楚能讲明白的人其实不多。我写这篇东西就是想把MTK Bootable启动流程从Pre-loader到Kernel这一段彻底拆开把每一段代码在干什么、为什么这么设计、调试时去哪里看一次说透。MTK的启动链路用一句话概括就是芯片内部固化的Boot ROM先跑从存储里把Pre-loader拉起来Pre-loader初始化内存和存储再把LKLittle Kernel或U-Boot加载进来LK负责把boot分区里的Kernel和Ramdisk搬到内存里最后跳进Linux Kernel。Kernel起来之后挂载rootfs启动init进程Android用户态才算正式接管。这个链路跟高通平台最大的区别在于MTK把所有启动阶段都做得非常“整包化”。Pre-loader、LK、Boot Image、甚至vendor.img、system.img每个都是独立打包、独立签名、独立校验的状态。所以你在调试MTK平台的时候经常遇到的不是“一个地方坏了”而是“校验链路上有东西对不上”。这也是为什么很多人第一次接触MTK启动流程会头大因为光搞懂各个镜像之间的依赖关系就需要一些耐心。写这篇文章之前我把最近网上搜到的一些热词也过了一遍比如“MTK按键进入拍照”“MTK与高通的区别”“mtk端口驱动安装”这些。会发现大部分人是卡在实操层面——要么是刷机进不了BROM模式要么是LK阶段log没有输出要么是Kernel起不来报错看不懂。所以我后面会重点讲调试方法和坑位排查纯理论的部分控制在“够用”的粒度不无限展开。1.1 从任意平台看MTK启动链路的特殊之处MTK Bootable流程虽然整体也是标准的ARM Boot Chain——ROM code - Bootloader 1 - Bootloader 2 - Kernel但它的具体实现里加了很多自己的东西。首先是“Download Agent”机制也就是我们常说的DA。Boot ROM里的代码非常小它本身不具备把完整系统拉起来的能力但它有一个USB/UART下载通道可以让PC端工具通过DA模式把Pre-loader、LK等镜像直接写进设备存储里。这就是SP Flash Tool能工作的底层基础。其次是MTK的Boot Image头部跟AOSP标准boot.img头部不兼容。AOSP标准的boot img header是magic “ANDROID!”后面跟着kernel size、ramdisk size这些通用字段。MTK在这个基础上加了一堆自定义字段比如platform、chip version、security相关标记等。如果你直接用标准的mkbootimg工具去打包MTK的boot镜像刷进去大概率起不来要么校验报错要么Kernel直接没被正确加载。再有就是安全启动链路。MTK从很早就默认开启Secure BootPre-loader本身是带签名的LK也带签名boot镜像同样要校验。很多开发者刚拿到一块非量产板子想直接fastboot刷个自己编译的boot.img结果被“signature verify fail”卡住这就是安全校验在起作用。不是说不能关而是你得先搞懂它的开关在哪里、需要什么工具才能处理。1.2 完整链路图与各阶段职责划分完整链路大致可以分成六个阶段我用文字描述一下不画图但顺序是严格的Boot ROMBRom芯片出厂固化的只读代码第一条指令从这里开始初始化极简外设然后尝试从eMMC/UFS/NOR等存储介质读取Pre-loader。Pre-loaderMTK的第一阶段引导程序职责是初始化DRAM、部分时钟、存储控制器并把LK镜像从指定分区加载到内存最后跳转执行。LKLittle Kernel第二阶段引导程序负责初始化显示、触摸、按键、USB、eMMC等设备建立内存布局加载boot分区里的Kernel和Ramdisk。Kernel解压并初始化整个系统内核挂载rootfs启动第一个用户态进程。init进程Android世界的起点启动Zygote、SystemServer等。Android用户态我们看到的SystemUI、桌面、App进程。每个阶段向下一个阶段交接的时候靠的是一份约定的内存布局和跳转地址。Pre-loader把LK放到DRAM的某个固定地址LK把Kernel放到另一个固定地址这些地址在不同芯片平台上会有些差异但整体设计思想是一致的像接力赛一样上一棒把下一棒送到指定位置然后转身让路。这里有一个MTK特有的点Pre-loader和LK在加载时是“裸”ELF格式的不是压缩包。不像U-Boot有时会压缩MTK这两个引导程序直接以ELF或扁平二进制加载所以它们本身启动速度相对快。调试的时候你看到Pre-loader的log比较多因为它要完成的东西远比想象中多。2. 源头与前置Boot ROM 和 Pre-loader 的设计功课2.1 Boot ROM 到底做了什么Boot ROM是从芯片厂角度最容易被忽略但又最关键的一段。它固化在芯片内部用户改不了也不需要编译我们拿到的包里面一般只有它的行为描述没有源码。MTK的Boot ROM在芯片上电后首先会做基础时钟初始化和内部SRAM的启用然后选定启动介质。关键点在于启动介质的选择逻辑。MTK的Boot ROM有一套启动模式判断机制一般会先看是否有USB插入信号如果有可能进入Download模式等待PC端发DA。如果没有USB信号就从boot pins或eFuse里读取配置决定从eMMC还是UFS还是NOR Flash启动。这个机制也是很多人插上USB后设备直接被识别成“MTK USB Port”的原因其实芯片已经进入BROM下载模式了。对于开发板调试来说BROM模式太重要了。我见过不少人在量产机上刷砖后不知道怎么救就是因为他们没意识到只要Boot ROM还没被冲掉永远可以通过BROM下载模式把整个系统写回去。Pre-loader、LK、boot、system这些分区都可以在BROM模式下重新烧写只是需要对应的DA文件和合适的工具。2.2 Pre-loader 的加载与执行细节Pre-loader是第一个可以被开发者替换和编译的启动代码。它的主目录在vendor/mediatek/proprietary/bootable/bootloader/preloader/下不同平台分支会有所差异但整体结构类似。Pre-loader做完的事情简单来说是“把内存搞起来把LK搬进来”。从Boot ROM跳进Pre-loader之后Pre-loader首先要完成DRAM初始化。这一步非常吃硬件经验因为DRAM的初始化参数跟板子的布线、颗粒型号、频率都有关系。很多非量产的板卡跑到这里就卡死往往就是DRAM参数不对。之后Pre-loader会建立MMU映射设定栈打开cache然后才轮到外设。接下来是从存储分区加载LK。MTK的Pre-loader通过一套简单的块设备读取接口从预定义的分区表位置读入LK镜像放到约定好的DRAM地址。这里MTK引入了一个“device”概念Pre-loader会去解析设备节点获取boot device类型、芯片版本等信息决定走哪条加载路径。如果加载失败Pre-loader会回落到BROM下载模式这也是我们常说的“挂了就进刷机模式”的原理。Pre-loader在调试时最常见的输出是在串口上打印一串带版本的log开头一般是“[PreLoader]”或者类似标记。如果卡住不往前走第一件事要确认的是DRAM初始化有没有过、存储能不能正常读。2.3 设备特征与分区表Pre-loader 如何“认识”存储Pre-loader对存储的认知来源于分区表。MTK平台的分区表定义一般在lk的分区配置文件里面比如partition.xml或者device相关的头文件Pre-loader通过一个固定的偏移和大小去找到LK分区而不是扫全盘。这种设计可靠性高、速度快但也带来一个问题如果你刷过别的分区布局或者分区表被改动Pre-loader可能找不到LK整个系统就起不来。分区表里常见的有proinfo、nvram、protect1、protect2、seccfg、boot、recovery、system、vendor、cache、userdata等等。Pre-loader和LK依赖的是前面几个proinfo里存了一些产品信息nvram是校准数据区seccfg是安全配置。对业务开发来说最重要的是boot和recovery两个分区因为日常刷机其实就是写入这些分区。我自己的经验是遇到Pre-loader不加载LK的问题时先在log里看Pre-loader打印的分区偏移和大小再跟编译时用的partition配置对一下。很多奇怪的起不来问题最后发现是分区表不匹配。比如你原本是6GB的UFS配置却刷了4GB版本的Pre-loader偏移量就全乱了。3. 二阶段引导LK 与 U-Boot 的职责边界3.1 为什么MTK选LK而非常规Bootloader高通平台很多地方用U-Boot或者AbootMTK过去则一直以LK为主要二阶段引导程序。LK全称Little Kernel源码非常轻量最初的定位就是嵌入式设备的小型引导操作系统支持线程、定时器、IPC这些基础能力。MTK基于LK做大量定制增加显示驱动、触摸驱动、按键框架、fastboot、recovery支持等。很多人问LK和U-Boot到底哪个好我个人的看法是在MTK平台上LK的代码路径更简单启动更快适合跟Android的boot flow深度集成。LK能把fastboot、recovery、正常启动三件事都做了而且每个功能模块之间的耦合比较少想改起来相对容易。U-Boot的优势是社区生态更丰富、支持的硬件更多但如果只是做MTK平台的启动用LK会更顺手。MTK后来出的部分平台也提供U-Boot版本但大部分文档和工具链还是围绕LK来做的。我自己调试的板子绝大多数走的是LK路线。3.2 boot image 头部解析与内核装载LK最核心的职责是把boot分区里的Kernel和Ramdisk加载到正确位置然后跳转。要做到这一点LK必须先解析boot.img的头部。MTK的boot image头部是基于AOSP标准头部扩展的结构体一般叫boot_img_hdr最常见的字段包括magicANDROID!、kernel_size、kernel_addr、ramdisk_size、ramdisk_addr、tags_addr、页大小等。MTK扩展字段会包含platform、chip、sec_mode等等。打包工具一般对应的是mkbootimg或MTK自带的脚本比如在vendor/mediatek/proprietary/scripts/目录下有mkimage相关脚本。这里最大的坑是地址对齐和页面大小的设置很多自己打包的人不注意pagesize导致LK解析时读到错误的偏移内核加载失败。解析完头部之后LK会把kernel镜像拷贝到kernel_addrramdisk拷贝到ramdisk_addr然后设置一些寄存器参数包括机器码或设备树信息最后跳转到kernel入口。为了让Kernel知道当前用的boot device、要挂载哪个分区LK还会在内存特定位置放一份tagged list或者device tree binary。3.3 显示、按键与fastbootLK里不为人知的细节LK阶段对用户可见的东西很少但做的事情很多。首先是显示初始化。如果你在LK阶段能看到logo说明显示驱动已经开始工作。MTK在LK里面带了基础的LCM和PMIC驱动但一般来说只支持有限的屏幕型号所以很多定制屏在LK阶段是黑屏进去Android才会亮这并不代表启动失败。按键状态在LK里也承担着重要作用。热词里提到的“MTK按键进入拍照”对应的就是工厂测试或专项测试模式通常是在LK阶段检查音量键状态然后设置对应的boot mode后续内核和用户态根据这个模式决定启动到正常系统、recovery还是工厂模式。类似地长按音量下键进入fastboot是LK里最常见的按键分支逻辑。fastboot本身不是一个独立程序而是LK里的一个子模块。它实现了USB protocol映射到fastboot命令上比如fastboot flash、fastboot boot、fastboot oem unlock等。MTK在fastboot模式下还会多出一些厂商自定义命令比如写NV、查芯片ID、设置secure boot状态等。如果fastboot模式不识别设备先查USB枚举和驱动问题Windows下常见的是MTK USB Port驱动没装好。4. 内核启动从入口到Android用户态4.1 内核进入前的参数传递cmdline与dtbLK跳进Kernel之前有一件非常重要的事把启动参数传给内核。这部分包括cmdline、设备树地址、ramdisk地址等。MTK平台的cmdline通常会包含console配置比如consolettyS0,921600n8这样内核启动时的log才能从串口出来。否则你很可能看到LK的log正常但内核阶段一片安静。cmdline里还会包含androidboot.hardware、androidboot.selinux、androidboot.slot_suffix等关键参数。这些参数直接影响内核挂载哪个硬件平台、SELinux是enforcing还是permissive、以及A/B slot选择。对于调试来说最常用的操作是在cmdline里临时加上androidboot.selinuxpermissive可以大大减少权限问题带来的启动失败。设备树DTB也是LK传给内核的一个重要参数。MTK平台的内核启动一定要有匹配的DTBDTB里面包含了CPU、内存、中断控制器、串口、I2C、eMMC等所有硬件信息。如果LK加载的DTB跟实际硬件不匹配Kernel会在早期panic常见报错比如“psci: failed to boot CPU”这一类或者直接死在不打印log的地方。4.2 kernel启动的早期流程基于ARM64以ARM64架构为例Kernel入口在stext或者对应的汇编入口开始时关闭中断、设定异常向量表然后读取设备树并建立早期页表。在MMU打开之前CPU跑在物理地址上此时如果设备树内存节点不对基本必挂。MTK平台上这一段的log不一定能在串口看到有时候需要打开earlycon才能抓到。打开earlycon的方法很简单在cmdline里加earlyconuart8250,mmio32,0x11002000这样的参数具体地址得查平台的用户手册。加了之后你能在非常早期看到“sched_clock”、“Calibrating delay loop”这类信息。对于定位“内核完全没反应”的问题earlycon几乎是必备调试手段。再往后内核会初始化时钟、中断、定时器然后执行setup_arch把设备树里的各种资源注册进内核。接下来是console初始化挂载rootfs执行initrd/initramfs中的init程序。如果你看到“VFS: Cannot open root device”类似报错说明rootfs没挂上常见原因是分区名不对比如cmdline里的root/dev/mmcblk0p55与实际分区不符。4.3 initramfs与Android init的衔接MTK平台的Android普遍使用initramfs也就是ramdisk作为早期用户态它会被内核解压并作为第一个rootfs挂载。ramdisk里面包含了init可执行文件、fstab、一些启动脚本以及需要提前加载的ko模块。Android init进程起来之后会按照init.rc里的动作顺序挂载system、vendor等分区然后启动属性服务、zygote、surfaceflinger等。ramdisk和system分区的交接依赖dtb里的fstab信息MTK在vendor里面通常有一份启动阶段使用的fstab配置里面定义了system、vendor的实际块设备或DM节点。如果这一层对接不上你的现象是内核log正常但卡在init启动早期比如一直打印“init: Untracked pid”。排查时要先看分区是否存在、能不能被dm-verity正常映射。到了这个阶段Bootable启动链路基本就结束了后面Android用户态的事情属于运行时优化和系统服务的范畴。但我在实际项目里发现很多所谓“启动慢”“启动卡死”的问题反而能追溯到前面的引导阶段比如LK的显示等待超时、Kernel加载某个驱动阻塞了几秒这类问题用抓trace的方式能直观定位。5. 安全链路签名、校验与防回滚5.1 安全启动分级从普通boot到强制AVBMTK平台的安全启动是一个层层递进的过程。最基础的是Pre-loader和LK的签名校验如果芯片里的Secure Boot熔丝烧了Boot ROM会校验Pre-loader签名Pre-loader也会校验LK签名。其次是对boot镜像的校验很多平台会开启Android Verified BootAVB 2.0对kernel、ramdisk、system、vendor等分区做哈希校验和签名验证。AVB模式下boot分区的头部除了常规boot header之外还附加了vbmeta相关的结构。LK在加载boot的时候会先取vbmeta里的公钥哈希跟信任根比对一致才继续。这个信任根可能存放在芯片eFuse中也可能存放在特殊分区里。如果直接刷一个没有签名的boot.img开机时通常会在LK阶段打印failed to verify boot image然后可能进入recovery或者停在警告界面。防回滚属于另一个层面它由rollback index机制实现。AVB vbmata里会记录一个最低rollback index当刷入的镜像索引小于当前值设备就拒绝启动。MTK早年版本在这块比较宽松后来适配Android 10以上比较严格了你在降级刷机时遇到“rollback check fail”基本就是这个机制在拦截。5.2 LK态校验与kimg头部的关系MTK在boot镜像里还塞了一个叫kimg的字段来描述内核镜像的合法性。部分平台在LK阶段会对kernel头部再次校验确认镜像格式正确避免坏的内核镜像被带进内存。这个kimg头部通常包含镜像大小、加载地址、校验和、签名信息。如果开发时改过内核镜像格式或者加载地址需要同步更新kimg信息否则会触发拒绝加载。有些热词里提到的“hip error: device kernel image is invalid”或者“comfy no kernel image is available for execution on the device”虽然平台不完全一样但本质都是引导阶段发现kernel image无效。在MTK平台遇到类似问题优先检查boot.img是否用了正确的mkbootimg参数打包以及是否签名完整。调试安全启动时最常用的手段是确认secure boot状态。MTK有个seccfg分区里面记录当前安全状态比如已锁定、已解锁、回滚保护开启等。fastboot oem unlock或者客户解BL操作实际上就是在改写seccfg状态和清除信任根配置。开发板一般开着unlock量产机则默认锁定这是流程差异。5.3 调试与解锁如何绕开校验做开发对于做驱动的开发者来说天天签名校验太痛苦。好在除了严格的量产形态之外MTK还提供了工程机或userdebug模式的选项。工程机通常不烧Secure Boot熔丝可以随意刷boot image。userdebug版本中seccfg可能处于unlock状态且Android的验证启动也会放宽为警告而非强制。如果手里是一台已经锁定且带完整校验的量产机想刷第三方镜像就必须执行官方解锁流程。解锁一般会触发数据清除把seccfg改写为unlock状态后续fastboot刷boot/system就会宽松很多。但要注意解锁后rollback机制通常仍然生效不要尝试降到过旧的镜像版本。我自己在调试中很少硬碰安全校验更推荐准备一台专用的开发板把安全开关按需求预先配置好。这样能省下大量跟校验作斗争的时间。如果一定要在量产形态下调试也可以把安全校验的报错log抓出来逐条分析MTK每条校验失败log里基本都包含了是哪个分区、哪个公钥、哪一步对不上照着修就行。6. 实操抓trace、改启动参数与经典排查6.1 串口log的打开姿势调试MTK Bootable启动流程最核心的通道就是UART串口。MTK平台一颗SoC通常会引出多路UART调试常用的是uart0或uart1波特率一般是921600。使用USB转串口模块连接主板的调试串口把TX、RX、GND接对然后打开串口工具就能看到Pre-loader、LK、Kernel的完整启动log。接线时务必要先确认电平是否匹配很多板子的调试串口是1.8V电平直接用3.3V的串口模块容易烧串口或者不稳定。抓log时建议从一开始就记录完整时序尤其是Pre-loader阶段出现的时间戳。因为Pre-loader运行时间极短如果不打开时间戳事后很难判断卡了多久。我喜欢用支持时间戳的串口工具比如minicom加自动日志或者Putty的log模式。这样后面排查“慢启动”“卡启动”时才有依据。如果是没有串口的设备MTK也支持通过USB log输出部分启动信息但完整度不如UART。所以在条件允许的情况下尽量给调试板接串口这是启动调试的黄金通道。6.2 常用的boot链路调试命令进入fastboot模式后常用命令有这些fastboot devices确认设备枚举成功fastboot flash boot boot.img烧写boot分区fastboot boot boot.img临时启动指定镜像不改写分区适合快速验证fastboot getvar all查看设备变量包括secure状态、slot等fastboot oem unlock执行解锁流程在LK阶段你也可以在串口控制台输入命令具体取决于平台是否开启了LK shell。有的平台支持reboot到指定模式比如reboot fastboot、reboot recovery调试按键逻辑时非常方便。如果需要在Kernel启动阶段验证cmdline是否生效可以进入系统后用dmesg | grep Kernel command line来看。或者直接读/proc/cmdline能看到当前内核收到的实际cmdline内容。注意如果Android的bootloader做了二次修改实际生效cmdline可能跟LK传出的有差异要以/proc/cmdline为准。6.3 常见故障速查表我整理下面这张表基本覆盖了日常能遇到的大部分启动链路问题。每一条都是我在实际调试中踩过或者协助别人排查过的。现象可能原因排查方向完全没有串口log串口接线或电平错误或芯片根本没跑起来先查电源、复位、时钟再查串口工具配置只有Boot ROM log没有Pre-loaderPre-loader没加载到或DRAM初始化失败查启动介质选择查Pre-loader分区是否存在查DRAM参数Pre-loader循环重启Pre-loader校验失败、或者跳到LK的地址不对抓重启前的完整log重点看“jump to LK”的地址LK阶段黑屏且不进fastboot按键配置不对、显示时序卡住、boot分区损坏试试按键进fastboot短接测试点确认boot分区可读LK卡在加载bootboot镜像头部或签名校验失败用工具解析boot.img头部确认kimg、签名、地址Kernel无logcmdline里console串口没配对、earlycon没开检查LK传给内核的设备树和cmdlineKernel panic没有rootfsramdisk挂载失败、分区名不对检查cmdline里的root参数、fstab、dtb里的分区节点卡在init早期SELinux权限或分区属性问题先加androidboot.selinuxpermissive确认可起来这个表可以当成排查第一参考遇到问题先对着现象找方向再配合log精确定位。很多看起来复杂的启动问题最后定位下来原因都很简单只是链路长容易让人找不到下手点。7. 写到最后我给初学者的三条建议第一条建议不要在没有任何log的情况下猜问题。启动链路的每一级几乎都有log输出你只需要把串口接对、工具开好就能看到每一阶段的名字和数据。没有log就去查接线、查工具配置不要盯着代码空想。第二条建议先把镜像的加载地址和分区表吃透。MTK平台所有启动阶段都跟“地址”强相关Pre-loader放LK的地址、LK放Kernel的地址、Kernel挂rootfs的路径任何一个对不上都会导致问题。把这些地址关系整理成一张脑图以后排查故障会快很多。最后一条Debug版本和量产版本在启动链路上的行为差异很大。开发早期尽量用工程机或者userdebug版本调试把所有校验先放宽等到功能稳定了再回到量产配置验证安全启动和防回滚逻辑。这样既保证开发效率又不会被安全校验反复打断。我自己踩过不少这方面的坑真心建议你把安全启动的开关状态作为每次环境配置的第一检查项。
返回列表