树莓派 5 内核交叉编译速通——为 HM-SMR 硬盘启用 zoned 支持
树莓派 5 内核交叉编译速通——为 HM-SMR 硬盘启用 zoned 支持
这是Ultrastar DC HC620 分析与实战——树莓派 5 驱动主机管理式 SMR 硬盘的续篇。前文提过,Raspberry Pi OS 官方内核的 bcm2712_defconfig 没有启用 CONFIG_BLK_DEV_ZONED,主机管理式叠瓦(Host-Managed SMR,HM-SMR)盘接上以后,内核会直接拒绝为它创建块设备节点。所以想用 HC620,自己编译内核这一步省不掉。
本文的做法是在一台 x86_64 的 Ubuntu Desktop 26.04 虚拟机里交叉编译树莓派内核,打好包传到 Pi 5 上安装。官方内核、设备树和 cmdline.txt 全部原样保留,只在 config.txt 末尾加一行 os_prefix 做启动选择,回滚时删掉这一行重启就行。
环境
| 角色 | 配置 |
|---|---|
| 编译机 | Ubuntu Desktop 26.04 虚拟机,x86_64,8 vCPU / 8 GB 内存 |
| 目标机 | Raspberry Pi 5,Raspberry Pi OS(64-bit) |
| 内核源码 | raspberrypi/linux,分支 rpi-6.18.y |
分支跟着目标机走,不用特意挑。在 Pi 上执行 uname -r,看当前内核属于哪个系列,目标机为 6.18.34+rpt-rpi-2712,选同系列的分支即可。本文写作时,rpi-6.18.y 的最新版本是 6.18.39 Commit 820b5b663。
1 编译机准备
装工具链和内核构建依赖:
1 | sudo apt update |
crossbuild-essential-arm64 会带上 aarch64-linux-gnu-gcc 全套交叉工具链。
2 获取源码
只要当前分支最新提交,浅克隆省时间省空间:
1 | git clone --depth=1 --branch rpi-6.18.y https://github.com/raspberrypi/linux.git |
3 配置
先应用官方默认配置,再用 scripts/config 脚本修改差异。这种方式比在 menuconfig 中逐项翻菜单更容易复现,后续跟版重编时直接重放这几条命令即可。
1 | make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- bcm2712_defconfig |
各项配置的作用如下:CONFIG_BLK_DEV_ZONED 是通用块层的 zoned 支持开关,启用后 SCSI 磁盘驱动中的 ZBC(Zoned Block Commands)支持会随之构建。CONFIG_DM_ZONED 与 CONFIG_ZONEFS_FS 是两条可选的上层使用路径,本文编译为模块,按需加载。btrfs 的 zoned 模式没有单独的配置项,只需启用普通的 CONFIG_BTRFS_FS,本文同样显式设为模块,避免依赖未来版本的 defconfig。
页大小方面,Pi 5 的 bcm2712_defconfig 默认使用 16K 页。启用 zoned block support 本身并不要求改成 4K,本文仍选择 4K 页。除了与前文已验证的 btrfs、dm-zoned 和用户态工具环境保持一致、减少排障时的变量之外,还有两个更实际的考虑。
先看 16K sectorsize 的情况。常规配置的 6.18 系内核中,16K 页下的 Btrfs 支持 4K 和 16K 两种 sectorsize。文件系统一旦使用 16K sectorsize,普通的 4K 页内核就无法直接挂载,发生故障时不能把硬盘接到常见的 x86_64 PC 上直接救援。Linux 6.18 开始提供“block size 大于 page size”的基础支持,启用 CONFIG_BTRFS_EXPERIMENTAL 的 4K 页内核可以处理 16K sectorsize,但这条路径仍属实验功能,存在多项限制,不适合作为常规救援手段。
再看 4K sectorsize 的情况。在 16K 页内核上使用 4K sectorsize 时,普通 Btrfs 会进入 subpage 模式。该模式从 Linux 6.15 起移除了实验性警告,到 6.18 已经相对完整并经过测试,但原生 btrfs zoned 与 subpage blocksize 的组合尚未实现。要让 Btrfs 以原生 zoned 模式运行在 HM-SMR 设备上,sectorsize 仍需与内核页大小保持一致。把 Pi 5 内核改成 4K 页后,既可以走原生的 4K sectorsize 路径,也保留了把硬盘接到普通 4K 页 Linux PC 上直接挂载和救援的能力。
如果走 dm-zoned 路径,Btrfs 面对的是 dm-zoned 暴露出来的普通块设备,不会进入原生 btrfs zoned 模式,此时 16K 页加 4K sectorsize 的普通 subpage 路径在 6.18 中已经可用。不过,为了统一前文的三条实验路径并保留跨机器救援的兼容性,本文统一采用 4K 页。保留默认的 16K 页也可行,但需要根据实际使用的原生 btrfs zoned、dm-zoned 或 zonefs 路径分别做实机验证。
LOCALVERSION 用于给自编内核设置独立名称,使其模块安装到单独的 /lib/modules/<kernelrelease>/ 目录中,不与官方内核模块混用。
修改完成后验证一遍,注意确认 16K 页确实已关闭。
1 | grep -E 'CONFIG_LOCALVERSION=|CONFIG_ARM64_(4K|16K)_PAGES|CONFIG_BLK_DEV_ZONED|CONFIG_DM_ZONED|CONFIG_BTRFS_FS=|CONFIG_ZONEFS_FS' .config |
预期的关键配置行如下:
1 | CONFIG_LOCALVERSION="-v8-4k-zoned" |
4 编译
1 | make -j$(nproc) ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- Image.gz modules dtbs |
编译完成后记录完整内核版本号(基础版本 + LOCALVERSION),后续验证环节需要与其对照:
1 | make -s ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- kernelrelease |
在 8 vCPU / 8 GB 内存的虚拟机上,本次编译用时约 17 分钟。
5 打包产物
将自编内核的全部启动产物放入独立的 zoned/ 目录,后续通过固件的 os_prefix 机制按目录整体切换,不覆盖官方内核、设备树、overlays 和 cmdline.txt:
1 | rm -rf dist |
Image.gz 直接改名后即可作为树莓派固件可引导的内核镜像,固件根据文件内容判断压缩格式,不依赖扩展名。镜像命名为 kernel_2712.img,因为这是 Pi 5 固件的默认查找名,配合 os_prefix 使用就无需再添加 kernel= 配置行。INSTALL_MOD_STRIP=1 用于去除模块的调试符号,可以显著减小模块体积。
将压缩包传到 Pi:
1 | scp pi-kernel-zoned.tar.gz <树莓派用户名>@<树莓派地址>:~/ |
6 树莓派上安装
解包并安装。这组命令采用先删后装的方式,首次安装、同版本重编和跟版更新都适用,不会残留旧文件:
1 | rm -rf dist && mkdir dist |
/boot/firmware/config.txt 末尾追加:
1 | [all] |
[all] 用于清除 config.txt 中此前可能生效的条件过滤段(如 [cm4]、[pi4]),保证这行配置对 Pi 5 生效。固件会到 zoned/ 目录中按默认名称查找内核、设备树、overlays 和 cmdline.txt,原有的对应文件均保持不变。固件启动时会先检查 zoned/ 中是否存在预期的内核和设备树,关键文件缺失时会忽略该前缀并回退到原有启动文件。这是一层基本保护,但并不等于事务式更新。如果复制中途断掉,缺失的又只是 overlays 或 cmdline.txt,前缀仍可能通过检查,系统会带着不完整的文件启动。因此重启前应当确认关键文件全部就位:
1 | if test -s /boot/firmware/zoned/kernel_2712.img && |
有两点需要注意。第一,zoned/cmdline.txt 是安装时复制的快照,之后只要修改了 /boot/firmware/cmdline.txt,例如根分区 PARTUUID、串口控制台、cgroup 或其他内核启动参数,都要重新复制一份到 zoned/。第二,本文测试机从 SD 卡上的 ext4 根分区启动,相关存储与文件系统驱动均为内置,实测不需要额外制作 initramfs。如果根分区使用了 btrfs、LUKS、LVM 或特殊存储控制器,或者现有系统本身依赖 initramfs,则需要为自编内核生成匹配的 initramfs 并放入 zoned/(auto_initramfs=1 时固件按 kernel_2712.img 在同目录查找 initramfs_2712)。

