OpenBMC 的活真不是说拿来就能开工的,光是把编译环境拉起来就有不少人卡在头三天。玩过 Yocto 的朋友都知道,那套东西和你平时 nginx 直接 make 完全不是一个量级——依赖多、下载量大、编译时间长,动不动就是二三十 GB 的磁盘空间、一晚上的全量构建。这还是在网络畅通、工具链熟悉的前提下。要是再把“硬件移植”这件事加进来,比如给一块新板子加 OpenBMC 支持,那开发环境构建就更像是一场需要提前排雷的工程,而不是单纯的搭环境。
这篇文章聚焦 OpenBMC 开发里的第二步:构建一套能正常编译、能输出固件镜像、能直接用于后续硬件移植的开发环境。说清楚目录结构怎么摆、bitbake 怎么跑、新板子怎么加、踩坑怎么排。既有命令也有思路,配着实操记录讲,尽量让看的人少走点弯路。
1. 先把 OpenBMC 的工程构成和构建原理理清楚
1.1 它不是一个仓库,而是一堆仓库
OpenBMC 是 Linux Foundation 托管的开源项目,但这玩意儿不是“clone 一个仓库改一行就能跑”的项目。从代码组织方式上看,它是由一个主仓openbmc/openbmc加无数个下一级子模块组成的多层次结构。主仓里包含的更多是元数据、构建策略和各子模块的版本指针,真正的板级代码、C++ 服务(比如 phosphor 系列)、Kernel 补丁、U-Boot 源码、BIOS/BIOS 相关的 fw 接口,都是以 Git 子模块的方式嵌进来的。
这一点和 Android 的 repo 管理方式有点像。最初玩的时候,如果你只浅层 clone 主仓,进去会发现meta-phosphor、meta-aspeed、qemu这些目录内容是空的。必须先git submodule update --init --recursive把全部子模块拉下来,构建系统才看到真正的源码。
从构建系统角度,OpenBMC 用的是 Yocto Project,底层是 BitBake 和 OpenEmbedded。BitBake 是任务执行引擎,OpenEmbedded 的 meta 层负责描述“从源码到镜像”的每一个 step。OpenBMC 的本质就是若干 OE 层叠加,比如meta-phosphor放通用 BMC 功能,meta-aspeed放 ASpeed 芯片相关,meta-google、meta-facebook、meta-ibm等对应各家的板子。
理解这个结构,构建环境才不会瞎。你处理的问题分几类:主仓构建逻辑调整、某个 meta 层的新增改动、某个 C++ service 的独立编译、kernel 补丁的修改。它们处理的手段不太一样,但都在同一套 bitbake 工作流里。
1.2 为什么构建环境比代码本身更考验人
说句实在话,OpenBMC 的代码修改多数是模块化的,改一个 D-Bus 方法或者调一个 phosphor-state-manager 的行为,很多工程师能搞定。但构建环境这一关,卡住的概率反而最高。原因有三点:
第一是依赖链太长。从 host 系统的 gcc/make 到 Python 模块,到chrpath、diffstat、texi2html这些工具,任何一环缺了或者版本不对,都可能在某次 parse 阶段报错。第二是下载量大。OpenBMC 构建会拉取 Linux Kernel、U-Boot、OpenSSL、glibc 等大批量的源码包,动辄四五个 GB,网络稍不稳定就失败。第三是环境的一致性要求高。Yocto 官方支持 Ubuntu 和 Fedora 系列,但即便是 Ubuntu,不同版本对 Python、make 的版本要求也不一样,直接用错宿主环境,编译到一半才暴露问题是常有的事。
所以,构建开发环境的本质不光是“能把命令跑通”,而是搭建一个稳定、可复现、利于调试的宿主系统。这块值得花时间认真对待,省得后面移植新板子时,每次改一个 dts 都要重新痛苦挣扎一遍。
2. 搭建 OpenBMC 构建环境的前期准备
2.1 主机系统怎么选
官方一贯推荐 Ubuntu LTS,我自己在 Ubuntu 20.04 和 22.04 上都跑过,20.04 配老版本 OpenBMC 更顺滑,22.04 在编译速度上略好。如果你是纯新手,我建议直接用 Ubuntu 22.04,遇到问题社区匹配度高,依赖安装也容易。
CPU 没硬性限制,但核心数直接决定编译时间。我在自己的机器上实测过,8 核 16 线程编译witherspoon这类完整镜像,全量大概需要 3 个小时左右;16 核 32 线程能压到 1.5 到 2 小时。内存建议至少 16GB,低于这个数,并行编译任务稍微开多点就会爆内存,bitbake 直接显示MemoryError或者被 OOM-killer 杀掉。
磁盘空间是容易被低估的点。OpenBMC 构建时,build/downloads里保存所有源码包,build/tmp里放编译产物和 sstate 缓存。首次构建一个基本镜像,容易摸到 20GB;如果多编译几个目标板型,翻倍也正常。所以分区至少留 100GB 才玩得开。我当时犯过错误:根目录只有 60GB,编到一半磁盘满,整个构建目录都坏了,只能删掉tmp重新来。
2.2 系统依赖安装清单
不同 Linux 发行版装依赖的命令不同,Ubuntu/Debian 系我整理了一份比较完整的最小集,照着装就行:
sudo apt update sudo apt install -y \ git python3 python3-dev python3-venv python3-setuptools \ gcc g++ make cmake ninja-build \ gawk wget diffstat texinfo chrpath \ rsync cpio file socat iputils-ping \ libssl-dev liblz4-tool zstd \ bison flex autoconf automake libtool \ libncurses-dev libxml2-utils \ unzip zip xz-utils \ bc u-boot-tools注意,不同 OpenBMC 版本对 host 包的要求略有差异,但上面这套基本覆盖 90% 的场景。如果 build 过程中 BitBake 提示缺了某个 host 工具,按提示补装即可。
还有个小细节:Ubuntu 20.04 自带 Python 3.8,而 22.04 是 3.10,都不影响编译 OpenBMC 的源码包,因为 Yocto 构建时用的是它自己下载到tmp/hosttools里的一套工具链,宿主机的大多数工具只用于“启动构建环境”。只有少数如git、tar、gzip这类底层工具会直接调用宿主版本,所以宿主里别装些奇奇怪怪的 alias,避免干扰。
2.3 拉取代码与子模块初始化
OpenBMC 主仓现在体积也不小,直接普通 clone 没问题,但建议加--recursive一次性把所有子模块拉下来:
git clone https://github.com/openbmc/openbmc.git --recursive如果之前只 clone 了主仓而忘了递归子模块,可以在主仓根目录里补:
git submodule update --init --recursive --depth=1推荐加--depth=1的原因很实际:OpenBMC 的历史提交非常多,完整拉取所有历史会占用巨大空间,而绝大多数人只需要最新状态。尤其子模块里包含 Linux Kernel 这类仓库,全历史动辄几个 GB,depth 限制能省很多时间和磁盘。
有一点要提醒:OpenBMC 子模块的分支切换和主仓版本是联动的,不要乱 checkout 某个子模块的 master 分支。如果主仓指向某个特定 commit,子模块也要保持对应 commit,否则构建时大概率会出现“recipe 找不到 source”或者编译到一半失败的情况。这也是 Yocto 体系下“可复现构建”的基础。
3. 首次构建 OpenBMC 镜像的完整实操
3.1 初始化构建目录与选择目标机型
先看代码根目录的结构。拉完代码后,你会看到一个setup脚本,它是构建环境的入口。用法很简单:
source setup <machine>比如要构建 IBM Witherspoon 机型的镜像:
source setup witherspoon执行完这步后,项目根目录下会生成build/witherspoon目录,并且环境变量也被设置好了,这之后才能直接跑bitbake。这个目录就是你的工作区,所有编译中间产物都在里面,换板型只需要重新执行source setup <machine>,互不影响。
如果想看当前支持哪些机器,可以直接:
ls meta-*/*/conf/machine/*.conf或者查看setup脚本里列出的 machine 列表。
这里有个新手容易懵的点:source setup之后 Bash 提示符前面会多出一串类似openbmc-env的前缀,这是正常的,说明 BitBake 环境已经激活。如果你开了多个终端,每个终端在跑 bitbake 之前都必须重新source setup,不然找不到配置环境变量。
3.2 目标镜像概念与生成固件
OpenBMC 框架里,bitbake的默认目标通常不是“镜像文件”,而是一个 image 配方。比如:
bitbake obmc-phosphor-image这个obmc-phosphor-image是 OpenBMC 的基础系统镜像,里面包含了 Phosphor 基础服务、BMC 系统管理功能、WebUI 等。如果是硬件移植阶段,一般先编这个镜像,确保最基础的系统能跑起来,再按需追加 layer 和其他功能包。
编译完成后,镜像产物在:
build/witherspoon/tmp/deploy/images/witherspoon/目录下常见几个文件:
obmc-phosphor-image-witherspoon.static.mtd:这是最终刷入 BMC 的镜像,对应的静态布局 mtd 格式。obmc-phosphor-image-witherspoon.ubi:UBI 文件系统镜像。u-boot.bin、u-boot.elf:U-Boot 相关。fitimage-*或kernel-fit:打包了 kernel 的 FIT 镜像。
以*.mtd结尾的是刷机常用固件,很多工程师直接拿这个文件通过 ASpeed 的flashrom或者 BMC 内部分区烧写工具写入 Flash。
3.3 加速构建的几招实操
首次构建全量可能耗时很久,这里有几个实测有效的加速方法。
最有效的是开启共享状态缓存(sstate cache)。OpenBMC 默认在每个板型的 build 目录里有自己的tmp/sstate-cache,但你可以让多个板型共享一个缓存,减少重复编译。修改conf/local.conf,设置:
SSTATE_DIR ?= "/path/to/shared/sstate-cache" DL_DIR ?= "/path/to/shared/downloads"DL_DIR是所有板型共享的源码包下载目录,SSTATE_DIR是编译状态缓存目录。这样当你在不同机器之间切换,或者给相同架构的新板子做构建时,大量编译任务可以直接命中缓存,不用重新编译。
第二个加速是增加并行度。在conf/local.conf里加:
BB_NUMBER_THREADS = "16" PARALLEL_MAKE = "-j16"数值按 CPU 核心数调整,一般是物理核心数或线程总数。不是越大越好,我在 8 核机器上设置 16 后反而因为内存交换导致变慢,12GB 内存下建议最多等于核心数。
第三招是配置本地镜像源。如果你在国内网络环境,GitHub 和 kernel.org 下载源可能不稳定,可以换用镜像源站点,比如部分大厂内网镜像或高校镜像站。这个根据自己的环境灵活处理,核心是把DL_DIR先准备好的源码包填满,离线构建也能成功。
我自己的习惯是先跑一次bitbake obmc-phosphor-image,什么都不改,让它老老实实下载所有依赖,确认能完整编出镜像。这一步跑通,就证明宿主环境和依赖链没问题。之后再开始改代码、加板型,遇到问题就能确定是代码改动造成的,而不是环境导致的。
4. 硬件移植场景下的开发环境配置
4.1 新板子从哪里入手
OpenBMC 所谓“硬件移植”,本质上就是让 OpenBMC 的软件栈支持你手里那块新硬件。相比通用 Linux 移植,OpenBMC 的硬件抽象层次更多:有 U-Boot、有 Linux Kernel、有设备树(dts)、有各类传感器监控、有 D-Bus 服务。构建开发环境时,就要把这些都纳入到一个可编译的体系里。
最常见的起点是“叠一个自己的 meta 层”。OpenBMC 已经提供了非常多 OpenBMC 兼容板型的模板,你新做板卡时,不一定从零写所有配方,可以基于一个 CPU/SOC 最接近的已有机器来扩展。比如你的板子用 AST2600 的 BMC 芯片,那就先找到基于 ASpeed AST2600 的现有机器配置,复制一份改成自己的机器名,逐步替换设备树和外围设备信息。
大体流程是:
- 在某个 layer 下新建
meta-<company>/meta-<board>/conf/machine/<board>.conf。 - 设置机器相关变量:
PREFERRED_PROVIDER_virtual/kernel、KERNEL_DEVICETREE、UBOOT_MACHINE等。 - 添加 u-boot 的 defconfig 和 Linux kernel 的 defconfig。
- 修改 dts 设备树源文件,匹配新产品硬件。
- 继承并调整 image 配方,去掉不需要的驱动,加上需要的应用。
这套改动不是在 OpenBMC 主仓里乱来的,而是通过新 layer 去覆盖原 layer 的配方。BitBake 的 layer 优先级机制允许你用高优先级 layer 覆盖低优先级 layer 里的相同配方,所以保留 OpenBMC 官方代码不动,你的移植改动都放自己的 meta 层里,是最安全、最可维护的做法。
4.2 layer 配置与依赖关系
一个全新的层要有自己的配置文件:
meta-myboard/conf/layer.conf里面大概长这样:
BBPATH .= ":${LAYERDIR}" BBFILES += "${LAYERDIR}/recipes-*/*/*.bb ${LAYERDIR}/recipes-*/*/*.bbappend" BBFILE_COLLECTIONS += "myboard" BBFILE_PATTERN_myboard := "^${LAYERDIR}/" BBFILE_PRIORITY_myboard = "9"BBFILE_PRIORITY越高,遇到同名配方时优先使用你这层的版本。OpenBMC 官方层的优先级一般在 5 到 7 之间,你用 9 就能覆盖绝大多数东西。
然后在你的 machine conf 里面引用:
include conf/machine/include/ast2600.inc官方提供的ast2600.inc里已经定义好了很多通用项:PREFERRED_VERSION 等。你只需要补上自己机器特有参数,比如 Flash 大小、U-Boot config、DTS 文件名。
开发环境这一步要特别留意TEMPLATECONF和 meta 层的组合关系。不同板型可能依赖不同的 layer,比如用到 AST2600 就必须把meta-aspeed加进bblayers.conf。如果你的板子不需要某些功能,比如依赖 BCM 或 Intel 平台的 layer,可以从bblayers.conf里删掉,减少不必要的解析和编译,构建速度也会快一点。
4.3 内核和设备树在构建环境中的配置要点
OpenBMC 里 Linux Kernel 的构建不是单独 make,而是通过 BitBake 配方触发。OpenBMC 默认的内核配方在meta-phosphor/common/recipes-kernel/linux/下面,通常叫linux-aspeed.bb或者类似名字。硬件移植时,多半要针对新板子生成新的内核 config 或 patch。
通常的操作是:
在自定义 layer 里添加一个 linux-aspeed 的 append 文件,比如:
meta-myboard/recipes-kernel/linux/linux-aspeed_%.bbappend内容里指定你板子的设备树和 defconfig:
FILESEXTRAPATHS:prepend := "${THISDIR}/files:" SRC_URI += "file://defconfig" SRC_URI += "file://myboard.dts" KERNEL_DEVICETREE_myboard = "aspeed-bmc-myboard.dtb"把myboard.dts放到该目录的files子目录下,然后用 bitbake 构建内核。BitBake 会把这个 dts 编译成 dtb,并打包进 FIT 镜像和最终 BMC 固件里。
这里有个常见坑:内核设备树的编译是在内核源码树里进行的,OpenBMC 又常常使用 prebuilt kernel source 或者多层叠加的补丁,如果你改了 dts 但没有强制重新编译内核,bitbake 可能认为文件没变化、还用旧缓存。排错时用:
bitbake -c cleanall linux-aspeed bitbake obmc-phosphor-image强制重新获取、重新展开、重新编译。注意cleanall会删除下载目录里的内核源码缓存,下次构建又要重新拉取,所以一般用cleansstate比较多:
bitbake -c cleansstate linux-aspeed这条只会清编译缓存,源码包保留,重编速度更快。
另外,OpenBMC 里很多机器直接把 kernel defconfig 内嵌在 machine conf 里,比如:
KBUILD_DEFCONFIG_myboard = "aspeed_g5_defconfig"如果你要用自己的 config 文件,可以新增一个.config文件放到内核源码目录,或者在 append 文件里执行额外脚本。方法很多,但核心思想一致:你在自定义层里提供文件,其他行为交给 BitBake 去完成。
5. OpenBMC 构建环境排错实录
5.1 依赖与宿主环境相关报错
这类问题最多,我整理几个实录过的:
报错 1:Cannot fetch URL ...或者 download 阶段网络超时。
最常见的是下载源码包失败。检查网络、DNS、镜像源。如果用的是国内网络,GitHub 上的部分大文件容易超时,可以配置 git 低限速、或用替代源,但最稳妥的是提前把所有依赖包下载到 DL_DIR。另外子模块下载失败,可以多试几次:
git submodule sync git submodule update --init --recursive报错 2:Please install the following missing dependencies。
当 OpenBMC 要求宿主有gawk、chrpath等,而你没有装全时,setup 脚本会在开头直接提示。按提示补装:
sudo apt install gawk wget git-core diffstat unzip texinfo gcc-multilib build-essential chrpath socat cpio python3 python3-pip python3-pexpect xz-utils debianutils iputils-ping报错 3:bitbake: command not found。
激活环境后bitbake应该可用。如果提示不存在,多半是没source setup,或者 setup 过程中某个依赖报错导致环境变量没有完整注入。重新执行 source,确认开头没有 error。
5.2 BitBake 解析阶段的常见问题
解析阶段失败很让人崩溃,但大部分是语法或配置错误。比如 machine 名字拼写错误,bitbake 无法识别;或者 layer 依赖缺失、BBFILE_COLLECTIONS名字冲突。这类问题可以通过:
bitbake-layers show-recipes bitbake-layers show-layers导出当前的解析状态,排查是不是某个 meta 层没加载。
还有一类典型错误是Missing metadata for machine ...。这通常是因为你写的 machine conf 文件没有落到conf/machine目录,或者 layer 没加入bblayers.conf。检查自定义层路径是否被正确添加。
5.3 编译阶段典型异常
编译阶段最常见的错误是磁盘空间不足和内存不足。磁盘不足在编译大型软件包时非常明显,报错类似于No space left on device,而且往往在tmp/work/...目录。解决方法是清理旧构建产物:
bitbake -c cleanall <target>或者把build/tmp整个删掉重新来,前提是你有 sstate 缓存。平时建立目录软链到大分区也是个实用策略,比如:
mkdir -p /data/openbmc-build ln -s /data/openbmc-build build将build目录放到数据盘,避开系统盘空间瓶颈。
内存不足多见于并行编译过多,bitbake 进程直接被 kill。解决:降低BB_NUMBER_THREADS和PARALLEL_MAKE,或者在local.conf限制任务内存。如果你用 8 核 16GB 内存,BB_NUMBER_THREADS = "4"是稳妥的。
5.4 缓存命中与失败重编技巧
你改了自定义 layer 里的东西,但 bitbake 认为没变化,这种问题隐蔽。原因不外乎 sstate 缓存未失效,或者本机文件时间戳问题。稳妥做法是使用-c compile/-c install强制编译某个具体阶段,而不是每次全量重新构建。
比如单独重编内核:
bitbake -c compile -f linux-aspeed bitbake -c deploy -f linux-aspeed-f强制忽略缓存状态。硬件移植调 dts 和补丁时,这个技巧可以说每天都要用。
我把踩过的坑按类型整理成了下面这张表,方便遇到问题时快速定位。
| 错误类型 | 典型提示 | 排查重点 | 解决方案 |
|---|---|---|---|
| 依赖缺失 | missing dependency | 宿主包 | 按依赖列表补装 |
| 下载失败 | Cannot fetch | 网络/仓库 | 配置镜像,重试子模块 |
| 磁盘不足 | No space left on device | build 目录分区 | 清理或迁移 build 目录 |
| 内存不足 | OOM killed | BB_NUMBER_THREADS | 降低并行度 |
| 解析错误 | ParseError | machine/layer 配置 | 检查 conf,运行 bitbake-layers |
| sstate 缓存不刷新 | 改动未生效 | sstate 状态 | 使用 cleansstate 或 -f 强制编译 |
6. 关于构建环境稳定复用的几个忠告
开发环境搭好以后,我强烈建议维护好两个目录:downloads和sstate-cache。前者是源码包归档,后者是编译状态缓存。在你做硬件移植、反复调整代码的那几个月,这两个目录的价值仅次于代码仓库本身。
可以把DL_DIR设成一个共享目录,比如/opt/openbmc-archive,之后所有板型、所有分支都用它;SSTATE_DIR可以每套代码对应一份,避免不同提交间的缓存污染,但同一套代码多次构建之间就可以复用。这个策略帮我节省的编译时间非常可观,至少三次以上全量构建改拼接缓存,大量公共库和工具链都能直接跳过。
最后一个忠告:不要把生产用的构建环境折腾成“测试环境”,更不要在同一个目录里既跑构建又频繁清缓存。给 OpenBMC 单独一块干净文件夹,所有 bitbake 操作都发生在激活环境里,所有临时文件都在build/tmp里解决。即使出了问题,删掉整个tmp重来也比反复排查环境变量靠谱。
我从 OpenBMC 源码构建到第一次完整刷板,前后折腾了一周多。回头再看,构建环境这一关其实没有太多捷径,就是踏踏实实把宿主环境配好、把目录规划好、把第一次全量构建跑通。后续一切硬件移植、功能裁剪、服务开发,都建立在这个“能编、能跑、能复现”的基础上。把这个地基打牢了,后面的活会顺很多。