news 2026/9/28 7:33:50

飞腾E2000/D2000多系统镜像定制:Buildroot与Debian实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
飞腾E2000/D2000多系统镜像定制:Buildroot与Debian实践

折腾过飞腾板子的朋友应该都有体会:官方BSP和文档往往围绕 Ubuntu 展开,教程里全是apt install和现成桌面环境,一旦你想自己精简系统、定制镜像、或者把根文件系统换成更干净的 Debian,网上能直接抄作业的东西少得可怜。这个项目就是我自己从 Ubuntu 环境迁移到 Debian 环境、用 Buildroot 2022.02 给飞腾 E2000/D2000 两个平台编译多系统镜像的完整记录,踩了无数坑,也总结出了一套能一次性跑通的流程。

这篇文章的核心内容分四块:为什么从 Ubuntu 换到 Debian、Buildroot 环境怎么搭、E2000 和 D2000 的 board 差异怎么管理、以及如何生成 Debian/Ubuntu 风格的多套根文件系统镜像。适合正在做飞腾平台 BSP 适配的嵌入式工程师、想从 Yocto/BitBake 切到更轻量构建方案的团队,以及拿到 E2000/D2000 开发板却对着 Ubuntu 官方镜像不知所措的入门玩家。我会把关键配置项、踩坑记录和能直接复制的命令都写出来,尽量让读到的人少走几周弯路。

2. 项目背景与方案选型:为什么最终选了 Debian + Buildroot

先说结论:这套方案的核心不是 Buildroot 本身,而是“用 Buildroot 管内核和 uboot,用 debootstrap 管根文件系统,用外部树管板级差异”。理解了这三个角色的分工,后面所有配置都是水到渠成的事。

2.1 从 Ubuntu 迁移到 Debian 的三点理由

最开始我的构建主机是 Ubuntu 20.04,跟着网上的飞腾教程一步步装工具、编镜像,整体也能跑起来,但越用越难受,最后换到 Debian 11,大概有三个原因。

第一,Ubuntu 的桌面版自带一堆和编译无关的服务和快照机制,长期跑编译任务时内存和磁盘开销都不小。嵌入式镜像编译动辄二十分钟以上,还经常要同时挂多个任务,系统一旦因为自动更新或者 snap 后台刷新卡顿,真的很影响效率。当然你可以删掉这些组件,但每次换机器就得重新调一遍,麻烦。

第二,Ubuntu 的软件仓库版本更新太快。今天装个gcc-aarch64-linux-gnu是 9,过几个月大版本升级变成 10,工具链一变,整个 Buildroot 的构建链可能就跟着出问题,尤其是内核这类对编译器版本敏感的代码。Debian 的 stable 分支版本固定,release 期间几乎不变,作为长期重复使用的构建环境非常合适。

第三,我后面要生成的目标根文件系统是 Debian 风格和 Ubuntu 风格两套,都依赖 glibc 动态链接。Buildroot 自带的工具链如果选了 uClibc 或 musl,跑起 Debian/Ubuntu 的二进制程序会直接报Illegal instruction或者.so找不到,所以工具链必须用 glibc。而 glibc 工具链在 Debian 宿主上通过交叉编译生成是最省事的,Ubuntu 上反而经常被 snap 的版本搅和。

2.2 Buildroot 2022.02:LTS 选型思路

Buildroot 的版本策略是每两年出一个 LTS,2022.02 就是其中一个,维护周期到 2023 年 4 月左右,之后还在持续打补丁。选它而不是更新版本,主要是 LTS 的生态判断更稳,社区反馈的问题基本都修过一轮了,不容易遇到莫名其妙的新版回归问题。

有人会问,为什么不用 Yocto?Yocto 功能确实全,对大规模量产和复杂依赖管理很合适,但有两个问题很难受:一是 BitBake 的解析速度慢,机器配置稍微差一点,一次全量编译能吃掉一整天;二是 Yocto 的 layer 结构对飞腾这种需要厂商 BSP 深度定制的平台来说,适配工作量非常大,为了一个板子维护一堆 recipe,成本太高。Buildroot 用 Makefile 体系,裁剪和定制都更快,直接把厂商内核包源码扔进来就能编。

还有人问,为什么不直接debootstrap一套 Debian 根文件系统然后手动装内核?这个思路其实方向是对的,但有个前提缺陷:debootstrap 只生产根文件系统,不管内核、bootloader、设备树和分区表,而飞腾平台恰恰是这几个部分最容易出问题的地方。所以我的做法是让 Buildroot 干它最擅长的活——编译内核、uboot、生成根文件系统骨架和启动镜像,然后再把 debootstrap 产出的 Debian/Ubuntu 用户态拿过来做 overlay 替换。这样两个工具都用在刀刃上。

