系统镜像把引导程序、分区表和各分区内容按确定的偏移组合进一个文件。下面以旧 Lite200 / Allwinner SD 启动实验的 256 MiB、MBR、512 字节逻辑扇区布局为例,解释每个字节放在哪里。它只生成普通镜像文件;是否可启动仍取决于匹配的板卡、SPL/U-Boot、内核、设备树与 rootfs。
分区和格式化是两件事
跳转到“分区和格式化是两件事”分区表记录分区的起点、长度和类型。格式化则在分区内部建立 FAT、ext4 等文件系统元数据。写入 MBR 的类型码 83 不会自动建立 ext4,选择 FAT 分区类型也不会自动产生 FAT 文件系统。
U-Boot 的 load mmc 0:1 ... 先定位相应 MMC 设备的第 1 个分区,再由文件系统驱动按文件路径查找数据。分区表不会逐个记录 zImage 或 DTB 的「绝对文件地址」,文件也未必由一段连续扇区组成。

这张原图画的是 MBR 主分区项和扩展分区/EBR 链,原图下方的「FAT32 分区表」标题不准确。MBR 在本例第 0 个 512 字节扇区内,含分区项和末尾 55 AA 签名;FAT32 属于文件系统层。本例只有两个主分区,不使用扩展分区。
8 KiB 引导位置与 1 MiB 分区起点
跳转到“8 KiB 引导位置与 1 MiB 分区起点”
传统 Allwinner SD 启动在扇区 16,即 16 × 512 = 8192 字节处查找具有正确头部和校验的引导镜像。这里放的是包含 SPL 的 u-boot-sunxi-with-spl.bin,不能任意替换成另一种 u-boot.bin。U-Boot Allwinner 文档 同时指出,8 KiB 位置可能覆盖 GPT 分区表;部分较新 SoC 支持另一偏移,但不能将其推广到所有型号。
本例将第一个分区对齐到 1 MiB,为 MBR 和引导程序留空间。不是「文件系统规定前 1 MiB 全部是分区表」,也不是「1 MiB 是唯一合法起点」。图中的 /dev/sdb 只是旧设备名,不能据此选择当前机器上的写入目标。
精确计算布局
跳转到“精确计算布局”1 MB = 1,000,000 字节,1 MiB = 1,048,576 字节。GNU dd 的 bs=1M 使用后一种大小;下面统一用 MiB 描述容量。

