1. 为什么需要从零构建ARM Ubuntu根文件系统如果你玩过树莓派、香橙派或者各种国产的开发板肯定遇到过这样的问题板子自带的系统镜像版本太老软件仓库早就停止更新了想装个新点的软件包都找不到。或者系统里预装了一大堆你用不上的软件什么办公套件、影音播放器把宝贵的存储空间和内存占得满满的对于嵌入式开发来说这简直就是“负重训练”。我自己就踩过不少坑。早些年拿到一块开发板兴致勃勃地准备开始项目结果发现系统是Ubuntu 14.04Python版本还是2.7很多现代的开发库根本装不上。想升级官方没提供新镜像。自己编译内核和驱动已经够头疼了还要折腾整个根文件系统想想就劝退。所以今天我想跟你分享的就是如何彻底摆脱这种束缚。从零构建一个ARM架构的Ubuntu根文件系统听起来很高大上其实就像自己动手组装一台电脑的操作系统只不过我们是在为ARM芯片“量身定做”。它的核心价值在于“定制化”和“可控性”。你可以自由选择Ubuntu的版本比如最新的22.04 LTS确保软件源新鲜你可以只安装最基础的命令行工具打造一个极简、高效的纯开发环境你也可以预先配置好网络、用户、开发工具链做到开箱即用。更重要的是这个过程不依赖于任何现成的、可能过时的板卡厂商镜像。你只需要一个能运行Linux的主机比如你的Ubuntu虚拟机或另一台Linux电脑以及目标存储设备SD卡、eMMC或硬盘就能亲手打造一个干净、现代、完全符合你项目需求的“地基”。这个“地基”就是根文件系统。它包含了系统运行所必需的所有目录结构、配置文件、基础命令和库文件。内核启动后最终会挂载这个根文件系统并执行其中的第一个程序通常是/sbin/init我们的定制化系统就此诞生。接下来我们就一步步拆解这个构建过程。2. 构建前的核心准备工具与分区规划动手之前得把“厨房”收拾好。这里说的厨房就是你的构建环境。首先明确一个关键原则构建系统的主机环境和目标系统的存储介质必须是分开的。你不能在正在运行的Ubuntu系统上去构建一个要覆盖它自己的根文件系统那等于自己拆自己的房子。通常我们是在一个x86的Ubuntu虚拟机或物理机上为ARM架构的设备构建系统最终的系统文件会存放在一个单独的目录里最后再拷贝到SD卡等设备上。2.1 安装核心工具debootstrap整个构建过程的灵魂工具是debootstrap。你可以把它理解为一个“系统下载和组装器”。它不会去下载一个完整的、臃肿的ISO镜像而是根据你指定的Ubuntu版本和架构从官方软件仓库里只下载最最基础的软件包base组并在本地目录里搭建出一个可运行的、最小化的Linux系统骨架。在开始构建之前我们需要先安装这个工具。打开你的终端执行以下命令sudo apt-get update sudo apt-get install debootstrap安装完成后debootstrap命令就可以使用了。它的基本语法是sudo debootstrap --arch架构 发行版代号 目标目录 镜像源地址这里有几个参数需要你根据实际情况决定架构这是关键。对于ARM设备常见的有arm64 对应64位的ARMv8/AArch64架构比如树莓派3B/4B、瑞芯微RK3568等。armhf 对应32位的ARM硬浮点架构比如树莓派1/2/Zero、全志H3等老一些的芯片。发行版代号Ubuntu的版本代号。我强烈建议使用长期支持版LTS以获得更长的维护周期。例如jammy: Ubuntu 22.04 LTSfocal: Ubuntu 20.04 LTSbionic: Ubuntu 18.04 LTS目标目录一个空目录用来存放即将构建出的根文件系统。比如/opt/ubuntu-rootfs。确保你有这个目录的写入权限。镜像源地址推荐使用国内的镜像源以加速下载。对于ARM架构必须使用-ports仓库。例如中科大源http://mirrors.ustc.edu.cn/ubuntu-ports/阿里云源http://mirrors.aliyun.com/ubuntu-ports/2.2 为你的设备规划磁盘分区在把系统文件“烧录”到存储设备比如SD卡之前我们需要先想好怎么给这块“地”划分“区域”。一个典型的嵌入式Linux系统至少需要两个分区建议三个Boot分区启动分区作用存放Linux内核镜像Image或uImage、设备树文件.dtb、引导程序如U-Boot等。内核启动初期需要读取这个分区。建议大小200MB - 1GB。如果内核和设备树文件不大256MB足够。文件系统通常格式化为FAT32vfat因为大多数Bootloader如U-Boot对FAT32的支持最好、最通用。命令是sudo mkfs.vfat /dev/sdX1。Root分区根分区作用存放我们即将构建的整个Ubuntu根文件系统。这是系统运行的主体。建议大小根据你的需求。一个最小化的命令行系统可能只需要2-3GB但如果要安装图形界面和开发工具建议8GB以上。嵌入式设备存储有限需要精打细算。文件系统通常使用Ext4它在稳定性和性能上比较均衡。命令是sudo mkfs.ext4 /dev/sdX2。Swap分区交换分区可选作用当物理内存不足时将部分内存数据临时交换到磁盘上防止系统因内存耗尽而崩溃。对于内存较小的设备如512MB建议创建。建议大小一般为物理内存的1-2倍但不宜过大因为频繁的Swap交换会严重影响性能。例如1GB内存的设备可以设置1GB或2GB的Swap。文件系统类型就是swap。创建命令是sudo mkswap /dev/sdX3。你可以使用fdisk或图形化的gparted工具来对你的SD卡进行分区。记住分区的设备名比如/dev/sdb1、/dev/sdb2我们后面会用到。3. 实战第一步使用debootstrap构建最小系统好了工具和规划都准备好了现在开始真正的构建。假设我们要为一块64位的ARM开发板比如树莓派4B构建Ubuntu 22.04 LTS的根文件系统。首先创建一个工作目录并执行debootstrap命令# 创建一个目录用于存放构建出的根文件系统 sudo mkdir -p /opt/ubuntu-arm64 # 使用debootstrap构建最小系统 sudo debootstrap --archarm64 --variantminbase jammy /opt/ubuntu-arm64 http://mirrors.aliyun.com/ubuntu-ports/我来解释一下这条命令--archarm64指定目标架构为ARM 64位。--variantminbase这是一个非常实用的选项。它告诉debootstrap只安装绝对最基础的包比默认的base组还要精简得到的系统更小。非常适合嵌入式环境。jammyUbuntu 22.04 LTS的代号。/opt/ubuntu-arm64我们之前创建的目标目录。最后是镜像源地址。执行这个命令后终端会开始疯狂地下载软件包并解压安装。这个过程取决于你的网速可能需要十几到几十分钟。期间你可以去泡杯茶。如果一切顺利最后你会看到I: Base system installed successfully.这样的成功提示。此时/opt/ubuntu-arm64目录里已经有一个可以运行的Ubuntu系统“骨架”了。但它现在还非常“脆弱”无法直接在里面操作因为它缺少一些关键的虚拟文件系统。我们需要通过chrootChange Root命令“进入”这个新系统环境进行配置。在chroot之前必须绑定bind mount主机的三个虚拟文件系统到目标目录这样新系统才能识别设备、进程和系统信息sudo mount -t proc /proc /opt/ubuntu-arm64/proc sudo mount -t sysfs /sys /opt/ubuntu-arm64/sys sudo mount -o bind /dev /opt/ubuntu-arm64/dev有的教程还会绑定/dev/pts用于伪终端但绑定/dev通常就够了。现在我们可以chroot进去了sudo chroot /opt/ubuntu-arm64 /bin/bash执行成功后你的命令行提示符很可能会变成rootyourhostname:/#。注意此时你的“根目录”/已经变成了主机上的/opt/ubuntu-arm64目录。你在这个shell里执行的所有操作都只影响这个新构建的系统不会影响你的主机系统。恭喜你已经“穿越”到了你亲手打造的ARM Ubuntu世界里4. 在chroot环境中进行基础配置现在我们在新系统的“内部”了。第一件事是更新软件包列表然后安装一些让这个系统变得“可用”的基础工具。4.1 更新源与安装核心工具# 首先更新软件包列表 apt-get update # 安装一些绝对必要的软件包 apt-get install -y sudo vim net-tools iproute2 iputils-ping wget curlsudo允许普通用户执行管理员命令这是安全和管理的基础。vim或nano一个文本编辑器用于后续修改配置文件。你可以按喜好选择。net-tools,iproute2,iputils-ping提供ifconfig,ip,ping等网络诊断和配置命令。虽然net-tools有点老但ifconfig用起来直观很多脚本也依赖它。wget和curl从网络下载文件的工具后续安装软件经常用到。4.2 配置系统基础信息设置root密码这是必须的否则你无法登录。passwd输入两次你要为root用户设置的密码。设置主机名给你的设备起个名字。echo my-arm-device /etc/hostname同时需要更新/etc/hosts文件让系统能解析自己的主机名echo 127.0.1.1 my-arm-device /etc/hosts配置时区让系统时间显示正确。# 安装tzdata包 apt-get install -y tzdata # 交互式设置时区选择Asia - Shanghai或你所在的时区 dpkg-reconfigure tzdata或者非交互式设置echo Asia/Shanghai /etc/timezone ln -fs /usr/share/zoneinfo/Asia/Shanghai /etc/localtime配置语言环境Locale这决定了系统的语言、字符编码等。对于中文环境我们需要生成zh_CN.UTF-8。apt-get install -y locales # 编辑locale生成配置文件取消注释 zh_CN.UTF-8 UTF-8 和 en_US.UTF-8 UTF-8 # 可以用 sed 命令快速操作 sed -i /en_US.UTF-8/s/^# //g /etc/locale.gen sed -i /zh_CN.UTF-8/s/^# //g /etc/locale.gen # 生成locale locale-gen # 设置默认locale update-locale LANGen_US.UTF-8 LC_ALLen_US.UTF-8我通常将系统默认语言设为英文en_US.UTF-8这样可以避免一些终端和软件出现乱码同时支持中文显示和输入。4.3 配置网络与软件源为了让系统能顺利安装更多软件我们需要配置好软件源并确保网络基础服务就绪。配置APT软件源将系统自带的源文件替换为国内镜像速度会快很多。# 备份原来的源文件 cp /etc/apt/sources.list /etc/apt/sources.list.bak # 使用sed命令替换为阿里云源针对Ubuntu 22.04 Jammy cat /etc/apt/sources.list EOF deb http://mirrors.aliyun.com/ubuntu-ports/ jammy main restricted universe multiverse deb http://mirrors.aliyun.com/ubuntu-ports/ jammy-updates main restricted universe multiverse deb http://mirrors.aliyun.com/ubuntu-ports/ jammy-backports main restricted universe multiverse deb http://mirrors.aliyun.com/ubuntu-ports/ jammy-security main restricted universe multiverse EOF记得根据你构建的Ubuntu版本如focal对应20.04修改上面的代号。安装网络管理器可选但推荐对于有桌面环境或需要灵活管理Wi-Fi、有线连接的系统可以安装network-manager。apt-get install -y network-manager如果只是简单的有线网络并且通过DHCP获取IP那么之前安装的dhcpcd5或isc-dhcp-client可能就够了。但network-manager功能更强大图形界面和命令行nmcli都支持。配置DNS确保系统能解析域名。通常DHCP会自动配置但我们也可以设置一个备用DNS。echo nameserver 114.114.114.114 /etc/resolv.conf echo nameserver 8.8.8.8 /etc/resolv.conf完成这些基础配置后可以再次运行apt-get update测试一下源是否可用。5. 创建用户与配置系统服务一个总是使用root用户的系统是不安全也不规范的。我们应该创建一个日常使用的普通用户并赋予其sudo权限。5.1 创建普通用户假设我们要创建一个名为developer的用户# 添加用户并同时创建家目录(/home/developer)指定shell为bash useradd -m -s /bin/bash developer # 为用户设置密码 passwd developer输入并确认你为developer用户设置的密码。5.2 将用户添加到sudo组在Ubuntu中sudo组的成员可以使用sudo命令。# 将developer用户添加到sudo组 usermod -aG sudo developer # 也可以添加到adm组用于查看系统日志等 usermod -aG adm developer5.3 配置fstab文件/etc/fstab文件定义了系统启动时需要自动挂载的文件系统。我们需要根据之前的分区规划来编辑它。在chroot环境中编辑这个文件vim /etc/fstab添加类似以下内容请将/dev/sda2等替换为你实际的设备名或UUID# file system mount point type options dump pass /dev/sda1 /boot vfat defaults 0 2 /dev/sda2 / ext4 errorsremount-ro 0 1 # 如果有swap分区 /dev/sda3 none swap sw 0 0第一列设备文件或UUID。使用UUID更稳定因为设备名如/dev/sda可能会变。可以用blkid命令查看分区的UUID。第二列挂载点。第三列文件系统类型。第四列挂载选项。defaults是常用选项。对于根分区errorsremount-ro表示发生错误时以只读方式重新挂载防止数据损坏。第五列dump备份工具是否备份0表示不备份。第六列fsck磁盘检查顺序。根分区应为1其他文件系统为20表示不检查。5.4 安装并配置SSH服务器强烈推荐为了能通过网络远程登录你的ARM设备安装SSH服务几乎是必须的。apt-get install -y openssh-server安装后SSH服务通常会自动启用。你可以根据需要修改配置文件/etc/ssh/sshd_config例如禁止root密码登录以增强安全PermitRootLogin prohibit-password # 只允许密钥登录root或改为 no 完全禁止 PasswordAuthentication yes # 允许密码登录初期调试方便生产环境建议改为no并使用密钥基础配置到这里就差不多了。现在可以退出chroot环境并卸载绑定的文件系统exit # 退出chroot环境 sudo umount /opt/ubuntu-arm64/proc sudo umount /opt/ubuntu-arm64/sys sudo umount /opt/ubuntu-arm64/dev6. 系统烧录与首次启动现在我们有了一个完整的、定制化的根文件系统目录/opt/ubuntu-arm64。接下来就是把它“烧录”到目标存储设备比如SD卡的根分区上。6.1 将根文件系统拷贝到目标分区假设你的SD卡在电脑上识别为/dev/sdb其中第二个分区/dev/sdb2是预留的根分区。# 首先格式化根分区为ext4如果之前没格式化 sudo mkfs.ext4 /dev/sdb2 # 挂载该分区 sudo mount /dev/sdb2 /mnt # 使用rsync同步文件保留所有属性权限、链接等 sudo rsync -aAXv /opt/ubuntu-arm64/ /mnt/ # 同步完成后卸载 sudo umount /mnt使用rsync比简单的cp命令更好它能完美保留文件权限、符号链接等所有属性。6.2 处理Boot分区Boot分区需要放置内核、设备树等文件。这些文件通常需要你为目标板卡单独编译或者从板卡厂商提供的SDK中获取。将Boot分区假设是/dev/sdb1格式化为FAT32并挂载。sudo mkfs.vfat /dev/sdb1 sudo mount /dev/sdb1 /mnt将你准备好的内核镜像如Image、设备树文件如*.dtb、以及可能需要的U-Boot引导文件拷贝到/mnt/下。具体的文件结构和名称取决于你的硬件和Bootloader。卸载分区sudo umount /mnt。6.3 首次启动与调试将SD卡插入你的ARM开发板连接串口调试线这是嵌入式开发必备的调试手段上电启动。在串口终端中你应该能看到内核启动日志。如果一切顺利最终会出现登录提示。用你之前创建的developer用户和密码登录。首次登录后我建议做以下几件事检查网络运行ip addr或ifconfig看看网卡是否识别IP地址是否获取到。更新系统sudo apt update sudo apt upgrade安装所有可用的安全更新和软件包更新。安装你的开发环境现在可以像在普通Ubuntu上一样安装你需要的任何工具比如build-essentialGCC编译套件、python3-pip、git、cmake等。sudo apt install -y build-essential git cmake考虑安装图形界面可选如果你的板子性能足够并且需要桌面环境可以安装轻量级的桌面如XFCE或LXDE。sudo apt install -y xubuntu-desktop # 这是XFCE桌面一些应用稍大 # 或者更精简的 sudo apt install -y --no-install-recommends xfce4 xfce4-goodies lightdm7. 进阶定制与优化技巧构建一个能启动的系统只是开始要让其真正成为高效的开发环境还需要一些进阶操作。7.1 清理无用文件以节省空间在chroot环境中或系统运行后可以清理APT缓存和不需要的文档这对存储空间紧张的嵌入式设备非常有用。# 清理已下载的软件包缓存 sudo apt clean # 删除不需要的依赖包 sudo apt autoremove --purge # 删除 man 手册和文档如果确定不需要 sudo rm -rf /usr/share/man/* /usr/share/doc/*7.2 使用qemu-user-static在x86主机调试ARM程序这是一个非常强大的技巧它允许你在x86的构建主机上直接运行和调试为ARM编译的程序无需真正的ARM硬件。# 在主机上安装 sudo apt install qemu-user-static # 将qemu-arm-static拷贝到目标根文件系统 sudo cp /usr/bin/qemu-aarch64-static /opt/ubuntu-arm64/usr/bin/ # 重新chroot此时会自动使用qemu进行指令翻译 sudo chroot /opt/ubuntu-arm64现在在chroot环境里你可以运行apt install安装ARM架构的软件包甚至编译和运行ARM程序就像在真机上一样。这对于交叉编译环境的验证和调试极其方便。7.3 固化配置创建自定义软件包列表如果你经常为同类型板子构建系统可以创建一个列表文件记录所有需要安装的软件包。然后让debootstrap在构建时一并安装。# 创建一个pkglist.txt文件每行一个软件包名 echo -e sudo\nvim\nnet-tools\niproute2\nopenssh-server\ngit\nbuild-essential /path/to/pkglist.txt # 使用 --include 参数指定 sudo debootstrap --archarm64 --variantminbase --include$(cat /path/to/pkglist.txt | tr \n ,) jammy /opt/ubuntu-arm64 http://mirrors.aliyun.com/ubuntu-ports/7.4 处理硬件特定的配置不同的ARM开发板可能需要不同的内核模块或固件。你需要将从板卡厂商获取的或自己编译的以下文件放入根文件系统对应位置内核模块位于/lib/modules/内核版本/。将编译好的模块目录整个拷贝进去。固件文件位于/lib/firmware/。一些无线网卡、GPU等需要特定的固件文件才能工作。用户空间驱动或配置脚本有些硬件需要额外的用户空间程序或服务需要根据厂商文档进行安装和配置。整个过程虽然步骤不少但每一步都有其明确的目的。我第一次成功从零构建出系统并看到登录提示时那种成就感远超直接刷写一个现成的镜像。它让你对Linux系统的组成有了更深的理解也让你对自己的开发环境有了完全的掌控力。以后无论遇到什么奇怪的板子你都知道如何为它打造一个称手的“家”了。