2.3 飞腾 E2000/D2000 平台特点与构建差异

飞腾 E2000 和 D2000 都是 ARMv8-A 64 位架构,所以交叉编译工具链可以共用,指令集级别也都按armv8-a处理,不用为某个板子专门选过于激进的 CPU 优化参数。两者最大的区别在定位和集成度。

E2000 面向嵌入式场景,功耗低,封装更小,常出现在工业控制、瘦客户端、边缘网关这些设备里,很多板子只有串口、网口、USB 和少量 PCIe,启动介质以 SD 卡和 eMMC 为主。D2000 是桌面级处理器,核心数更多、主频更高,外设也更丰富,支持 NVMe、多路 SATA、HDMI 显示输出,适合做台式机、一体机和轻量服务器。

这两个平台在 Buildroot 构建上的差异,主要集中在三块:内核配置(defconfig 不同)、设备树(dts/dtsi 文件不同)、uboot 配置(板级头文件和默认环境变量不同)。对于搞过 Linux 移植的朋友,这套差异模型并不陌生,但很多新手一开始没意识到,拿着 D2000 的内核配置去启动 E2000,结果各种外设不工作,还以为是镜像坏了。后面我会单独开一节,讲清楚怎么用 Buildroot 外部树把这套差异管理起来。

3. 构建环境准备与工具链搭建

环境准备这块看起来简单,但实际上“在 Debian 上装哪些包、装到什么版本、工作目录怎么摆”直接决定后面编译顺不顺。我踩过的坑基本都是在这里埋下的。

3.1 Debian 主机初始化:必要依赖安装

我使用的是 Debian 11(bullseye)作为构建主机。为了减少兼容性问题,建议用 stable 分支,不建议用 testing 或 sid,因为那些分支的 gcc 和 binutils 版本太新,和 Buildroot 2022.02 自带的工具链脚本偶尔会有适配问题。

首次安装完系统后,先更新软件源,然后安装以下包:

sudo apt update sudo apt install -y git wget cpio unzip rsync bc \ build-essential libncurses-dev flex bison \ libssl-dev libelf-dev device-tree-compiler \ u-boot-tools python3 python3-distutils \ gcc-aarch64-linux-gnu g++-aarch64-linux-gnu \ dosfstools mtools parted kpartx

逐一说下这些包的作用。build-essential是宿主编译必需,gcc-aarch64-linux-gnu提供交叉编译器,但在 Buildroot 自编译工具链模式下其实用不到宿主的 aaarch64 GCC,装了主要是为了应急调试和手动编译小工具。libncurses-dev是make menuconfig的依赖,没有它图形配置界面根本起不来。flex和bison是内核和 uboot 编译时需要的语法解析器,libssl-dev对应内核开启模块签名和安全功能时需要,libelf-dev用于处理 ELF 文件。device-tree-compiler提供dtc命令,虽然 Buildroot 编内核时会自动构建,但主机上装一份可以单独检查设备树格式。u-boot-tools提供mkimage,制作启动脚本和 FIT image 时要用。

3.2 BSP 整理与工作目录规划

拿到开发板后,第一件事不是急着编镜像,而是先把厂商提供的 BSP 整理清楚。飞腾平台一般由板厂或方案商提供内核源码包、uboot 源码包和设备树文件,这三样的版本必须匹配,不能随意混搭。

我自己习惯的目录结构是这样:

~/e2000_d2000_build/ ├── buildroot-2022.02/ ├── bsp/ │ ├── kernel/ │ │ ├── e2000_linux.tar.gz │ │ └── d2000_linux.tar.gz │ ├── uboot/ │ │ ├── e2000_uboot.tar.gz │ │ └── d2000_uboot.tar.gz │ └── dts/ ├── output/ └── board/ ├── e2000/ └── d2000/

buildroot-2022.02是官方源码目录,保持原始状态不改动,方便以后升级。所有自定义内容都放进board/目录,以BR2_EXTERNAL形式传给 Buildroot 使用。bsp/目录放厂商源码包,编译时 Buildroot 直接从这里读取 tar 包,不用每次解压到源码目录。output/是编译输出目录,这个可以丢弃,但建议每次变更配置后先make clean再重新编译,避免旧配置残留。