| 区域 | 起始字节 | 起始扇区 | 扇区数 | 最后扇区(含) | 容量 |
|---|---|---|---|---|---|
| 前部保留区域 | 0 | 0 | 2,048 | 2,047 | 1 MiB |
| BOOT | 1,048,576 | 2,048 | 65,536 | 67,583 | 32 MiB |
| rootfs | 34,603,008 | 67,584 | 456,704 | 524,287 | 223 MiB |
| 整个镜像 | 0 | 0 | 524,288 | 524,287 | 256 MiB |
起止范围采用「最后扇区包含在内」的表示法,长度是 end - start + 1。引导文件从 8,192 字节开始,最多占用 1,048,576 - 8,192 = 1,040,384 字节;超过此值就会撞到 BOOT 分区,不能直接继续写入。
生成镜像文件
跳转到“生成镜像文件”准备四个匹配同一板卡与 BSP的输入:带 SPL 的引导文件、zImage、DTB 和已生成的 rootfs.ext4。本例用 FAT16,对应 MBR 类型 0e;根分区用 Linux 类型 83。根文件系统镜像允许小于分区,但其内部文件系统不会因分区变大而自动扩容。
优先让 Buildroot 直接生成配置好的 ext4 镜像。若原材料只有 rootfs.tar,需要先解包到能够保留 UID/GID、权限、符号链接和设备节点的受控目录,再由文件系统工具生成镜像;不能把 tar 包直接当作 ext4 写入。手工 mkfs.ext4 -d 的输入目录也必须具备正确的元数据,并核对 ext4 特性与目标内核兼容性。原记录的 RAID stride/stripe-width 参数没有给出相应存储条带依据,因此不作为通用选项保留。
下面脚本需要 Linux、Bash、GNU coreutils、util-linux、dosfstools、mtools 和 Python 3。保存为 pack-lite200.sh 后,按顺序传入四个输入文件。脚本每次在当前目录新建一个结果目录,不接收磁盘设备作为输出。
#!/usr/bin/env bashset -euo pipefailif (( $# != 4 )); then printf 'usage: %s UBOOT ZIMAGE DTB ROOTFS_EXT4\n' "$0" >&2 exit 2fifor tool in realpath stat truncate mktemp sfdisk mkfs.fat mcopy blkid dd python3; do command -v "$tool" >/dev/nulldoneuboot=$(realpath -e -- "$1")kernel=$(realpath -e -- "$2")dtb=$(realpath -e -- "$3")rootfs=$(realpath -e -- "$4")for input in "$uboot" "$kernel" "$dtb" "$rootfs"; do [[ -f "$input" && -s "$input" ]] || { printf 'not a nonempty regular file: %s\n' "$input" >&2 exit 1 }done(( $(stat -c %s -- "$uboot") <= 1040384 )) || { printf 'bootloader overlaps the BOOT partition\n' >&2; exit 1;}(( $(stat -c %s -- "$rootfs") <= 233832448 )) || { printf 'rootfs image exceeds 223 MiB\n' >&2; exit 1;}[[ $(blkid -p -s TYPE -o value "$rootfs") == ext4 ]] || { printf 'rootfs input is not identified as ext4\n' >&2; exit 1;}
result=$(mktemp -d "$PWD/lite200-image.XXXXXX")image="$result/Lite200.img"boot="$result/BOOT.fat"truncate -s 268435456 "$image"truncate -s 33554432 "$boot"mkfs.fat -F 16 -S 512 -h 2048 -n BOOT "$boot"mcopy -i "$boot" "$kernel" ::/zImagemcopy -i "$boot" "$dtb" ::/suniv-f1c100s-licheepi-nano.dtbsfdisk --no-reread --no-tell-kernel --wipe never "$image" <<'LAYOUT'label: dosunit: sectorssector-size: 512
start=2048, size=65536, type=0estart=67584, size=456704, type=83LAYOUT
dd if="$uboot" of="$image" bs=1024 seek=8 conv=notrunc status=nonedd if="$boot" of="$image" bs=1M seek=1 conv=notrunc status=nonedd if="$rootfs" of="$image" bs=1M seek=33 conv=notrunc status=nonesfdisk --json "$image" > "$result/partitions.json"printf 'Image: %s\nPartition metadata: %s\n' "$image" "$result/partitions.json"FAT 的隐藏扇区字段设为 2048,对应它在最终镜像中的分区起点。DTB 在 FAT 中使用固定文件名,必须让 U-Boot 的加载配置与此一致;重命名并不会把其他板卡的设备树变成 Nano 的设备树。若 FAT 空间不够,mcopy 会失败,脚本停止,留下结果目录供检查;不能忽略错误后继续发布。
为什么每次组合写入都用 conv=notrunc
跳转到“为什么每次组合写入都用 conv=notrunc”向已有普通文件写入时,dd 默认可能截断输出文件。原示例先建立 256 MiB 文件,再用没有 conv=notrunc 的命令写 U-Boot 或分区,会破坏预定的总长或先前内容。seek 只指定输出偏移,不能替代不截断选项。脚本先写分区表,再在互不重叠的区域组合内容,每次都保留已有文件尾部。
/dev/null 不是「只能写入」:写入的数据被丢弃,读取立即返回文件结束。/dev/zero 可读取零字节流,也接受并丢弃写入。dd 是按输入、输出和转换参数复制数据的工具,不能凭名字把它解释成一个通用设备驱动。
验证布局和内容
跳转到“验证布局和内容”至少核对镜像总长、MBR 签名、两个分区的起点、长度和类型,以及引导文件和两个分区镜像的写入内容。下面的 Python 3 验证器接收结果目录和原始 U-Boot、rootfs 镜像,直接解析当前镜像的 MBR 分区项,再与预期布局和生成时的 JSON 记录交叉核对,按数据段计算 SHA-256;它不执行板上启动:
import hashlibimport jsonfrom pathlib import Pathimport structimport sys
result = Path(sys.argv[1])uboot, rootfs = map(Path, sys.argv[2:4])image = result / "Lite200.img"assert image.stat().st_size == 256 * 1024**2table = json.loads((result / "partitions.json").read_text())["partitiontable"]assert table["label"] == "dos" and table["sectorsize"] == 512parts = table["partitions"]assert len(parts) == 2expected = [(2048, 65536, 0x0E), (67584, 456704, 0x83)]recorded = [(p["start"], p["size"], int(p["type"], 16)) for p in parts]with image.open("rb") as stream: mbr = stream.read(512)assert len(mbr) == 512 and mbr[510:512] == b"\x55\xaa"actual = []for index in range(2): boot, chs_start, kind, chs_end, start, count = struct.unpack_from( "<B3sB3sII", mbr, 446 + index * 16 ) assert boot == 0, "unexpected boot flag" actual.append((start, count, kind))assert mbr[478:510] == bytes(32), "unexpected third or fourth partition"assert actual == expected == recorded, "MBR/expected/JSON layout mismatch"
def digest(stream, length): value = hashlib.sha256() while length: block = stream.read(min(length, 1024 * 1024)) assert block, "unexpected end of file" value.update(block) length -= len(block) return value.digest()
for source, offset in [(uboot, 8192), (result / "BOOT.fat", 1048576), (rootfs, 34603008)]: size = source.stat().st_size with source.open("rb") as original, image.open("rb") as assembled: assembled.seek(offset) assert digest(original, size) == digest(assembled, size), source.nameprint("256 MiB layout and all three copied byte ranges verified")另用 mdir -i 结果目录/BOOT.fat ::/ 查看 FAT 内的内核与 DTB,再检查 ext4 镜像的内容与一致性。这里已用合成的引导/内核/DTB 字节和真实 ext4 测试文件运行两段代码,核对了正常组合、边界容量、错误输入、FAT 容量不足和原文件保全;也确认了保留 55AA 签名但篡改分区项,或留下不一致 JSON 时,校验会失败。没有使用真实板卡固件。打包验证只能证明文件格式和组合位置,不能证明 SPL 头部、DDR 参数、设备树或根文件系统能在真实板上启动。
旧实验通过挂载 FAT/ext4 分区、复制内核和解包 rootfs,再卸载并组合镜像,也能实现同一布局;挂载点必须明确指向对应镜像或分区,并在写入完成后正常卸载。上面的普通文件流程减少了对 loop mount 的依赖。把成品写入物理介质属于后续部署步骤,应使用该板卡已确认的烧录流程;eMMC、SD 与其他 SoC 不一定共用此布局。