1. 为什么要在嵌入式开发中自己构建Ubuntu根文件系统
做过嵌入式Linux的朋友都知道,拿到一块ARM64开发板之后,最头疼的往往不是内核编译,而是根文件系统。厂商给的BSP包里通常附带一个已经打包好的根文件系统镜像,可能是Buildroot生成的精简版,也可能是Yocto构建的定制版,但当你真正想在上面跑一些稍微复杂的东西——比如Python3的完整环境、Docker、图形界面、或者某些只提供deb包的商业软件——就会发现那个根文件系统缺胳膊少腿,缺库、缺工具、缺包管理器,补起来比重做还费劲。
这就是为什么我后来转向了用Ubuntu Base来构建ARM64根文件系统这条路。Ubuntu Base是Canonical官方提供的一个极简根文件系统压缩包,本质上就是一个能跑起来的最小Ubuntu,里面只有最基础的目录结构和apt包管理器,没有内核、没有引导程序,但你可以chroot进去,用apt随意安装你需要的任何软件包。它和桌面版Ubuntu、服务器版Ubuntu共享同一套软件源,这意味着你装出来的根文件系统,软件生态和你在PC上用的Ubuntu完全一致。
这个方案解决的核心问题是:在ARM64嵌入式设备上获得一个功能完整、可自由扩展、与主流Ubuntu生态兼容的根文件系统。它特别适合以下几类人:正在做ARM64开发板产品原型的嵌入式工程师、需要在嵌入式设备上运行复杂应用(如AI推理、视频处理、边缘计算)的开发者、以及想学习根文件系统构建原理但不想从BusyBox一步步手搓的学生和爱好者。
我这次用的环境是Ubuntu 22.04 LTS x86_64主机,目标架构是ARM64,最终根文件系统会通过QEMU验证后再部署到真实开发板上。整个过程涉及交叉架构的chroot、QEMU用户态模拟、软件源配置、系统裁剪等环节,踩过的坑不少,下面一步步拆开讲。
2. 整体方案设计与核心思路拆解
2.1 为什么选Ubuntu Base而不是Buildroot或Yocto
嵌入式领域构建根文件系统,主流方案有三个:Buildroot、Yocto、以及直接用发行版的Base包。Buildroot胜在轻量,编译出来的根文件系统可能只有几MB,适合资源极度受限的场景,但它的软件包管理很弱,想加个新软件往往要重新编译整个系统。Yocto功能强大、可定制性极高,但学习曲线陡峭,一个完整的构建动辄几小时,而且配置复杂,对新手不友好。
Ubuntu Base的优势在于平衡。它不像Buildroot那么精简,基础包解压后大约30-50MB,但换来的是完整的apt生态。你可以像在PC上一样apt install任何软件,不用重新编译整个系统。对于ARM64开发板来说,现在主流芯片(如瑞芯微RK3588、树莓派CM4、NXP i.MX8系列)的存储至少16GB起步,跑一个完整的Ubuntu根文件系统完全没压力。而且Ubuntu官方长期维护ARM64的软件源,安全更新和版本升级都有保障。
另一个关键考量是开发效率。用Buildroot时,每次加一个软件包都要改配置、重新编译、重新打包,一轮下来半小时起步。用Ubuntu Base,chroot进去apt install几秒钟搞定。在产品原型阶段,这种效率差异是决定性的。
2.2 整体构建流程的架构设计
整个构建过程可以拆成五个阶段,我用一个表格把每个阶段的输入输出和关键操作列出来:
| 阶段 | 输入 | 关键操作 | 输出 |
|---|---|---|---|
| 准备阶段 | 主机Ubuntu、网络 | 安装QEMU、debootstrap等工具 | 可用的构建环境 |
| 下载解压 | Ubuntu Base压缩包 | 下载ARM64版base包并解压 | 基础根文件系统目录 |
| 配置阶段 | 基础根文件系统 | 配置DNS、软件源、挂载点 | 可chroot的系统 |
| chroot构建 | 可chroot的系统 | QEMU模拟下apt安装软件 | 定制化的根文件系统 |
| 打包部署 | 定制化系统 | 清理、压缩、生成镜像 | 可烧录的根文件系统 |
这个流程的核心思路是在x86主机上模拟ARM64环境。因为Ubuntu Base是ARM64架构的二进制,x86主机无法直接执行里面的程序,所以需要QEMU的用户态模拟(qemu-user-static)来翻译指令。chroot配合QEMU,就能让ARM64的apt在x86主机上正常运行,这是整个方案的技术基石。
2.3 关键工具选型与理由
QEMU用户态模拟(qemu-user-static):这是让x86主机能执行ARM64二进制文件的关键。qemu-user-static是静态编译的,不依赖主机库,可以直接拷贝到目标根文件系统里使用。相比qemu-system的全系统模拟,user-static模式性能更好,因为它只翻译用户态指令,不需要模拟整个硬件。
debootstrap vs 直接解压Base包:debootstrap可以从零开始构建一个Debian/Ubuntu系统,但它需要从网络下载所有包,速度慢且依赖网络稳定。Ubuntu Base压缩包是官方预构建好的最小系统,下载解压即可用,省去了大量下载时间。我选后者,因为更快更可控。
chroot而非容器:Docker等容器方案也能实现类似效果,但chroot更轻量,不需要额外的守护进程,而且能直接操作文件系统,对嵌入式场景更直观。容器方案在需要多架构支持时配置更复杂,chroot配合QEMU的binfmt_misc注册,一条命令就能切换架构。
注意:qemu-user-static的binfmt_misc注册是全局的,注册后主机上所有ARM64二进制都会自动用QEMU执行。这在构建时很方便,但如果主机上同时有其他ARM64任务,可能会互相干扰。构建完成后可以用
update-binfmts --disable qemu-aarch64临时禁用。
3. 核心细节解析与实操要点
3.1 Ubuntu Base版本选择与下载
Ubuntu Base的官方下载地址在cdimage.ubuntu.com的ubuntu-base/releases/目录下。截至我写这篇文章时,最新的LTS版本是24.04(Noble),但22.04(Jammy)在嵌入式领域更成熟,很多BSP包和驱动都是针对22.04验证的。我建议优先选LTS版本,非LTS版本支持周期只有9个月,不适合产品开发。
下载时要注意文件名格式:ubuntu-base-22.04.4-base-arm64.tar.gz。其中arm64是架构标识,一定要确认清楚。曾经有同事误下了armhf版本,结果chroot进去所有程序都报"Exec format error",排查了半天才发现是架构不对。
下载完成后,用sha256sum校验一下完整性。官方会提供SHA256SUMS文件,校验这一步不能省,我遇到过下载过程中网络中断导致压缩包损坏的情况,解压时报错才发现。
# 下载Ubuntu Base 22.04 ARM64 wget https://cdimage.ubuntu.com/ubuntu-base/releases/22.04/release/ubuntu-base-22.04.4-base-arm64.tar.gz # 校验完整性 wget https://cdimage.ubuntu.com/ubuntu-base/releases/22.04/release/SHA256SUMS sha256sum -c SHA256SUMS --ignore-missing3.2 解压与目录结构规划
解压之前先规划好目录。我习惯在~/arm64-rootfs下操作,最终生成的根文件系统放在~/arm64-rootfs/rootfs目录。这样做的好处是构建过程中的临时文件、脚本、日志都在同一个父目录下,方便管理和清理。
解压时必须用sudo,因为Ubuntu Base里包含设备节点文件(如/dev/null),普通用户没有权限创建。解压命令用tar -xzf,注意-p参数保留权限,否则某些可执行文件的权限会丢失。
mkdir -p ~/arm64-rootfs/rootfs sudo tar -xzf ubuntu-base-22.04.4-base-arm64.tar.gz -C ~/arm64-rootfs/rootfs解压完成后,用ls看一下目录结构,应该能看到bin、etc、usr、var等标准Linux目录。这时候的根文件系统还不能直接chroot,因为缺少QEMU模拟器和必要的挂载点。
3.3 QEMU用户态模拟的配置
这是整个构建过程中最关键的一步。qemu-user-static的安装很简单,apt install qemu-user-static即可,但配置binfmt_misc才是重点。binfmt_misc是Linux内核的一个功能,它能根据文件头识别二进制格式,并自动调用对应的解释器。注册QEMU后,当你执行一个ARM64二进制时,内核会自动用qemu-aarch64-static来运行它。
安装qemu-user-static后,binfmt_misc通常会自动注册。可以用ls /proc/sys/fs/binfmt_misc/检查,如果看到qemu-aarch64文件,说明注册成功。如果没有,手动注册:
sudo update-binfmts --enable qemu-aarch64然后把qemu-aarch64-static拷贝到根文件系统的/usr/bin/下。这一步不能省,因为chroot后,根文件系统内部执行ARM64程序时,需要这个模拟器在系统内部可用。
sudo cp /usr/bin/qemu-aarch64-static ~/arm64-rootfs/rootfs/usr/bin/实操心得:有些教程会让你把qemu-aarch64-static放到
/usr/bin/下就行,但实测发现如果根文件系统的/usr/bin/权限不对,chroot后会报"Permission denied"。解压后确认一下/usr/bin/的权限是755,属主是root。
3.4 挂载点与DNS配置
chroot之前需要挂载几个关键目录,让根文件系统能正常访问主机的设备和网络:
sudo mount -t proc /proc ~/arm64-rootfs/rootfs/proc sudo mount -t sysfs /sys ~/arm64-rootfs/rootfs/sys sudo mount -o bind /dev ~/arm64-rootfs/rootfs/dev sudo mount -o bind /dev/pts ~/arm64-rootfs/rootfs/dev/pts/proc和/sys是内核提供的虚拟文件系统,apt运行时需要读取系统信息。/dev和/dev/pts是设备节点,apt安装某些包时需要访问/dev/null、/dev/random等。
DNS配置经常被忽略,但它是apt能否联网的关键。Ubuntu Base默认没有/etc/resolv.conf,需要从主机拷贝一份:
sudo cp /etc/resolv.conf ~/arm64-rootfs/rootfs/etc/resolv.conf注意,如果主机用的是systemd-resolved,/etc/resolv.conf可能是个软链接,直接拷贝会拷到链接本身。用cp -L强制拷贝实际内容,或者手动写入nameserver:
echo "nameserver 8.8.8.8" | sudo tee ~/arm64-rootfs/rootfs/etc/resolv.conf echo "nameserver 114.114.114.114" | sudo tee -a ~/arm64-rootfs/rootfs/etc/resolv.conf3.5 软件源配置与架构确认
Ubuntu Base默认的软件源是ports.ubuntu.com,这是ARM64等非x86架构的官方源。但国内访问ports源速度很慢,建议换成国内镜像。清华、中科大、阿里云都有ARM64的ports镜像。
# 备份原配置 sudo cp ~/arm64-rootfs/rootfs/etc/apt/sources.list ~/arm64-rootfs/rootfs/etc/apt/sources.list.bak # 替换为清华源 sudo tee ~/arm64-rootfs/rootfs/etc/apt/sources.list << EOF deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ jammy main restricted universe multiverse deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ jammy-updates main restricted universe multiverse deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ jammy-backports main restricted universe multiverse deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ jammy-security main restricted universe multiverse EOF这里有个细节:源地址必须用ubuntu-ports而不是ubuntu。ubuntu是x86架构的源,ARM64用不了。曾经有新手直接抄了x86的源配置,结果apt update报"404 Not Found",排查半天才发现是源地址错了。
4. 实操过程与核心环节实现
4.1 chroot进入ARM64环境
挂载和配置完成后,就可以chroot了。chroot命令本身很简单,但进入后需要做一些初始化工作:
sudo chroot ~/arm64-rootfs/rootfs /bin/bash进入后,先确认架构是否正确:
uname -m # 应该输出 aarch64如果输出的是x86_64,说明QEMU没有生效,检查/usr/bin/qemu-aarch64-static是否存在、binfmt_misc是否注册。
接下来更新软件源并安装基础工具。Ubuntu Base太精简了,连vim、ping、ifconfig都没有,先装一批常用工具:
apt update apt install -y vim net-tools iputils-ping sudo ssh systemd这里有个坑:systemd在chroot环境下无法正常启动,安装时可能会报错或卡住。如果只是构建根文件系统,可以先不装systemd,等部署到真实设备后再装。或者用apt install -y systemd --no-install-recommends跳过推荐包,减少冲突。
4.2 安装常用软件与依赖处理
根据你的应用场景,安装不同的软件包。如果是做AI推理,装Python3、numpy、opencv;如果是做视频处理,装ffmpeg、gstreamer;如果是做网络服务,装nginx、redis。
# 基础开发工具 apt install -y build-essential cmake git python3 python3-pip # 网络工具 apt install -y curl wget openssh-server # 根据需求选装 apt install -y ffmpeg python3-opencv安装过程中注意观察依赖关系。ARM64的软件包依赖和x86略有不同,某些包在ARM64上可能没有预编译版本,需要从源码编译。比如某些Python的C扩展包,pip安装时会尝试编译,如果根文件系统里没有gcc和python3-dev,就会失败。
实操心得:pip安装时加
--no-cache-dir可以节省空间,嵌入式设备存储有限,pip的缓存目录可能占几百MB。另外,如果某个包在ARM64上没有wheel,pip会从源码编译,这时候需要确保build-essential和对应的-dev包已安装。
4.3 系统裁剪与体积优化
Ubuntu Base装完常用软件后,体积可能膨胀到1-2GB。对于嵌入式设备,这个体积可以接受,但如果存储紧张,可以做一轮裁剪:
# 清理apt缓存 apt clean apt autoremove -y # 删除文档和man页 rm -rf /usr/share/doc/* rm -rf /usr/share/man/* # 删除不需要的语言包 apt install -y localepurge localepurge裁剪时要小心,不要删掉运行时需要的库。/usr/share/doc和/usr/share/man删掉没问题,但/usr/lib下的库文件不能乱动。我一般只做apt clean和autoremove,文档和man页占的空间其实不大,删了收益有限,反而可能影响某些工具的帮助功能。
4.4 用户配置与网络设置
根文件系统默认只有root用户,直接部署到设备上不太安全。创建一个普通用户并配置sudo权限:
useradd -m -s /bin/bash embed passwd embed usermod -aG sudo embed网络配置方面,Ubuntu 22.04用netplan管理网络。在根文件系统里创建/etc/netplan/01-netcfg.yaml:
network: version: 2 ethernets: eth0: dhcp4: true注意,这个配置在chroot环境下不会生效,只有部署到真实设备、systemd启动后才会应用。如果设备用的是静态IP,把dhcp4: true改成addresses和gateway4配置。
4.5 退出chroot与打包
所有配置完成后,退出chroot:
exit然后卸载之前挂载的目录,顺序很重要,先卸载子目录再卸载父目录:
sudo umount ~/arm64-rootfs/rootfs/dev/pts sudo umount ~/arm64-rootfs/rootfs/dev sudo umount ~/arm64-rootfs/rootfs/proc sudo umount ~/arm64-rootfs/rootfs/sys打包成tar.gz:
cd ~/arm64-rootfs/rootfs sudo tar -czf ../ubuntu-arm64-rootfs.tar.gz .如果要做成ext4镜像文件,可以用dd创建空镜像,然后mkfs.ext4格式化,再挂载拷贝:
dd if=/dev/zero of=rootfs.img bs=1M count=2048 mkfs.ext4 rootfs.img mkdir -p /mnt/rootfs sudo mount -o loop rootfs.img /mnt/rootfs sudo cp -a ~/arm64-rootfs/rootfs/* /mnt/rootfs/ sudo umount /mnt/rootfs4.6 QEMU全系统验证
打包完成后,强烈建议用QEMU全系统模拟验证一下根文件系统能否正常启动。这一步能提前发现很多问题,比如init程序缺失、库依赖错误、网络配置问题等。
qemu-system-aarch64 \ -M virt \ -cpu cortex-a57 \ -smp 2 \ -m 1024 \ -kernel Image \ -append "root=/dev/vda rw console=ttyAMA0" \ -drive file=rootfs.img,format=raw,if=virtio \ -nographic这里的Image是ARM64内核镜像,需要提前编译好。如果只是验证根文件系统,可以用Ubuntu官方提供的ARM64内核。启动后如果能看到登录提示,说明根文件系统基本可用。
5. 常见问题与排查技巧实录
5.1 chroot报"Exec format error"
这是最常见的问题,原因通常是QEMU没有正确配置。排查步骤:
- 确认
/usr/bin/qemu-aarch64-static存在于根文件系统中 - 确认主机上binfmt_misc已注册:
ls /proc/sys/fs/binfmt_misc/qemu-aarch64 - 确认根文件系统的
/usr/bin/qemu-aarch64-static有可执行权限 - 确认下载的Ubuntu Base是arm64版本,不是armhf或amd64
如果以上都正常,尝试重新注册binfmt_misc:
sudo update-binfmts --disable qemu-aarch64 sudo update-binfmts --enable qemu-aarch645.2 apt update报"404 Not Found"
软件源地址错误。ARM64必须用ports.ubuntu.com或对应的国内ports镜像,不能用普通的archive.ubuntu.com。检查sources.list里的地址是否包含ubuntu-ports。
另一个可能原因是Ubuntu版本代号写错了。22.04的代号是jammy,20.04是focal,24.04是noble。写错了会报404。
5.3 apt install报"Unable to locate package"
先确认apt update是否成功执行。如果update成功但install报找不到包,可能是该包在ARM64上没有预编译版本。用apt search查一下,或者去packages.ubuntu.com上确认该包的ARM64支持情况。
某些包只在universe或multiverse仓库里,确认sources.list包含了这些仓库。
5.4 根文件系统启动后卡在"Waiting for root device"
这是部署到真实设备时常见的问题,原因通常是内核没有对应的存储驱动,或者root=参数指定的设备不对。检查内核配置里是否编译了对应的驱动(如virtio_blk、mmc_block、nvme等),以及/etc/fstab里的设备名是否正确。
如果是NFS挂载根文件系统,检查内核是否支持NFS,以及root=/dev/nfs nfsroot=...参数是否正确。NFS v3和v4的配置略有不同,v3需要nfsvers=3参数。
5.5 常见问题速查表
| 问题现象 | 可能原因 | 解决方法 |
|---|---|---|
| Exec format error | QEMU未配置 | 检查binfmt_misc和qemu-aarch64-static |
| apt update 404 | 源地址错误 | 改用ubuntu-ports源 |
| 找不到软件包 | 架构不支持 | 确认包的ARM64支持情况 |
| 启动卡住 | 内核驱动缺失 | 检查存储驱动和root参数 |
| 网络不通 | netplan配置错误 | 检查yaml格式和网卡名 |
| 权限拒绝 | 文件属主错误 | 用chroot --userspec或修复权限 |
避坑技巧:构建过程中所有操作都用
sudo,但解压后的文件属主是root,如果你在主机上直接编辑根文件系统里的文件,可能会改变属主。建议所有编辑操作都在chroot内部用vim进行,或者用sudo编辑。
6. 部署到真实开发板的注意事项
根文件系统构建完成后,部署到真实开发板还有几个关键点。首先是内核匹配,根文件系统里的库和工具是针对特定glibc版本编译的,如果开发板内核太老,可能不支持某些系统调用。建议用和根文件系统同版本的内核源码编译内核。
其次是存储介质,eMMC、SD卡、NAND Flash的驱动不同,确认内核里编译了对应的驱动。如果是通过U-Boot引导,bootargs里的root=参数要指向正确的分区。
最后是首次启动配置,根文件系统第一次启动时会生成SSH密钥、初始化systemd,这个过程可能需要几分钟。如果设备没有串口输出,可能会以为卡死了,其实是在后台初始化。耐心等一会儿,或者接串口看日志。
我在实际项目里用这套方案构建的根文件系统,已经稳定运行在RK3588和i.MX8M Plus两块开发板上,跑Python AI推理和ffmpeg视频转码都没问题。唯一需要注意的是,每次更新软件包后要重新打包,版本管理要做好,不然容易出现"这台设备能跑那台跑不了"的情况。