3.3 第一个最小系统验证:确认板子能跑起来

建议大家不要一上来就编完整桌面镜像,那样问题太多,排错特别痛苦。我的习惯是先用 Buildroot 默认的 busybox 最小系统验证硬件链路:uboot 能不能启动、串口能不能打印、内核能不能解压、根文件系统能不能挂上。这一步通了,后面再谈 rootfs 换 Debian/Ubuntu 才有意义。

首次验证可以直接用 Buildroot 的 qemu aarch64 defconfig 做基础,把工具链设为 aarch64,先不接厂商内核,用主线内核跑一下看看编译环境是否正常:

cd buildroot-2022.02 make qemu_aarch64_virt_defconfig make -j$(nproc)

如果这一步能顺利产出output/images/,说明你主机上的构建依赖基本齐全了。接下来再做真正的板级适配,把厂商内核和 uboot 加进配置即可。这一步虽然简单,但能省去很多“Buildroot 本体没编过却直接怀疑飞腾平台有问题”的无谓排障。

4. Buildroot 2022.02 关键配置与多系统镜像编译实操

环境准备好之后,重头戏是 Buildroot 的配置。这个阶段的主题就是“让 Buildroot 听你的话”,把厂商内核、uboot、设备树和 debootstrap 产出的用户态全部集成到一套构建流程里。

4.1 defconfig 与 menuconfig 配置要点

Buildroot 的配置基础是 defconfig,所有配置项最终都写入.config。我建议不要直接手改.config,而是以make menuconfig修改,改完再用make savedefconfig保存成自己的最小 defconfig,方便后续版本管理。

关键配置项如下:

配置项推荐值说明
BR2_aarch64y目标架构为 64 位 ARM
BR2_TOOLCHAIN_BUILDROOT_GLIBCy使用 glibc 工具链,运行 Debian/Ubuntu 用户态必需
BR2_TOOLCHAIN_BUILDROOT_CXXy需要 C++ 支持的话打开,crosstool-NG会编 g++
BR2_LINUX_KERNELy启用内核构建
BR2_LINUX_KERNEL_CUSTOM_TARBALLy使用厂商内核源码包
BR2_LINUX_KERNEL_CUSTOM_TARBALL_LOCATIONfile:///home/.../bsp/kernel/e2000_linux.tar.gz指定内核 tar 包路径
BR2_LINUX_KERNEL_DEFCONFIGe2000_defconfig或d2000_defconfig取决于厂商提供的内核配置
BR2_LINUX_KERNEL_DTS_SUPPORTy编译设备树
BR2_TARGET_UBOOTy启用 uboot 构建
BR2_TARGET_UBOOT_CUSTOM_TARBALLy使用厂商 uboot 源码包
BR2_TARGET_UBOOT_BOARD_DEFCONFIG根据板卡填写厂商 uboot 的板级 defconfig
BR2_ROOTFS_OVERLAYboard/e2000/overlay需要覆盖到根文件系统的文件目录

这里最关键的认知是:Buildroot 不是万能的编译系统,它的价值在于把内核、uboot、rootfs 三件事用一个 Makefile 串起来。平台相关的适配,最终还是靠厂商提供的源码包和配置来落地。BR2_LINUX_KERNEL_CUSTOM_TARBALL允许你直接从本地 tar 包构建,而不是依赖 Git 仓库,这对内网开发和版本锁定非常方便。

4.2 在 Buildroot 中集成硬件原厂 uboot 与内核

飞腾平台的 uboot 一般不是主线 uboot,而是厂商基于某个 uboot 版本二次开发出来的,里面会有飞腾特有的 64 位启动处理和 PCIe 初始化。所以BR2_TARGET_UBOOT这里必须用本地 tar 包,不能指望主线 uboot 一把过。

配置过程中有个小技巧:先不要着急勾选BR2_TARGET_UBOOT,而是先把内核编译通过,再把 uboot 加进来。这样排障时能精确定位是内核问题还是 uboot 问题。我见过太多人一上来全勾选,结果编译失败后根本分不清是哪一环出的问题。

uboot 配置项里比较关键的是BR2_TARGET_UBOOT_BOARD_DEFCONFIG,这个值对应厂商源码目录下configs/里的文件名。比如厂商给了e2000_defconfig,就填e2000_defconfig。有时候厂商 uboot 源码里默认的板级配置不是最终可用的,可能还需要自己调整几个存储相关宏定义,常见的比如CONFIG_SYS_LOAD_ADDR、CONFIG_BOOTCOMMAND、CONFIG_EXTRA_ENV_SETTINGS等,这些一般在源码里搜索#define就能找到。