不使用 initramfs 引导还存在一个连带问题。内核直接挂载的根文件系统在 /proc/mounts 中显示为 /dev/root,这并不是一个真实存在的设备节点。Pi OS 默认的 MODULES=dep 模式要求 update-initramfs 能够解析根设备,因此在运行自编内核期间,任何触发 initramfs 重建的 apt 操作,例如安装带有钩子的软件包或更新官方内核,都会报出 mkinitramfs: failed to determine device for / 错误并中断执行,dpkg 会因此停留在未配置完成的状态。解决办法是修改 /etc/initramfs-tools/initramfs.conf,将 MODULES=dep 改为 MODULES=most,然后执行 sudo dpkg --configure -a 完成剩余的配置。这一修改的唯一影响是官方内核的 initramfs 体积会略微增大。
重启:
1 | sudo reboot |
7 验证
重启完成后,先确认内核版本与页大小:
1 | uname -r # 应与第4节 kernelrelease 的输出一致 |
HC620 应出现在列表中,ZONED 列显示 host-managed。随后针对具体设备做进一步确认。设备名以 lsblk 的实际输出为准,经 USB-SATA 桥接时不一定是 sda:
1 | cat /sys/block/<设备名>/queue/zoned # 预期 host-managed |

最后确认 dm-zoned 与 zonefs 两个模块的来源:
1 | modinfo -n dm-zoned zonefs |
输出的两条路径都应位于当前 uname -r 对应的 /lib/modules/<内核版本>/ 目录下,本文实测示例为 /lib/modules/6.18.39-v8-4k-zoned+/。验证完成后,分区与格式化操作与前文一致,直接参照执行即可。
8 回滚
如需恢复官方内核,注释或删除 config.txt 中的 os_prefix=zoned/ 一行并重启即可。官方内核、官方设备树和官方 cmdline.txt 全程未做改动,删除前缀后系统会自动回到原有启动文件,保留 [all] 行没有影响。无论编译配置有误还是新版本存在问题,回滚成本仅为一次重启,这是本方案在可维护性上最大的优势。
如果系统已经无法引导,可将 SD 卡插入其他机器直接修改 config.txt。boot 分区为 FAT 格式,在任意操作系统上均可读写。
9 跟版更新
需要先明确更新机制。用户态软件包仍会通过 apt 正常接收安全更新,apt 也会照常更新官方内核文件(os_prefix 目录之外的部分),但正在引导的自编内核不会随之自动升级。内核的漏洞修复与版本合并需要手动跟版、重新编译并安装。具体流程为重复上述步骤:
1 | cd linux |
必须完整重放上述步骤,包括 bcm2712_defconfig 这一步,不要沿用旧版 .config。官方 defconfig 在新版本中可能存在增删,直接沿用旧配置会偏离“官方默认加少量差异”的原则,配置差异会随版本推移不断累积。
模块目录以完整内核版本号(基础版本 + LOCALVERSION)命名,基础版本从 6.18.39 更新到 6.18.40 后,模块会安装到新目录而非覆盖旧目录。确认旧自编内核不再用于引导后,可手动删除对应的 /lib/modules/ 目录。官方内核的模块目录必须始终保留,回滚操作依赖这些文件。重复构建同一基础版本则无需特殊处理,第六节的安装命令本身采用先删后装的方式,不会残留已从配置中移除的旧模块。
建议将第3至5节的命令整理为脚本并纳入 dotfiles 管理,跟版时执行一次即可完成打包。可定期检查 rpi-6.18.y 分支是否发布了新的补丁版本,遇到安全公告或重要修复时及时重新编译。
结语
到这里,从配置、编译、打包到安装、验证、回滚的完整流程已经完成。整套方案的核心只有两点:用 scripts/config 显式表达与官方 defconfig 的差异,用 os_prefix 把自编内核的启动文件整体隔离。前者保证跟版重编可以逐条复现,后者保证任何环节出问题都能用一次重启退回官方内核。