4. 恢复出厂设置:Factory Reset 的实现策略与数据保护

恢复出厂设置,听起来简单——把设备清空,回到刚出厂的干净状态。但实际做起来,坑特别多。

我最早做这个功能时,以为就是格式化一下分区。结果第一次测试就出事了:设备变砖,连串口都救不回来。后来我才明白,Factory Reset 的核心不是「删」,而是「怎么安全地删」和「哪些不能删」。

4.1 什么是真正的 Factory Reset?

说白了,Factory Reset 要做三件事:

  • 清除用户数据:配置、日志、缓存、用户安装的证书
  • 恢复系统文件:把固件、默认配置、校准参数还原到出厂版本
  • 保证设备不砖:即使复位过程中断电,重启后还能继续复位

你想想看,如果用户按了复位键,结果设备变砖了,那这个功能还不如不做。

核心原则:Factory Reset 必须是幂等的、可中断恢复的。执行到一半断电,重启后要么继续复位,要么回滚到上一个安全状态。

4.2 数据分类:哪些该删,哪些该留?

我在项目中见过最蠢的 bug,就是把设备的 MAC 地址和校准参数也一起删了。结果每台设备复位后都要返厂重新校准。

所以第一步,先把存储数据分好类:

数据类别 示例 复位策略
系统固件 内核、文件系统、Bootloader 保留,不可删除
出厂校准 传感器偏移、射频校准、MAC 地址 保留,写入后只读
用户配置 WiFi 密码、服务器地址、用户偏好 清除,恢复默认值
运行时数据 日志、缓存、临时文件 清除,直接删除
用户证书 HTTPS 证书、SSH 密钥 清除,但需用户确认

注意:千万不要把 Bootloader 和系统固件放在可擦除的用户分区里。我曾经见过一个产品,把 Bootloader 和配置文件放在同一个 SPI Flash 分区,结果一次复位把 Bootloader 也擦了——直接变砖,只能拆机用编程器救。

4.3 实现策略:双分区 + 出厂镜像

我个人习惯用双分区方案。为什么?因为安全。

基本思路是这样的:

  • A 分区:运行中的系统,包含用户数据
  • B 分区:出厂镜像,只读,包含干净的固件和默认配置

执行 Factory Reset 时,直接把 A 分区擦掉,从 B 分区复制一份干净的镜像到 A 分区。这样即使复制过程中断电,重启后 Bootloader 检测到 A 分区不完整,会自动从 B 分区重新复制。

// 伪代码:Factory Reset 流程
int factory_reset() {
    // 1. 检查出厂镜像完整性
    if (verify_image(IMAGE_PARTITION) != OK) {
        log_error("出厂镜像损坏,无法复位");
        return -1;
    }

    // 2. 擦除用户分区
    erase_partition(USER_PARTITION);

    // 3. 复制出厂镜像到用户分区
    if (copy_image(IMAGE_PARTITION, USER_PARTITION) != OK) {
        // 复制失败,标记需要重试
        set_recovery_flag(RECOVERY_NEEDED);
        reboot();
    }

    // 4. 清除复位标志
    clear_recovery_flag();

    // 5. 重启进入新系统
    reboot();
    return 0;
}

经验之谈:复制镜像时,建议先写一个完整的校验头,最后再写校验标志。这样如果中途断电,Bootloader 看到校验头不完整,就知道需要重新复制。我管这叫「先写票,后上车」。

4.4 数据保护:哪些数据绝对不能丢?

嗯,这里要注意。有些数据丢了,设备就废了。

  • 硬件唯一标识:MAC 地址、序列号、芯片密钥。这些通常在出厂时烧录在 OTP 或 eFuse 里,软件删不掉。
  • 校准参数:每个设备的传感器校准值都不一样。我建议把这些参数放在一个独立的小分区,标记为只读,复位时不动它。
  • 用户证书:这个比较特殊。有些场景下,用户希望复位后证书还在(比如企业批量部署的设备)。我一般加一个选项:factory_reset --keep-certs。

我曾经踩过的坑:有一次,我把校准参数和用户配置放在同一个文件里。结果复位时把整个文件删了,校准参数也丢了。后来我学乖了:校准参数单独放一个分区,并且用硬件写保护引脚锁住。软件想删都删不掉。

4.5 远程触发 Factory Reset

远程诊断时,经常需要远程复位设备。但远程复位比本地复位危险得多——你没法按着电源键强制重启。

我的建议是:

  1. 双重确认:远程复位命令必须带一个随机 token,用户需要在设备上确认。或者发两次命令,间隔 30 秒。
  2. 超时回滚:如果复位后设备 5 分钟内没有上报心跳,自动回滚到上一个版本。
  3. 日志保留:复位前,把当前日志上传到服务器。这样即使设备变砖,也能分析原因。
// 远程复位命令格式
{
    "command": "factory_reset",
    "token": "a1b2c3d4e5f6",
    "keep_certs": true,
    "reason": "用户远程触发",
    "timeout": 300
}

4.6 核心流程图

下面这张图,是我做 Factory Reset 时一直贴在墙上的。每次改代码都要看一眼,确保没漏掉关键步骤。

Factory Reset 核心流程图 收到复位命令 检查权限 & Token 备份日志到服务器 校验出厂镜像完整性 镜像完好? 否 记录错误,拒绝复位 是 擦除用户数据分区 复制出厂镜像到用户分区 重启进入新系统 失败分支 成功分支

4.7 避坑指南

最后,分享几个我踩过的坑,希望能帮你省点时间:

  • 不要直接格式化整个 Flash:有些 Flash 芯片的擦除寿命只有 1 万次。频繁复位会缩短寿命。我建议只擦除用户分区,不要动 Bootloader 和校准分区。
  • 复位后要清理敏感数据:如果设备存过密码或密钥,简单的文件删除是不够的。要全块覆写,防止被恢复。我一般用随机数覆写三遍。
  • 给用户一个取消的机会:远程复位命令发出后,留一个 30 秒的取消窗口。我曾经误触过一次,把整个机房的设备都复位了——从那以后,我坚持加取消机制。
  • 记录复位日志:谁、什么时间、为什么触发复位,这些信息要写到不可擦除的日志分区。方便事后审计。

我的习惯:每次做 Factory Reset 功能,我都会在开发板上模拟一次「复位过程中断电」。如果设备能自动恢复,才算通过。这个测试虽然简单,但能发现 80% 的隐藏 bug。