编译 uboot 时还需要注意,飞腾平台的启动流一般是:上电 -> ROM 代码 -> uboot SPL -> 加载主 uboot -> 启动内核。如果你的板子没有 SPL,那 uboot 二进制会被直接烧到启动介质固定偏移处,镜像打包方式就完全不同。我在 D2000 板子上遇到的就是无 SPL 方案,直接用dd把 uboot 写到 SD 卡的 64KB 偏移处;而在 E2000 板子上则用了 SPL + FIT 方式。这块没有统一标准,必须看板卡手册或厂商 uboot 发布说明。

部分厂商的 BSP 提供一个build.sh一键打包脚本,它会把 uboot、内核、rootfs 自动组合成可烧写的镜像。凡是遇到这种脚本,我建议先打开读一遍,理解它到底做了什么,再决定是否自己在 Buildroot 里重做一套。大多数厂商脚本的本质不过是用dd和mkfs组合命令行,你可以把它们翻译成 Buildroot 的 post-image 脚本,实现可复现的自动构建。

4.3 用外部树管理 E2000 与 D2000 差异

当同一套 Buildroot 需要同时产出 E2000 和 D2000 两个平台的镜像时,最优雅的方案是用BR2_EXTERNAL外部树。外部树里可以放私有的 defconfig、package 和 rootfs overlay,Buildroot 在编译时会把这些内容当作一等公民处理,不会污染官方 Buildroot 源码。

我的外部树结构长这样:

board/ ├── Config.in ├── external.mk ├── external.desc ├── e2000/ │ ├── e2000_defconfig │ ├── overlay/ │ │ ├── etc/ │ │ │ ├── fstab │ │ │ └── network/ │ │ └── root/ │ └── post-image.sh └── d2000/ ├── d2000_defconfig ├── overlay/ └── post-image.sh

external.desc文件内容很简单:

name: PHYTIUM_BOARD desc: E2000/D2000 board support

构建时使用make BR2_EXTERNAL=/path/to/board/ e2000_defconfig,Buildroot 会自动找到外部树里的e2000_defconfig并生成.config。e2000_defconfig里除了 4.1 节提到的公共配置,还会额外指定BR2_ROOTFS_OVERLAY="board/e2000/overlay"和BR2_ROOTFS_POST_IMAGE_SCRIPT="board/e2000/post-image.sh",这样每个平台都有自己的根文件系统覆盖层和打包脚本。

为什么这么设计?核心目的是降低两个平台的耦合。D2000 桌面镜像需要的桌面环境、中文字体、输入法配置完全不该出现在 E2000 的嵌入式镜像里,反之亦然。用一堆ifdef或 shell 判断去内部处理不同板级差异,初始实现很快,但后续维护会非常痛苦。外部树加两个独立 overlay 目录,自然地把差异隔离开,板上需要什么直接往自己 overlay 里塞,互不干扰。

实践中的一个小经验:E2000 和 D2000 的公共部分,比如内核开启CONFIG_DEVTMPFS、rootfs 开启 DHCP 客户端、工具链选择 glibc 等,可以单独提炼一个公共配置片段或者写进文档,避免两个 defconfig 里重复维护相同项。我因为偷懒一度在两个 defconfig 里各写了一份公共项,后来改一个忘一个,折腾了好几次才意识到公共项应该做成独立的common.fragment,在两个平台 defconfig 里用BR2_DEFCONFIG之外的方式统一维护。不过 Buildroot 本身不支持多个BR2_ROOTFS_OVERLAY拼接 fragments,所以更简单的办法还是单独写一个脚本,在 post-image 阶段把公共内容复制到每个平台输出目录。

4.4 结合 debootstrap 生成 Debian/Ubuntu 风格根文件系统

Buildroot 默认生成的根文件系统是 busybox 那一套,体积小,适合嵌入式系统,但如果你想跑桌面应用、安装 Debian/Ubuntu 生态里的软件包,还是要换成完整的 glibc 根文件系统。

在 Buildroot 体系里做 Debian/Ubuntu rootfs,我的方案分三步:

第一步,在 Debian 构建主机上用debootstrap生成基础 rootfs。生成 Debian 风格 rootfs 的命令:

sudo debootstrap --arch=arm64 --foreign --variant=minbase \ bullseye debian_rootfs/

这里--foreign表示不执行 chroot 内部的第二阶段配置,因为目标架构是 arm64,无法直接在当前机器上运行。--variant=minbase只安装最基础的系统,减少后续裁剪工作量。

