只读根文件系统:如何保留配置、日志与升级状态

只读 Linux 根文件系统可以让软件镜像在日常运行中保持稳定,但产品仍然需要可写状态。网络设置要能在重启后保留,日志需要有容量限制的存储位置,升级也必须保留足以完成恢复的信息。设计的关键是为每类数据明确生命周期、责任主体和故障处理策略。

先建立写入清单

逐个列出服务写入什么、何时写入、最多写入多少,以及写入失败时如何处理。清单应覆盖启动早期程序、DHCP 客户端、SSH 身份、时钟状态、数据库和崩溃转储。即使某个应用很少写入,只要默认写入路径位于只读挂载点,也可能因此无法启动。

数据类别 典型位置 所需行为
系统二进制文件与出厂默认配置 只读根文件系统 通过受控软件升级替换
运行时套接字、PID 文件及临时数据 /run 或 /tmp 下有容量限制的 tmpfs 每次启动后重新创建
客户配置 持久化应用目录 经过验证和版本管理,在受支持升级中保留
设备身份与凭据 限制访问的持久化存储或硬件保护存储 唯一配置,并具备明确的重置策略
诊断信息 有容量限制的易失和/或持久化日志区 执行保留期限、隐私和存储预算要求
升级元数据与暂存内容 预留持久化区域或非活动槽位 在规定的恢复状态转换中保留

表中路径描述的是职责,并非通用分区方案。使用 eMMC、小容量 NOR 和原始 NAND 的板卡需要不同的存储集成方式。应选择实际 BSP、引导程序和 Flash 技术支持的文件系统与升级结构。

只读根文件系统槽位连接应用,可写数据分为易失运行时数据、持久配置以及有容量限制的日志和升级状态。
图 1:为每类写入明确位置和生命周期。软件回滚还需要兼容或可恢复的应用数据。图中文字为英文。

选择不可变层与可写边界

SquashFS 是压缩只读文件系统。以只读方式挂载 ext4 根文件系统也是一种设计选择,但镜像和恢复方面的考虑不同。单凭这两种选择都不能验证运行软件的真实性。如果需要验证软件,应设计信任链;dm-verity 根据可信根哈希检查数据块完整性,并需要集成到可信启动路径中。

优先使用应用专属可写路径,而不是让整个 /etc 可写。将默认配置保留在镜像内,将明确的客户覆盖设置单独存储。对于要求固定路径的旧软件,可通过绑定挂载将持久化应用目录映射到该位置。应在镜像中预建挂载点,并在启动服务前设置好所有权。

对整个根目录使用 OverlayFS 可以兼容大量硬编码写入路径,但会引入额外的升级行为。旧的上层文件可能遮住新版下层镜像中已修复的文件;删除标记也可能延续过时的目录视图。必须明确上层数据是可丢弃、需要迁移,还是与特定镜像代次绑定。

使用 OverlayFS 时,上层可写文件系统必须支持所需扩展属性和目录项信息;工作目录应为空,并且与上层目录位于同一文件系统。请按实际部署的内核核对内核 OverlayFS 文档。不要将不同供应商内核版本视为可以任意替换。

将启动顺序视为正确性条件

先挂载持久化存储,确认设备身份和布局符合预期,初始化所需目录,执行经过批准的迁移,然后才启动应用。若关键持久化配置缺失,不应悄悄回退到空的 RAM 目录。应进入可识别的恢复模式,或文档明确规定的受限功能模式。

以下示例说明数据卷成功挂载后,旧应用所需的路径关系。两个目录均须事先存在,权限也须匹配服务账户。应将这些步骤集成到所选 init 系统,而不是在启动后期临时执行。

# /data is the verified, mounted persistent filesystem.
# /etc/myapp is a mount point created in the rootfs image.
mount --bind /data/config/myapp /etc/myapp
# Start myapp only after this mount succeeds.

Systemd 依赖关系与 BusyBox/SysV 脚本表达启动顺序的方式不同。检查实际 Buildroot 目录骨架、init 选型和服务脚本。可在目标设备上检查 /proc/mounts,确认实际挂载结果,并判断挂载失败后路径是否只是底层普通目录。

