1. 分区镜像基础:从一张SD卡说起

各位同学好,我是老周。做嵌入式系统开发十几年了,今天咱们聊聊分区镜像这件事。

你想想看,一个嵌入式设备——比如你家的路由器、智能音箱、车载中控——它里面跑的不是Windows,也不是macOS,而是一套精简的Linux系统。这套系统怎么装进去的?靠的就是分区镜像。

分区镜像,说白了就是把一个分区的全部内容——文件、权限、属性、甚至空洞——原封不动地打包成一个二进制文件。烧录时直接把这个文件“怼”到存储设备的对应分区里,系统就能跑起来。

1.1 什么是分区镜像

我习惯把分区镜像比作“硬盘的X光片”。它不光记录了文件数据,还记录了文件系统的元数据、权限、时间戳、块布局。你拿 dd 命令把整个分区读出来,生成的就是一个原始镜像。

# 把 /dev/mmcblk0p2 分区完整备份成镜像
sudo dd if=/dev/mmcblk0p2 of=rootfs.img bs=4M status=progress

这个 rootfs.img 就是分区镜像。它可以直接烧录到另一块板的相同分区上,系统就能一模一样地跑起来。我在项目中遇到过好几次,产线烧录时镜像文件损坏了一个字节,结果整批设备启动到一半就卡死——嗯,从那以后我对镜像的完整性校验就特别上心。

小提示:分区镜像和文件归档(比如tar.gz)不一样。tar包只保存文件内容,不保存文件系统的元数据(比如块组描述符、inode表)。分区镜像是“块级别”的拷贝,连空闲块都原样保留。

1.2 为什么需要多分区

你可能会问:一个分区装所有东西不行吗?行,但风险很大。

我举个例子。早期有些低端路由器,整个系统就一个分区。有一次我在OTA升级时断电了,结果呢?Bootloader、内核、文件系统全部损坏,设备直接变砖。用户只能返厂维修。

多分区的好处,说白了就是隔离风险。每个分区各司其职,互不干扰。

单分区 多分区
升级失败 → 整机变砖 升级失败 → 只影响对应分区
无法独立更新内核或文件系统 可以单独升级内核、Rootfs、应用
日志写满 → 系统崩溃 日志分区写满不影响系统分区
安全补丁必须全量更新 可以只替换有漏洞的分区

我个人习惯至少分4个区:Bootloader、Kernel、Rootfs、Userdata。这样不管是开发调试还是产线量产,都灵活得多。

1.3 常见的嵌入式分区布局

嵌入式系统的分区布局,其实是有“套路”的。下面这张图是我自己画的典型布局,你一看就明白。

典型嵌入式分区布局 典型嵌入式分区布局(eMMC / NAND Flash) 存储设备(eMMC / NAND Flash) Bootloader uboot / spl Kernel zImage / uImage Rootfs squashfs / ext4 User data 各分区说明: • Bootloader:引导加载程序,负责初始化硬件、加载内核 • Kernel:操作系统内核,管理硬件资源、提供系统调用 • Rootfs:根文件系统,包含系统命令、库、配置文件 • Userdata:用户数据分区,存放应用、日志、配置

1.3.1 Bootloader 分区

Bootloader 是系统上电后第一个执行的程序。它的任务很简单:初始化CPU、内存、存储控制器,然后从Kernel分区加载内核到内存,跳转执行。

常见的Bootloader有U-Boot、GRUB、Coreboot。在嵌入式领域,U-Boot是绝对的主流。我记得有一次调试一块新板子,U-Boot死活起不来,最后发现是DDR时序参数配错了——这种问题,没有Bootloader分区独立调试,你根本没法搞。

注意:Bootloader分区一般只占几百KB到几MB。它必须是“自包含”的——不能依赖其他分区。如果Bootloader坏了,整个设备就真的变砖了。所以很多产品会做Bootloader的冗余备份。

1.3.2 Kernel 分区

Kernel分区存放的是操作系统内核镜像。在嵌入式Linux中,通常是zImage或uImage格式。内核负责管理进程、内存、设备驱动、文件系统等核心功能。

Kernel分区一般几MB到十几MB。为什么单独分一个区?因为内核更新频率比文件系统低,而且内核镜像需要被Bootloader直接读取——如果混在文件系统里,Bootloader还得先解析文件系统格式,太麻烦了。

# 查看当前内核分区信息(以i.MX6平台为例)
cat /proc/mtd
# 输出示例:
# dev:    size   erasesize  name
# mtd0: 00100000 00020000 "bootloader"
# mtd1: 00800000 00020000 "kernel"
# mtd2: 02000000 00020000 "rootfs"
# mtd3: 04000000 00020000 "userdata"

1.3.3 Rootfs 分区

Rootfs是根文件系统,包含/bin、/sbin、/etc、/lib、/usr等目录。系统启动后,内核会挂载Rootfs,然后执行init进程。

Rootfs用什么文件系统?这要看需求。我常用的方案有几种:

文件系统 特点 适用场景
squashfs 只读、压缩率高、体积小 产品固件,不允许用户修改系统
ext4 读写、成熟稳定 开发板、允许用户安装软件
ubifs 针对NAND Flash优化、支持磨损均衡 NAND Flash设备
initramfs 内存文件系统、启动极快 急救系统、临时启动

我个人习惯在产品发布时用squashfs——只读、防篡改、体积小。但开发阶段用ext4,方便调试。你想想看,如果开发阶段用只读文件系统,改个配置文件都要重新烧录,那效率得多低?

1.3.4 Userdata 分区

Userdata分区存放用户数据:应用程序、日志、配置文件、数据库等。这个分区通常是可读写的,用ext4或F2FS。

为什么要单独分一个Userdata?因为系统升级时,Rootfs会被整个替换,但用户数据不能丢。如果所有东西都在一个分区,升级就等于格式化——用户存的照片、配置全没了,这谁受得了?

经验之谈:我曾经在一个项目中,Userdata分区用F2FS文件系统。F2FS对Flash有优化,随机写入性能比ext4好不少。但要注意,F2FS在掉电时容易丢数据——所以关键数据要定期同步(sync)。

1.4 分区布局的变体

上面说的4分区布局是“标准款”。实际项目中,根据需求会有各种变体:

  • 双备份系统(A/B分区):Kernel和Rootfs各分A/B两份,升级时写B区,失败后回滚A区。汽车电子、医疗设备常用。
  • 单独的数据持久化分区:比如 /data 和 /cache 分开,防止日志写满影响应用数据。
  • 工厂校准分区:存放WiFi MAC地址、传感器校准参数,出厂后只读。
  • OTA升级包暂存分区:下载升级包时临时存放,避免占用Userdata空间。

我记得有个车载项目,客户要求系统升级时绝对不能变砖。我们做了A/B分区 + 3个Bootloader备份——总共7个分区。虽然浪费了点存储空间,但可靠性确实上去了。

核心思想:分区布局没有标准答案,完全取决于你的产品需求。但有一条原则我始终遵守——把变化频率不同的内容分开。Bootloader几乎不变,Kernel偶尔变,Rootfs随版本变,Userdata随时变。分开存放,各自管理,互不干扰。