如果想生成 Ubuntu 风格 rootfs,只需把发行版名称换成jammy等,并指定对应的 Ubuntu 软件源地址。注意 Ubuntu 的 ports 源和 Debian 的官方源目录结构不同,命令会不一样,但思路完全一致。

第二步,把生成的 rootfs 作为 Buildroot 的 overlay 塞进镜像。

Buildroot 的BR2_ROOTFS_OVERLAY机制是把一个本地目录整体拷贝到目标根文件系统里。理论上我们可以把 debootstrap 产出的debian_rootfs/作为 overlay,但要注意几个权限问题:debootstrap生成的设备节点、链接等文件在--foreign下不完整,需要手动补。更稳妥的做法是,在主机上先把 rootfs 调整好,比如挂载proc、sys、dev后执行二阶段配置:

sudo chroot debian_rootfs/ /debootstrap/debootstrap --second-stage

之后用cp -a把整个 rootfs 拷贝到 Buildroot 的 overlay 目录。拷贝时务必保留软链接和权限,所以不要用普通的cp -r,要用cp -a。

第三步,在 Buildroot 配置中启用这个 overlay,并关闭 Buildroot 自带的 busybox 用户态,避免覆盖 Debian 的/bin等目录:

BR2_ROOTFS_OVERLAY="board/e2000/debian_overlay board/e2000/overlay" BR2_PACKAGE_BUSYBOX=n

注意BR2_ROOTFS_OVERLAY可以设置多个目录,但多个目录的优先级要注意,后面的目录会覆盖前面的同名文件。所以我通常把 debootstrap rootfs 放在第一个,板级定制放在第二个。

为什么不用 Buildroot 直接编译一个 Debian 系统?因为 Debian 内部有完整的软件包依赖体系,Buildroot 不可能也没必要去重新实现这些二进制包。debootstrap 直接利用官方源生成一个正版 Debian rootfs,再用 overlay 机制和 Buildroot 的内核、uboot 结合,既保证了用户态完整度,又保留了嵌入式构建的可复现性。

这套方案唯一的缺点是 rootfs 体积比 busybox 大得多,Debian minbase 大约 400MB,加了桌面怎么也要奔着 GB 去。但在现代存储成本面前,这一点差异完全可以接受,换来的是系统生态完整性和日常运维的便利。

4.5 制作完整启动镜像:分区、烧写与验证

rootfs 和内核都编好后,最后一步是打包成可以直接烧到 SD 卡或 eMMC 的镜像。飞腾板卡最常见的启动介质是 SD 卡,支持 MBR 分区表。下面是我在 E2000 板子上验证过的分区方案:

分区起始扇区大小文件系统内容
12048256MBFAT32内核、设备树、boot.scr
2526336剩余ext4根文件系统

打包时我通常用一段 post-image 脚本,核心命令如下:

#!/bin/bash # post-image.sh 部分示例 IMG=$BINARIES_DIR/sdcard.img SIZE_MB=4096 dd if=/dev/zero of=$IMG bs=1M count=$SIZE_MB parted -s $IMG mklabel msdos parted -s $IMG mkpart primary fat32 4MiB 260MiB parted -s $IMG mkpart primary ext4 260MiB 100% LOOPDEV=$(losetup --find --show $IMG) partprobe $LOOPDEV mkfs.vfat ${LOOPDEV}p1 mkfs.ext4 ${LOOPDEV}p2 mount ${LOOPDEV}p1 /mnt/boot mount ${LOOPDEV}p2 /mnt/rootfs # 拷贝内核、dtb、boot.scr 到 boot 分区 cp $BINARIES_DIR/Image /mnt/boot/ cp $BINARIES_DIR/*.dtb /mnt/boot/ # 拷贝 rootfs 到根文件系统分区 rsync -a $TARGET_DIR/ /mnt/rootfs/ umount /mnt/boot umount /mnt/rootfs losetup -d $LOOPDEV

这里面最容易错的点是losetup --partscan或partprobe之后的设备节点识别。有的自动化脚本里mkfs.vfat ${LOOPDEV}p1会提示设备找不到,原因是系统还没刷新分区表。我建议在partprobe后加一句sleep 1,或者直接用kpartx -a $IMG生成映射设备,再操作映射设备。说实话,kpartx比裸用losetup省心很多,推荐读者直接用。

