news 2026/10/28 1:12:51

Ubuntu Base 构建 ARM64 根文件系统实战:从 chroot 到启动验证

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Ubuntu Base 构建 ARM64 根文件系统实战:从 chroot 到启动验证

1. 为什么我要用 Ubuntu Base 而不是 debootstrap 从头搓一个 ARM64 根文件系统

做嵌入式 Linux 的朋友大概率都经历过这个阶段:拿到一块 ARM64 开发板,内核编译通过了,串口能打印启动日志了,结果卡在最后一步——VFS: Cannot open root device或者Kernel panic - not syncing: No working init found。说白了就是根文件系统没搞对。很多人第一反应是去用 Buildroot 或者 Yocto,这俩确实成熟,但如果你只是想要一个"能跑起来、能装包、能调试"的 Ubuntu 环境,用 Buildroot 配半天 busybox 反而绕远了。

我这次的需求很明确:给一块 ARM64 开发板(具体芯片就不点名了,反正就是常见的那些)跑一个完整的 Ubuntu 根文件系统,要求能apt install、能跑 Python、能编译小程序,而不是一个只有 busybox 的极简环境。试过几条路之后,最终选了Ubuntu Base这个官方提供的 tar.gz 根文件系统压缩包作为起点。它本质上是 Ubuntu 官方为容器和嵌入式场景准备的一个最小化根文件系统,解压出来大概几十 MB,里面已经有 apt、dpkg、bash 这些基础设施,比 debootstrap 从零构建省事太多,也比直接拿 Docker 镜像导出干净。

这篇文章我会把整个流程拆开讲清楚:从 Ubuntu Base 的获取、解压、chroot 进去装软件,到交叉环境下的注意事项,再到打包成镜像、挂载到开发板验证。中间踩过的坑、参数为什么这么设、哪些步骤可以偷懒哪些绝对不能省,我都会说透。适合已经会编译内核、但对根文件系统构建还比较模糊的嵌入式开发者,也适合想理解"一个 Linux 发行版根目录到底由什么组成"的进阶学习者。

先说结论:Ubuntu Base 这条路的核心优势是快和全。快是因为它跳过了 debootstrap 的包下载和安装阶段,全是因为它自带完整的 dpkg/apt 体系,后续扩展能力吊打 busybox。但它也有代价——体积比 Buildroot 产物大,启动依赖比纯静态环境多,这些后面会细说。

2. Ubuntu Base 到底是个什么东西,和 debootstrap、Docker 镜像有什么区别

2.1 Ubuntu Base 的本质:一个预构建的最小根文件系统 tarball

Ubuntu Base 官方发布在 cdimage.ubuntu.com 的ubuntu-base/releases/目录下,命名格式类似ubuntu-base-24.04-base-arm64.tar.gz。你把它解压到某个目录,得到的目录结构就是标准的 Linux 根目录:/bin、/etc、/lib、/usr、/var一应俱全。它和你在 PC 上装的 Ubuntu 是同一套包管理体系,只是预装的包被裁剪到了极限——没有 systemd 的完整服务集、没有图形界面、没有多余文档。

关键点在于它是按架构分发的。你要 ARM64 就下arm64版本,要 ARMHF 就下armhf,要 x86_64 就下amd64。这一点非常重要,因为根文件系统里的二进制文件(比如/bin/bash、/usr/bin/apt)都是对应架构的 ELF 可执行文件,架构不对根本跑不起来。我见过有人图省事下了 amd64 的包往 ARM 板子上怼,结果内核启动到 init 阶段直接报Exec format error,排查半天才发现是架构错了。

2.2 和 debootstrap 的对比:省掉的是"从零组装"的苦力活

debootstrap 的工作方式是:你给它一个目标目录和一个 Ubuntu 版本代号,它去镜像源把base-files、dpkg、apt这些基础包一个个下载下来,用dpkg-deb解包,再 chroot 进去配置。整个过程依赖网络,速度取决于源的质量,而且中间任何一步失败都得重来。

Ubuntu Base 相当于官方帮你把 debootstrap 的第一阶段做完了,直接给你成品。你拿到手就能 chroot 进去继续装东西。省掉的时间不是一点半点——debootstrap 在慢速网络下跑一两个小时很正常,Ubuntu Base 下载加解压十分钟搞定。

但要注意,Ubuntu Base 的包版本是冻结在发布时刻的。你解压完第一件事应该是apt update && apt upgrade,否则装新包时可能遇到依赖版本对不上的问题。这个后面会专门讲。

2.3 和 Docker 镜像的区别:别直接拿 docker export 糊弄

