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
远程诊断时,经常需要远程复位设备。但远程复位比本地复位危险得多——你没法按着电源键强制重启。
我的建议是:
- 双重确认:远程复位命令必须带一个随机 token,用户需要在设备上确认。或者发两次命令,间隔 30 秒。
- 超时回滚:如果复位后设备 5 分钟内没有上报心跳,自动回滚到上一个版本。
- 日志保留:复位前,把当前日志上传到服务器。这样即使设备变砖,也能分析原因。
// 远程复位命令格式
{
"command": "factory_reset",
"token": "a1b2c3d4e5f6",
"keep_certs": true,
"reason": "用户远程触发",
"timeout": 300
}
4.6 核心流程图
下面这张图,是我做 Factory Reset 时一直贴在墙上的。每次改代码都要看一眼,确保没漏掉关键步骤。
4.7 避坑指南
最后,分享几个我踩过的坑,希望能帮你省点时间:
- 不要直接格式化整个 Flash:有些 Flash 芯片的擦除寿命只有 1 万次。频繁复位会缩短寿命。我建议只擦除用户分区,不要动 Bootloader 和校准分区。
- 复位后要清理敏感数据:如果设备存过密码或密钥,简单的文件删除是不够的。要全块覆写,防止被恢复。我一般用随机数覆写三遍。
- 给用户一个取消的机会:远程复位命令发出后,留一个 30 秒的取消窗口。我曾经误触过一次,把整个机房的设备都复位了——从那以后,我坚持加取消机制。
- 记录复位日志:谁、什么时间、为什么触发复位,这些信息要写到不可擦除的日志分区。方便事后审计。
我的习惯:每次做 Factory Reset 功能,我都会在开发板上模拟一次「复位过程中断电」。如果设备能自动恢复,才算通过。这个测试虽然简单,但能发现 80% 的隐藏 bug。