烧写验证时:dd if=sdcard.img of=/dev/sdX bs=4M conv=fsync,然后插入板卡上电。如果串口能看到 uboot 打印,说明 uboot 阶段正常;如果卡在Kernel panic - not syncing: VFS: Unable to mount root fs,优先检查 rootfs 分区是否满足内核启动参数里的root=设置,以及内核是否开启了对应的文件系统驱动。

5. 多系统切换与启动参数详解

构建出一套镜像只是第一步,真正麻烦的是在多套系统之间切换。很多板子需要同时具备 Debian 桌面版、Debian 精简版、Ubuntu 服务版等多个 rootfs,按需启动。

5.1 一套 SD 卡多根文件系统分区的做法

我的做法是在一张 SD 卡上划分多个 rootfs 分区,每个分区放一套独立的系统。比如:

分区起始扇区大小文件系统内容
12048256MBFAT32内核、设备树、boot.scr
252633610GBext4Debian 桌面版 rootfs
32072万6GBext4Debian 精简版 rootfs
4约 3390万8GBext4Ubuntu 服务版 rootfs

每个 rootfs 系统都写在独立分区里,内核是共用的,但启动参数里指定不同的root=PARTUUID即可切换系统。这样测试时改一个环境变量就能换了系统,不用反复烧卡。

PARTUUID的获取可以用blkid在当前 Linux 环境查,比如:

sudo blkid /dev/sdX2 sudo blkid /dev/sdX3

输出里会有PARTUUID="xxxxxxxx-xxxx-..."。这个字符串可以直接写进 uboot 环境变量。

5.2 extlinux.conf、boot.scr 与 u-boot 环境变量

飞腾平台的 uboot 不一定原生支持 extlinux.conf,很多板子的默认启动流程是加载boot.scr。boot.scr是一个用mkimage打包的 u-boot 脚本,可以在里面用setenv、load和booti等命令完成启动。

我习惯在源码目录里写一个boot.cmd,内容类似:

setenv bootargs 'console=ttyS0,115200 root=PARTUUID=xxxx-01 rootwait rw' load mmc 0:1 $kernel_addr_r Image load mmc 0:1 $fdt_addr_r <你的设备树文件>.dtb booti $kernel_addr_r - $fdt_addr_r

然后生成boot.scr:

mkimage -A arm64 -T script -C none -n "Boot Script" -d boot.cmd boot.scr

把boot.scr放到 SD 卡第一个分区后,uboot 会在启动时自动找到并执行。如果你想临时从另一个系统启动,不进源码重新编译,可以直接在 uboot 命令行用editenv bootargs修改root=参数,然后boot。

E2000 和 D2000 的串口地址可能不一样,常见的有ttyS0、ttyAMA0等,具体查阅厂商 BSP 附带的内核文档。这一点千万不能想当然,console=写错了,启动时串口会一片空白,看起来像变砖,实际内核其实在跑。

5.3 D2000 与 E2000 启动介质差异

D2000 平台上的固态存储用的是 NVMe 或 SATA,从 NVMe 启动时需要 uboot 支持对应驱动程序,并且内核的CONFIG_NVME_CORE等选项必须打开。我在 D2000 上遇到的典型问题是,uboot 能识别 NVMe,但内核启动后找不到 rootfs,最后发现是内核 config 里漏了CONFIG_BLK_DEV_NVME。

E2000 则更多用 SD 卡和 eMMC,这两类介质对 uboot 来说基本都是 mmc 设备,启动参数差异不大,但 eMMC 的分区烧写需要注意写保护位和 RPMB 区域,操作不当很可能直接把 eMMC 锁死。所以初次验证建议用 SD 卡,确认所有逻辑都正确后再动 eMMC。

另外,D2000 通常配合独立显卡和 HDMI 输出,如果你在镜像里想启动图形界面,内核的 DRM 驱动和 framebuffer 控制台也不能丢。E2000 的显示则更多是简单 framebuffer,配置相对轻量。这块差异决定了最终 rootfs 里要不要带 Xorg/Wayland 相关组件,也直接影响分区容量规划。

6. 常见编译问题与排查实录

多系统镜像编译的坑,90% 集中在三个方面:rootfs 挂不上、工具链不兼容、构建依赖缺失。我把实际项目中遇到过的典型问题和排查思路整理成速查表,方便大家遇到同类问题时快速定位。

6.1 根文件系统挂载不上的经典三问

拿到镜像启动,最常见的是:

Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(179,2)

遇到这种情况,我一般按下面顺序检查:

第一,内核有没有挂载根文件系统所需的驱动。如果是 ext4 根文件系统,内核必须开启CONFIG_EXT4_FS=y;如果 rootfs 分区在 SD 卡的第二个分区,还要内核支持mmc设备和对应控制器驱动。很多人喜欢把驱动编成模块,结果根文件系统都挂不上,模块也加载不了,死循环。所以 rootfs 最关键的解压、文件系统代码一定要直接编进内核,不要编成模块。

第二,启动参数里的root=路径对不对。用root=/dev/mmcblk0p2这种写法最直观,但设备节点编号可能随内核枚举顺序变化而漂移,现场排查时会头疼。更稳的写法是用root=PARTUUID=...或root=UUID=...,这样不怕节点顺序变化。

第三,CONFIG_DEVTMPFS和CONFIG_DEVTMPFS_MOUNT是否开启。如果根文件系统里没有预先创建/dev/console等节点,也没有开启 devtmpfs,那么内核在执行 init 时找不到设备节点,表现为启动到一半卡死但又不报文件系统错误。这个坑最容易骗人,因为前面分区、参数都检查过了,看着都正常,结果只是设备节点缺失导致。

6.2 Ubuntu 下能编译、Debian 下报错的坑

从 Ubuntu 迁移到 Debian 后编译报错,最常出现在内核编译阶段,典型现象是:

/bin/sh: 1: bison: not found

或者:

scripts/extract-cert.c:21:10: fatal error: openssl/bio.h: No such file or directory

前者是 flex/bison 缺失,后者是 libssl-dev 缺失。Ubuntu 桌面版默认带了一批开发工具,Debian 最小化安装后什么都没装,所以新装 Debian 后依赖缺失的概率很大。解决办法就是回到 3.1 节,把依赖清单全部过一遍。

另一个常见差异是系统 Python 版本。Buildroot 2022.02 对 Python 2/3 的脚本兼容性还行,但如果 Debian 系统默认 python 指向 Python 3,某些老旧的厂商脚本可能因为语法不兼容跑不起来。这个问题比较难排查,我建议在 Makefile 或 CI 脚本里显式指定PYTHON=/usr/bin/python3,避免调用到系统默认的 python 别名。

此外,Debian 的mkfs.ext4默认行为会随版本变化,比如创建的文件系统带metadata_csum特性,而较老的 uboot 不一定认识它。如果 uboot 在加载内核后读不到分区,但 rootfs 挂载时又正常,可以试试在mkfs.ext4加-O ^metadata_csum关闭这个特性。

6.3 编译时间过长与增量构建优化

从零开始全量编译一次 Buildroot,在主流配置的桌面上大约需要 20~40 分钟。如果每次都清干净重编,时间成本非常高。我推荐两个优化方向。

第一个是给 Buildroot 开ccache,在make menuconfig中进入Build options -> Enable compiler cache勾选即可。它可以把内核对同一段源码反复生成的中间对象缓存起来,尤其是你在调整配置参数过程中频繁重编同一个内核时,提速效果非常明显。

第二个是设置共享下载目录。Buildroot 默认把下载的源码包放在dl/目录,不同板卡不同输出目录之间可以共享。在配置里加入:

BR2_DL_DIR=/opt/buildroot_dl

这样无论编译多少次,外网下载的源码包只需要下载一次。对于频繁在 E2000 和 D2000 之间切换构建的人来说,省的不只是流量,还有反复校验包的等待时间。

带图形桌面的 Debian rootfs 整体构建时间会更长,主要瓶颈在 glibc 和桌面组件上。对这种重活,我通常配合make -j$(nproc --ignore=1)控制并行度,防止把构建机卡死。硬件性能允许的话,给构建机内存加到 16GB 以上,明显能减少 OOM 触发的概率。

6.4 常见错误速查表

错误现象原因解决办法
Kernel panic - not syncing: VFSrootfs 驱动未编入内核或root=参数错误开启对应文件系统驱动,改用PARTUUID指定分区
EXT4-fs: unable to read superblock分区表偏移错误或 rootfs 分区未格式化正确检查parted分区大小,重跑mkfs.ext4
/lib/aarch64-linux-gnu/libc.so.6: version GLIBC_2.34 not found用 musl/uClibc 工具链生成 rootfs,但目标系统需要 glibc工具链改为BR2_TOOLCHAIN_BUILDROOT_GLIBC=y
Error: unknown instruction内核查编报错GCC 与内核代码版本不匹配,或宿主 gcc 版本过高固定宿主 gcc 版本,或改用 Buildroot 交叉工具链
make: *** [scripts/Makefile.modinst] Error 127缺少rsync工具安装rsync
No working init foundrootfs 的/sbin/init不存在或 busybox 被关闭后没有提供 init检查 rootfs overlay,是否需要单独装 systemd/sysvinit
Console: switching to colour frame buffer device后无显示framebuffer 驱动或显示配置问题检查内核 DRM 配置和 bootargs 里vt.global_cursor_default等参数
uboot 找不到内核Image文件内核产物放在根分区而非 boot 分区,或 boot 命令路径不对检查 boot.scr 中的load路径和分区编号