有人会想,Docker 的ubuntu:24.04镜像不也是个根文件系统吗,docker export出来不就行了?理论上可以,但实际用起来坑不少。Docker 镜像的层结构、/etc/hosts、/etc/resolv.conf这些运行时注入的文件,以及默认的PATH环境变量,都是为容器场景优化的,直接搬到裸机上会出现各种诡异问题。比如 Docker 镜像里/etc/resolv.conf可能是个符号链接指向不存在的位置,导致网络解析失败。

Ubuntu Base 是专门为"解压即用"设计的,没有容器运行时的那层包袱,更适合嵌入式场景。所以我的建议是:老老实实用 Ubuntu Base,别在 Docker 导出这条路上浪费时间。

3. 在 x86 主机上准备 ARM64 根文件系统的完整操作链路

3.1 环境准备:主机需要装哪些工具

我的开发主机是 x86_64 的 Ubuntu,要在上面构建 ARM64 的根文件系统,需要几个关键工具:

  • qemu-user-static:提供qemu-aarch64-static,让 x86 主机能直接执行 ARM64 的二进制文件。这是 chroot 能工作的前提。
  • binfmt-support:配合 qemu 注册二进制格式,让内核知道遇到 ARM64 ELF 时该调用 qemu 来执行。
  • wget或curl:下载 Ubuntu Base 包。
  • tar:解压,注意要支持 xz 或 gzip。

安装命令很简单:

sudo apt update sudo apt install qemu-user-static binfmt-support wget tar

装完验证一下 binfmt 是否注册成功:

ls /proc/sys/fs/binfmt_misc/ | grep aarch64

如果看到qemu-aarch64这个条目,说明注册好了。这一步没做的话,后面 chroot 进去执行任何 ARM64 命令都会报Exec format error,这是新手最容易卡住的地方。

3.2 下载与解压:目录结构和权限的坑

下载对应版本的 Ubuntu Base:

wget https://cdimage.ubuntu.com/ubuntu-base/releases/24.04/release/ubuntu-base-24.04-base-arm64.tar.gz

解压到一个干净目录,比如~/arm64-rootfs:

mkdir -p ~/arm64-rootfs sudo tar -xpf ubuntu-base-24.04-base-arm64.tar.gz -C ~/arm64-rootfs

这里有几个细节必须注意。第一,tar一定要加-p参数保留权限位,因为根文件系统里有些文件(比如/usr/bin/sudo)需要 setuid 位,丢了权限后续 sudo 就用不了。第二,必须用sudo解压,否则设备文件、特殊权限文件创建会失败。第三,解压完检查一下/etc/passwd和/etc/group是否存在,这是判断解压是否完整的最快方法。

解压后目录大小大概在 100MB 出头,具体取决于版本。如果你解压出来只有几 MB,那肯定是下错包了(比如下成了某个增量包)。

3.3 配置 chroot 环境:resolv.conf、apt 源、挂载点

在 chroot 之前,有几件事必须先做,否则进去之后网络不通、apt 用不了。

第一,拷贝 DNS 配置:

sudo cp /etc/resolv.conf ~/arm64-rootfs/etc/resolv.conf

第二,配置 apt 源。Ubuntu Base 自带的sources.list可能是空的或者指向不合适的地址,需要根据你的网络环境替换。国内环境建议换成对应的镜像源,格式是deb http://<mirror>/ubuntu-ports/ noble main restricted universe multiverse。注意 ARM64 架构用的是ubuntu-ports而不是ubuntu,这个区别很多人不知道,写错了会报 404。

第三,挂载必要的虚拟文件系统。chroot 进去之后,/proc、/sys、/dev这些目录需要从主机挂载进去,否则很多命令会报错:

sudo mount --bind /dev ~/arm64-rootfs/dev sudo mount --bind /proc ~/arm64-rootfs/proc sudo mount --bind /sys ~/arm64-rootfs/sys sudo mount --bind /dev/pts ~/arm64-rootfs/dev/pts

这几条挂载是临时的,用完记得卸载,否则可能影响主机。我一般会写个脚本统一处理挂载和卸载,避免忘记。

3.4 chroot 进去之后的第一批操作

挂载完成后就可以 chroot 了:

sudo chroot ~/arm64-rootfs /bin/bash

进去之后先确认架构:

uname -m

如果输出aarch64,说明 qemu 正常工作。如果输出x86_64,那说明 binfmt 没生效,得退出去检查。

接下来第一批操作:

apt update apt upgrade -y apt install -y sudo vim net-tools iputils-ping systemd

这里systemd是否安装取决于你的启动方式。如果你打算用 systemd 作为 init,那必须装;如果打算用简单的 shell 脚本或者 busybox init,可以不装。我这次选了 systemd,因为 Ubuntu 的很多包默认依赖它。

