6、Recovery模式与启动流程:Recovery分区作用、内核引导与init进程、Recovery模式下的UI系统、按键与菜单交互

各位同学,今天我们聊一个在车载OTA开发中绕不开的话题——Recovery模式。说实话,很多刚入行的工程师觉得Recovery就是个刷机界面,没什么好研究的。但我在实际项目中吃过不少亏,才明白Recovery模式的设计直接决定了OTA升级的成败。你想想看,如果系统升级到一半断电了,或者刷了个坏包,谁来兜底?就是Recovery。

6.1 Recovery分区:系统最后的保险丝

先说说Recovery分区是干嘛的。它本质上是一个独立的小系统,跟主系统(boot + system)完全隔离。我习惯把它比作飞机的备用仪表盘——主系统挂了,它还能工作。

Recovery分区的核心作用:

  • 系统恢复:当主系统无法启动时,Recovery负责修复或重刷系统
  • OTA升级执行者:升级包下载完成后,由Recovery完成实际刷写操作
  • 数据擦除:支持恢复出厂设置、清除缓存等操作
  • 日志收集:记录升级过程中的关键信息,便于排查问题

分区布局上,Recovery通常包含自己的内核(zImage)和ramdisk。这意味着它不依赖主系统的任何组件。我在项目中遇到过一种情况:主系统的system分区被写坏了,但Recovery依然能正常启动,这就是隔离设计的好处。

嗯,这里要注意:Recovery分区的大小一般在32MB到64MB之间,具体取决于厂商。太小了放不下完整的UI和工具链,太大了又浪费存储空间。我个人建议至少留40MB,给未来功能扩展留点余地。

6.2 内核引导与init进程:Recovery的启动链条

Recovery的启动流程跟主系统很像,但更精简。我们来拆解一下:

  1. BootROM加载引导加载器:芯片上电后,BootROM从存储介质中读取引导加载器(通常是U-Boot或LK)
  2. 引导加载器选择启动分区:根据启动模式(正常/Recovery/刷机),加载对应的内核和ramdisk
  3. 内核启动:解压内核,初始化硬件,挂载根文件系统(ramdisk)
  4. init进程启动:内核启动完成后,执行ramdisk中的init程序
  5. Recovery服务启动:init解析recovery.rc脚本,启动recovery服务

为什么会单独讲init进程?因为Recovery模式下的init跟主系统完全不同。主系统的init要启动一大堆服务(SurfaceFlinger、SystemServer等),而Recovery的init只做三件事:挂载分区、启动UI、等待用户操作或自动执行升级脚本。

我曾经在调试一个车载项目时,发现Recovery启动后总是卡在init阶段。查了半天,原来是recovery.rc里挂载/data分区的命令写错了路径。这种问题很隐蔽,因为日志只打印一行"mount failed",不仔细看根本发现不了。

避坑指南:我曾经在Recovery的init脚本中忘记设置SELinux上下文,导致升级脚本无法访问某些文件。建议在recovery.rc中显式添加:

on fs
    mount ext4 /dev/block/by-name/system /system
    restorecon_recursive /system

下面这张图展示了Recovery模式的完整启动流程,我建议你把它保存下来,以后排查问题时会很有用:

Recovery模式启动流程 BootROM → 引导加载器 启动模式判断 正常模式 Recovery模式 启动主系统 加载Recovery内核 内核初始化硬件 init进程启动 Recovery服务启动 说明:Recovery模式使用独立的内核和ramdisk 不依赖主系统的任何组件

6.3 Recovery模式下的UI系统:轻量级图形界面

Recovery的UI系统跟Android主系统完全不是一回事。主系统用的是SurfaceFlinger + HWUI,而Recovery用的是自己的一套轻量级图形栈。说白了,Recovery不需要炫酷的动画,它只需要能显示文字、进度条和几个按钮就够了。

Recovery的UI架构大致是这样的:

  • 帧缓冲区(Framebuffer):直接操作/dev/graphics/fb0,绕过所有窗口管理器
  • 字体渲染:使用位图字体(通常是png格式的字体图集),不依赖FreeType
  • 图片解码:支持PNG格式,用于显示logo和背景
  • 进度条:通过不断刷新帧缓冲区实现动画效果

我记得第一次看Recovery的UI代码时,觉得这玩意儿也太原始了。但后来想想,这正是它的优势——简单、可靠、不容易出问题。你想想看,如果Recovery也搞个SurfaceFlinger,那一旦SurfaceFlinger挂了,Recovery本身也废了,这不就陷入死循环了吗?

Recovery UI的关键文件:

文件 作用
screen_ui.cpp 屏幕绘制核心,管理帧缓冲区
ui.cpp UI逻辑,处理菜单、进度条等
device.cpp 设备抽象层,处理按键映射
fonts.h 字体数据,包含ASCII字符的位图

6.4 按键与菜单交互:没有触摸屏怎么办?

车载系统跟手机不一样,很多车机在Recovery模式下根本没有触摸屏支持。那用户怎么操作?全靠物理按键。常见的按键映射包括:

  • 音量上/下:上下移动菜单选项
  • 电源键:确认选择
  • Home键或返回键:返回上一级菜单

按键的驱动在Recovery内核中就已经初始化好了。Recovery的init进程会启动一个按键监听线程,通过读取/dev/input/eventX设备文件来获取按键事件。嗯,这里有个坑:不同车机的按键扫描码(keycode)可能不一样,需要根据硬件调整映射关系。

我曾经在一个项目中,车机的音量键在Recovery模式下死活没反应。查了半天,发现是内核的GPIO按键驱动没配置对,导致按键事件根本没上报到input子系统。这种问题很头疼,因为Recovery模式下没有adb,调试全靠串口日志。

注意事项:在Recovery模式下,按键响应必须非常灵敏。用户升级时如果按键没反应,会误以为系统死机了。我建议在按键处理逻辑中加入去抖处理,同时把按键事件的日志打印到串口,方便调试。

菜单交互的逻辑其实很简单:

  1. Recovery启动后,显示主菜单(重启、应用更新、清除数据等)
  2. 用户通过按键选择菜单项
  3. 选中后执行对应的操作(调用install_package、wipe_data等函数)
  4. 操作完成后返回主菜单或重启

代码层面,菜单项定义在recovery.cpp中,每个菜单项对应一个函数指针。我个人习惯在添加新功能时,先在菜单列表里注册,再实现对应的处理函数。这样结构清晰,不容易遗漏。

调试技巧:如果你在开发Recovery UI,建议在screen_ui.cpp中加一个调试模式。长按某个按键组合可以显示帧缓冲区的刷新率、内存使用情况等信息。这个功能在排查UI卡顿问题时非常有用。

好了,关于Recovery模式的核心内容就讲到这里。记住一句话:Recovery是OTA升级的最后一道防线,它的设计原则是简单、可靠、可调试。下一节我们会深入Recovery的升级脚本系统,看看update-binary是怎么工作的。


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