在写入中断时保护配置

新配置生效前先进行验证。对于单文件格式,一种常见的应用层提交顺序是:在同目录写入临时文件,将其同步到存储,再通过重命名替换当前文件,最后同步目录。检查每一步返回值;若产品要求恢复能力,应保留已知有效的上一代配置。

validate(candidate)
write(temp_in_same_directory, candidate)
fsync(temp_file)
rename(temp_file, active_file)
fsync(parent_directory)
report_success()

以上是伪代码,不是可以直接运行的工具。发布新文件前应设置权限,处理多个并发写入者;若多条记录必须一起改变,应使用事务型数据库。Linux 的 fsync 文档解释了为什么只同步文件不一定能持久保存其目录项。持久性仍然取决于文件系统、驱动和存储设备的行为,因此必须在实际硬件上进行断电测试。

为日志和临时文件分别设定预算

tmpfs 使用虚拟内存,启用交换空间时可以使用交换空间,卸载后内容会丢失。应限制字节数和 inode 数,并将这部分预算纳入峰值内存测试。没有容量限制的大型 RAM 日志目录可能让原本正常的应用耗尽内存。

对于采用 systemd 的镜像,Storage=volatile 将日志保存在 /run/log/journal;可用时,持久化模式使用 /var/log/journal。应根据所选模式配置 RuntimeMaxUse 或 SystemMaxUse,并核对实际交付版本对应的 journald 文档。BusyBox syslog 则需使用自身的容量、轮转和输出位置设置。

配置和升级所需空间应独立于大量诊断日志保留。同一文件系统中的不同目录本身不能隔离容量;应采用合适的配额、分区或明确的空间预留机制。定义哪些事件必须在断电后保留,并对凭据进行脱敏。只有同时规定断线、队列增长和投递缺口的处理方式,远程日志才能发挥预期作用。

将持久化与回滚一起设计

A/B 软件槽位不会自动形成 A/B 应用数据。新应用可能将共享数据库迁移成旧应用无法读取的格式。可选办法包括向后兼容的数据结构、独立数据代次,或经过明确测试的恢复路径。RAUC 的数据存储指南讨论了共享与冗余数据分区,并明确指出迁移需要针对产品实现。

防止升级暂存文件耗尽配置存储空间。区分升级包已下载、升级包已验证、启动槽位已选定和健康启动已确认这些不同状态。只持久保存所选升级框架需要的状态。恢复出厂设置应有书面约定,明确是否清除客户设置、保留日志、凭据和设备身份;这些类别通常需要区别处理。

验收测试与故障排查

故障注入 需要定义的预期结果 优先检查的证据
配置提交期间断电 保留有效的上一代或新一代配置,不会静默使用损坏设置 配置校验、代次标记和文件系统错误
持久化卷缺失或损坏 执行文档规定的恢复行为 挂载日志、设备身份和应用启动顺序
日志字节空间或 inode 耗尽 仍满足配置和升级策略 文件系统容量、inode 使用量和日志程序错误
升级后回滚 旧软件可以使用保留或恢复的数据 数据结构版本和迁移记录
大量临时文件使用后的冷启动 在内存预算内重建运行时状态 tmpfs 限制和峰值内存记录
新根文件系统搭配原有覆盖层 新版默认配置和二进制文件按预期可见 上层文件、删除标记和迁移策略

如果设置丢失,应先验证可写路径是否属于持久化存储,并且在使用前已经挂载。若升级后旧行为仍然存在,应检查覆盖层遮蔽和保留配置。如果服务报告“只读文件系统”,应定位具体写入操作,不要直接将整个根文件系统重新挂载为读写。上述测试需要受控实验室故障注入和约定好的恢复流程;本文不声称已经取得断电实测结果。

Obeita 的固件与 BSP 诊断服务可作为存储和启动审查的相关入口。Linux/Qt HMI 已交付项目案例提供了相关终端背景。该案例不能作为本文持久化架构已通过验证的证据。

类似文章