装完 systemd 后,需要设置默认启动目标,嵌入式场景一般用multi-user.target:

systemctl set-default multi-user.target

4. 交叉环境下的软件安装与配置,哪些包必须装哪些是坑

4.1 基础工具链:别在根文件系统里装编译器

一个常见的误区是在根文件系统里装gcc、make这些编译工具。除非你的开发板性能足够强、存储足够大,否则没必要。根文件系统应该保持精简,编译工作在你的 x86 主机上用交叉工具链完成,编译好的二进制再拷进去。

但有些包是必须装的,我列个清单:

包名用途是否必须
sudo权限管理必须
vim 或 nano文本编辑必须
net-toolsifconfig 等网络工具建议
iputils-pingping 命令建议
openssh-server远程登录建议
systemd初始化系统视情况
python3脚本运行视情况

openssh-server这个特别值得说。嵌入式开发板通常没有显示器,串口调试又慢,装个 SSH 之后直接在主机上远程操作,效率提升巨大。但装完要配置/etc/ssh/sshd_config,允许 root 登录(或者建个普通用户),否则默认配置下 root 是登不上去的。

4.2 用户与密码:root 密码和普通用户的创建

Ubuntu Base 解压出来默认 root 是没有密码的,直接 chroot 进去就是 root。但一旦系统启动,没有密码的 root 会导致登录失败。所以必须在 chroot 里设置密码:

passwd root

如果要做正式产品,建议创建一个普通用户并加入 sudo 组:

useradd -m -s /bin/bash myuser passwd myuser usermod -aG sudo myuser

这里有个坑:useradd不会自动创建家目录,必须加-m参数。另外,Ubuntu 的 sudo 组默认在/etc/sudoers里配置好了,但如果你用的是其他发行版的 base,可能需要手动编辑 sudoers 文件。

4.3 网络配置:systemd-networkd 还是 NetworkManager

嵌入式场景我推荐用systemd-networkd,比 NetworkManager 轻量得多。配置方法是创建/etc/systemd/network/eth0.network:

[Match] Name=eth0 [Network] DHCP=yes

然后启用服务:

systemctl enable systemd-networkd systemctl enable systemd-resolved

注意网卡名字不一定是eth0,具体取决于你的内核和设备树配置。可以在启动后用ip link查看实际名字。如果网卡名字是end0之类的,配置文件里的Name要对应改。

DNS 解析依赖systemd-resolved,它会把/etc/resolv.conf变成一个符号链接指向/run/systemd/resolve/stub-resolv.conf。如果你之前手动拷贝了resolv.conf,启用 resolved 后可能会冲突,需要删掉手动拷贝的那个。

4.4 清理与瘦身:删掉不需要的东西

装完必要的包之后,根文件系统会膨胀不少。可以清理一下:

apt clean apt autoremove -y rm -rf /var/lib/apt/lists/* rm -rf /tmp/*

/var/lib/apt/lists里的包索引文件可能占几十 MB,删掉不影响使用,下次apt update会重新生成。/tmp里的临时文件也清掉。

但要注意,apt clean只是清理下载的 deb 缓存,已经安装的包不受影响。如果你追求极致体积,可以用dpkg-query列出所有包,手动删掉确实不需要的,但这个操作风险较高,容易破坏依赖关系,新手不建议。

5. 打包成可启动镜像并挂载到开发板验证

5.1 从目录到镜像:tar 打包还是直接做 ext4

根文件系统目录准备好之后,有两种方式送到开发板:

第一种是打包成 tar.gz,通过 NFS 或者 U 盘拷到开发板,在开发板上解压到某个分区。这种方式适合调试阶段,改起来方便。

第二种是直接在主机上做成 ext4 镜像文件,然后 dd 到 SD 卡或者 eMMC。这种方式适合量产。

做 ext4 镜像的步骤:

dd if=/dev/zero of=rootfs.ext4 bs=1M count=2048 mkfs.ext4 rootfs.ext4 mkdir -p /mnt/rootfs sudo mount -o loop rootfs.ext4 /mnt/rootfs sudo cp -a ~/arm64-rootfs/* /mnt/rootfs/ sudo umount /mnt/rootfs

count=2048表示 2GB 大小,根据你的实际需求调整。cp -a保留权限和符号链接,不能用普通的cp -r。

5.2 内核启动参数:root= 和 init= 怎么设

开发板内核启动时,需要告诉它根文件系统在哪里。这通过bootargs传递,典型配置:

root=/dev/mmcblk0p2 rootwait rw console=ttyS0,115200

root=指向根文件系统所在的分区,rootwait让内核等待设备就绪(SD 卡和 eMMC 需要),rw表示可读写挂载。如果根文件系统在 NFS 上,配置会复杂一些,需要nfsroot=和ip=参数。

init=一般不用显式指定,内核会按/sbin/init、/etc/init、/bin/init、/bin/sh的顺序找。装了 systemd 的话,/sbin/init就是 systemd 的符号链接,能正常启动。

5.3 首次启动常见报错与排查

第一次启动大概率不会一帆风顺,我列几个我遇到过的:

报错一:Kernel panic - not syncing: No working init found

原因通常是 init 程序找不到或者架构不对。排查方法:确认/sbin/init存在且是 ARM64 的 ELF 文件,用file命令检查。如果/sbin/init是符号链接,确认链接目标存在。

报错二:VFS: Cannot open root device

根分区找不到。检查bootargs里的root=是否指向正确的分区,用lsblk在开发板上确认分区名。有时候 SD 卡和 eMMC 的设备名会变,比如mmcblk0和mmcblk1。

报错三:启动到一半卡住,没有任何输出

可能是串口波特率不对,或者内核根本没跑到根文件系统挂载阶段。先确认内核本身能启动,再排查根文件系统。

报错四:systemd[1]: Failed to mount /sys之类的挂载失败

通常是/etc/fstab配置有问题。Ubuntu Base 默认的 fstab 可能是空的或者指向不存在的设备,需要根据实际情况修改,或者干脆清空让 systemd 自动处理。

5.4 验证清单:启动后必须检查的几项

系统起来之后,别急着庆祝,按这个清单过一遍:

  • uname -a确认内核版本和架构
  • ip addr确认网卡是否拿到 IP
  • ping外网确认网络通
  • apt update确认包管理可用
  • df -h确认根分区挂载正常且空间够用
  • systemctl status确认关键服务没有 failed

这几项都过了,才算一个可用的根文件系统。

6. 几个让我折腾半天的坑,以及我的最终建议

6.1 qemu 版本和内核 binfmt 的兼容性问题

有一次我在一台老主机上操作,qemu-user-static装的是比较老的版本,chroot 进去执行 ARM64 命令时随机崩溃,报qemu: uncaught target signal 11。排查了很久才发现是 qemu 版本和内核 binfmt 机制不兼容。解决办法是升级 qemu 到较新版本,或者用update-binfmts手动重新注册。

这个坑的教训是:构建环境尽量用较新的 Ubuntu 版本,别在太老的主机上折腾。我后来统一用 Ubuntu 22.04 或 24.04 作为构建主机,再没遇到过这个问题。

6.2 apt 源架构写错导致的 404

前面提过,ARM64 用的是ubuntu-ports而不是ubuntu。我一开始没注意,sources.list里写了http://mirrors.xxx.com/ubuntu/,结果apt update一直报 404。改成ubuntu-ports之后立刻正常。这个错误的迷惑性在于,浏览器里访问那个 URL 是能打开的,只是目录结构不对,所以不容易一眼看出来。

6.3 符号链接在打包时被解引用

用tar打包根文件系统时,如果不加-h或-P相关参数,符号链接可能被解引用成实际文件,导致体积暴涨或者链接关系丢失。正确做法是用tar -cpf保留符号链接。同理,cp命令要用-a而不是-r。

6.4 我的最终建议

如果你只是想在开发板上快速跑起一个 Ubuntu 环境做验证,Ubuntu Base 这条路是最省事的。但如果你要做正式产品,需要考虑体积、启动速度、安全性,那 Buildroot 或 Yocto 可能更合适。Ubuntu Base 的定位是"快速原型"和"调试环境",别拿它当量产方案。

另外,构建过程建议脚本化。我把下载、解压、挂载、chroot、装包、清理、打包这一整套写成了一个 shell 脚本,每次改配置重新跑一遍就行,比手动操作可靠得多。脚本里记得加错误检查,任何一步失败就退出,避免在半成品上继续操作。

最后分享一个小技巧:在 chroot 环境里装包时,可以用apt install --no-install-recommends避免装一堆推荐包,能显著减小体积。这个参数在嵌入式场景特别有用,因为很多推荐包在开发板上根本用不到。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/28 1:12:23

STM32寄存器学习分水岭:GPIO寄存器深度解析与实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/28 1:12:22

双闭环直流调速系统带载振荡的模糊自适应PID解法

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/28 1:11:38

STM32F1与DHT11实战:从零搭建温湿度监测系统

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/28 1:11:28

量产烧录一致性:从翻车案例到全流程工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/28 1:10:48

模糊自适应PID双闭环直流调速系统Simulink实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/28 1:09:56

芯片时序路径分析入门:四类路径与PrimeTime约束实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华