7. 一点个人体会

整个项目做下来,我感触最深的一点是:构建环境一定要固定,不要跟风升级。刚开始的时候,我的构建机时不时apt upgrade一下,直到一次升级把 glibc 从 Debian 11 的版本拉到了 Debian 12 的版本,导致之前编译好的 rootfs 里所有二进制直接报GLIBC_2.34 not found,我才意识到构建环境的一致性有多重要。后来我专门用一台虚拟机,固定 Debian 11,sudo apt 只做安全更新,不升大版本,问题就再没出现过。

另外一个体会是,飞腾 E2000/D2000 这两个平台的开发,表面上看起来是“编译个镜像”这么简单,但实际上绝大部分时间花在理解板卡差异、启动协议和厂商 BSP 的细节上。Buildroot 只能帮你把重复劳动自动化,不能替代你对平台本身的理解。如果你刚拿到板子,不要急着堆功能,先把最小系统跑起来,再逐步加需求,路径会顺畅很多。

最后再分享一个实用的小技巧:在 post-image 脚本里,顺手把内核版本、rootfs 生成时间、Git commit 号一并写入/etc/buildinfo,这样日后设备多了,哪台跑的是哪个版本,一条命令就能查出来,排查现场问题能省很多事。多系统镜像维护最怕的就是“不知道设备上现在跑的是什么”,一个可靠的版本标记文件,比任何运维文档都实在。

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

FPGA高速链路调试:Aurora 8B/10B回环测试工程实践

新板卡回来&#xff0c;第一件事别急着写业务逻辑&#xff0c;先验证高速串行链路本身能不能跑通。我做过好几个带光纤口的 FPGA 项目&#xff0c;最常用的开板手段就是做 Aurora 回环测试&#xff1a;FPGA 内部发数据&#xff0c;通过光模块发出去&#xff0c;再绕回接收端&am…

作者头像 李华
网站建设 2026/9/28 7:32:00

Univer 协同编辑引擎实战:Canvas 渲染、Facade API 与 Node.js 集成指南

1. 从“univer”这个关键词说起&#xff1a;它到底是什么&#xff0c;能解决什么问题第一次看到“univer”这个词&#xff0c;很多人会以为是“universe”的缩写&#xff0c;或者某个开源社区的新玩具。实际上&#xff0c;Univer 是一套面向电子表格、文档和幻灯片的通用协同编…

作者头像 李华
网站建设 2026/9/28 7:31:22

用ESP32-CAM自制云台宠物监控:远程追踪、运动检测全解析

家里养了只猫之后&#xff0c;我最大的焦虑从“稿子写没写完”变成了“它在家到底怎么了”。上班时想看它有没有好好吃饭、喝没喝水、有没有呕吐、精神状态对不对&#xff0c;市面上普通的宠物摄像头又太死板——视角固定在那儿&#xff0c;猫走到角落就找不到了。尤其是喂食器…

作者头像 李华
网站建设 2026/9/28 7:31:18

Qt表格数据导出与打印:从CSV到PDF的组件化实现

1. 为什么一个“导出数据”的按钮背后藏着这么多硬仗我有一次给实验室检测设备写上位机&#xff0c;需求文档最后一排写着“支持数据导出和打印”。我当时心想&#xff0c;这能有多大工作量&#xff0c;无非是拼字符串写文件、再调一下打印对话框。结果设备验收那天&#xff0c…

作者头像 李华
网站建设 2026/9/28 7:30:51

VSCode搭配IAR插件:STM32开发高效编辑与调试全流程指南

说实话&#xff0c;我一开始对“VSCode IAR Build插件”这套组合是持怀疑态度的。用了多年IAR Embedded Workbench&#xff0c;习惯了它那套“能编译能下载就行&#xff0c;丑点无所谓”的编辑器&#xff0c;突然听说官方出了VSCode插件&#xff0c;心里第一反应是&#xff1a…

作者头像 李华