openUBMC QEMU/KVM CI 实践:统一构建、ARM Runner 与 Robot 冒烟

发布版 · 2026-07 · 最后更新:2026-07-29
我们把 QEMU/KVM 放进 manifest PR 门禁:独立基准中,TCG 的协议就绪耗时为 KVM 的 3.5~6.8 倍;一次代表性门禁总耗时 26 分 20 秒,其中 QEMU 阶段约 6 分钟,Robot Framework 46/46 通过。

1. 应用场景

固件编译成功,并不能证明 kernel、rootfs、DTB 与设备模型匹配,也不能证明 CLI、Web、Redfish、IPMI 已可访问。我们的做法是在代码合入前增加提交级启动冒烟:

Gitea manifest PR
  → Jenkins:统一镜像、install_sdk、编译 QEMU 产物
  → 精简载荷、SHA256、原子交付
  → ARM KVM Runner:QEMU hi1711、8 vCPU、多协议探测
  → Robot Framework、日志、报告和 PR 结论

QEMU 不替代单板。外设电气行为、上电时序、掉电恢复和长稳仍由实板验证;QEMU 负责把软件集成问题前移。

统一镜像只固定 OS、基础工具和打包能力,真正生效的 SDK、bmcgo/bingo 与 Conan 环境仍由本次 manifest 收敛:

bmcgo build -t install_sdk -b openUBMC -r artifactory -ucc
bmcgo build -b openUBMC -sc qemu -r artifactory

2. KVM 性能收益与 CI 开销

门禁的“6 分钟”还包含打包、传递、Runner 准备、Robot 和结果回传,不能当作 Guest 启动时间。社区 《基于 KVM 的 QEMU 性能测试》 给出的独立基准如下;最后一列是本文代表性生产门禁的首次观测值:

协议 KVM 基准 TCG 基准 TCG / KVM 本次门禁
IPMI 94.55 秒 404.71 秒 4.3x 97.36 秒
Redfish 81.75 秒 410.59 秒 5.0x 104.78 秒
SSH 60.53 秒 414.28 秒 6.8x 84.83 秒
Telnet 16.25 秒 56.54 秒 3.5x 37.13 秒
Web 80.63 秒 410.03 秒 5.1x 84.85 秒

两组 KVM 数值都是真实数据,但不能互相替换:

  • 性能基准在内核启动标记出现后设置 T0,5 个线程立即并行执行完整连接或接口检查;
  • 门禁在 vemake 包装脚本前设置 T0,固定等待 30 秒后才启动单循环探针,因此会计入准备时间,早期服务只能记录“首次观测”;
  • 两边对 Telnet、SSH、Web、Redfish、IPMI 的通过条件不同,固件样本和 Runner 负载也会带来波动。

所以,基准回答“硬件加速能省多少时间”,门禁数据回答“开发者实际要等多久”。不开 KVM 时,各协议达到可用状态的耗时为 KVM 的 3.5~6.8 倍。

一次代表性 Jenkins 构建的时间账如下:

阶段 耗时 占比
容器与 SDK 初始化 31.028 秒 1.96%
编译与构建 1165.849 秒 73.75%
QEMU 冒烟 365.020 秒 23.09%
收集与收尾 18.875 秒 1.19%
合计 1580.772 秒(26:20.772) 100%

QEMU 阶段中,约 164 秒用于精简打包、传递和 Runner 准备;远端运行闭环 201 秒,其中 Robot 46 条用例执行 66.088 秒。近期正常样本约为 6:03~6:17,因此公开口径是“通常约 6 分钟”。

3. Robot 结果可以下钻

Jenkins 通过 RobotPublisher 发布 output.xml、报告、日志和趋势。代表性构建 46/46 通过:CLI 4 条、Web 5 条、Redfish 6 条、IPMI 31 条。

报告还能下钻到测试套和单条用例。下图是实际的 Get Thermal Info Via Redfish,包含描述、耗时、趋势和 PASS 状态。

4. 双终端复现完整数据面

下面是正式 CI 的可见缩影:终端 1 让 QEMU 始终占用前台并输出串口;终端 2 等待 Guest、登录并执行命令。以下 <...>/absolute/path/... 均需替换为自己的环境。

4.1 从 Windows 进入统一构建镜像

ssh "<构建用户>@<构建宿主机>"

使用独立目录和容器名,不复用、不重启现有 CI 容器:

RUN_ID="community-qemu-$(date +%Y%m%d-%H%M%S)"
DEMO_ROOT="/absolute/path/community-qemu-demo/$RUN_ID"
BUILD_CTR="qemu-build-$RUN_ID"
BUILD_IMAGE="<registry>/openubmc-buildenv@sha256:<digest>"

mkdir -p "$DEMO_ROOT"/{source,tmp,opt,payload}
git clone "<manifest 仓库地址>" "$DEMO_ROOT/source/manifest"

docker run --rm -it --name "$BUILD_CTR" --network host \
  --init --pids-limit=65535 --ulimit nproc=65535:65535 \
  --privileged \
  -v "$DEMO_ROOT/source:/home/workspace/source" \
  -v "$DEMO_ROOT/tmp:/tmp" -v "$DEMO_ROOT/opt:/opt" \
  -v "$DEMO_ROOT/payload:/home/workspace/payload" \
  "$BUILD_IMAGE" bash

--privileged 与 host network 只用于专用可信构建节点,镜像必须固定 digest。

容器内构建并只打包 QEMU 所需内容:

cd /home/workspace/source/manifest
bmcgo build -t install_sdk -b openUBMC -r artifactory -ucc
bmcgo build -b openUBMC -sc qemu -r artifactory

test -f output/packet/inner/zImage_openUBMC
test -f output/packet/inner/openUBMC_qemu_default.cpio.gz
test -f temp/openUBMC_qemu_default/hi1711_9p8c.dtb

tar -C /home/workspace/source -cf - \
  manifest/build/works/packet/qemu_shells \
  manifest/output/packet/inner \
  manifest/temp/openUBMC_qemu_default/hi1711_9p8c.dtb |
  zstd -T0 -3 -o /home/workspace/payload/manifest.tar.zst
exit

4.2 校验并交付 ARM Runner

ARCHIVE="$DEMO_ROOT/payload/manifest.tar.zst"
ARM_TARGET="<ARM用户>@<ARM-KVM-Runner>"
REMOTE_TMP="/absolute/path/incoming/${RUN_ID}.uploading"
REMOTE_FINAL="/absolute/path/incoming/$RUN_ID"

(cd "$(dirname "$ARCHIVE")" &&
  sha256sum manifest.tar.zst > manifest.tar.zst.sha256)
ssh "$ARM_TARGET" "test ! -e '$REMOTE_TMP' &&
  test ! -e '$REMOTE_FINAL' && mkdir -p '$REMOTE_TMP'"
scp "$ARCHIVE" "$ARCHIVE.sha256" "$ARM_TARGET:$REMOTE_TMP/"
ssh "$ARM_TARGET" "cd '$REMOTE_TMP' &&
  sha256sum -c manifest.tar.zst.sha256 &&
  mv '$REMOTE_TMP' '$REMOTE_FINAL'"

这段是手工演示;生产 CI 还会为每次运行分配唯一 ID、动态端口和异常清理。

4.3 终端 1:QEMU 前台启动

ssh "<ARM用户>@<ARM-KVM-Runner>"

RUN_ID="<与构建端一致的运行 ID>"
INCOMING="/absolute/path/incoming/$RUN_ID"
DEMO_ROOT="/absolute/path/runs/$RUN_ID"
MANIFEST="$DEMO_ROOT/manifest"
CTR="qemu-demo-$RUN_ID"
CI_CTR="<现有 CI 基线容器名>"
RUN_IMAGE="sha256:<已核对的运行镜像 ID>"

mkdir -p "$DEMO_ROOT"
tar --zstd -xf "$INCOMING/manifest.tar.zst" -C "$DEMO_ROOT"

CPIO_STAGE="$DEMO_ROOT/cpio-stage"
QEMU_SETUP="$MANIFEST/temp/qemu_temp/qemu_start_temp"
mkdir -p "$CPIO_STAGE" "$MANIFEST/output/data" "$QEMU_SETUP"
gzip -dc "$MANIFEST/output/packet/inner/openUBMC_qemu_default.cpio.gz" |
  (cd "$CPIO_STAGE" && cpio -idm --quiet)
cp -a "$CPIO_STAGE/data" "$MANIFEST/output/data/data"
cp -a "$CPIO_STAGE/mockdata" "$MANIFEST/output/data/mockdata"
cp -a "$CPIO_STAGE/opt/bmc/pram" "$MANIFEST/output/data/pram"
cp -a "$MANIFEST/temp/openUBMC_qemu_default/hi1711_9p8c.dtb" "$QEMU_SETUP/"
sudo install -m 0755 "<ARM64 QEMU 绝对路径>" \
  "$QEMU_SETUP/qemu-system-aarch64-release"
test -c /dev/kvm
sudo podman inspect "$CI_CTR" \
  --format 'CI_BEFORE={{.State.Status}} STARTED={{.State.StartedAt}}'

完整前台命令如下;没有 -dqemu-system-aarch64 就是容器主进程:

sudo podman run --rm -it \
  --name "$CTR" \
  --network host \
  --device /dev/kvm \
  --security-opt label=disable \
  --cpus=8 --memory=12g --pids-limit=512 \
  -v "$DEMO_ROOT:/demo:rw" \
  -w /demo/manifest \
  "$RUN_IMAGE" \
  ./temp/qemu_temp/qemu_start_temp/qemu-system-aarch64-release \
  -machine hi1711 \
  -accel kvm \
  -nographic \
  -smp 8 -m 4G \
  -kernel output/packet/inner/zImage_openUBMC \
  -dtb temp/qemu_temp/qemu_start_temp/hi1711_9p8c.dtb \
  -append 'root=/dev/ram0 ip=dhcp console=ttyS0 rdinit=/init memmap=1886M@0x87200000 ramdisk_size=30720 earlycon=serial-mm,0x08710000 printk.time=y ignore_loglevel ekbox=0x1f000$0x84be0000 kbox_mem=2432k@0x84900000' \
  -initrd output/packet/inner/openUBMC_qemu_default.cpio.gz \
  -netdev 'user,id=eth0,hostfwd=tcp:127.0.0.1:22101-:22,hostfwd=tcp:127.0.0.1:22102-:443,hostfwd=tcp:127.0.0.1:22103-:23,hostfwd=udp:127.0.0.1:22104-:623,hostfwd=udp:127.0.0.1:22105-:161' \
  -fsdev 'local,security_model=passthrough,id=fsdev1,path=output/data/pram' \
  -device 'virtio-9p-device,id=fs1,fsdev=fsdev1,mount_tag=pramshare' \
  -fsdev 'local,security_model=passthrough,id=fsdev0,path=output/data/data' \
  -device 'virtio-9p-device,id=fs0,fsdev=fsdev0,mount_tag=hostshare' \
  -fsdev 'local,security_model=passthrough,id=fsdev2,path=output/data/mockdata' \
  -device 'virtio-9p-device,id=fs2,fsdev=fsdev2,mount_tag=mockshare' \
  -monitor 'telnet:127.0.0.1:22106,server,nowait'

4.4 终端 2:进入 Guest

ssh "<ARM用户>@<ARM-KVM-Runner>"
deadline=$((SECONDS + 240))
until ssh-keyscan -T 1 -p 22101 127.0.0.1 2>/dev/null |
  grep -q 'ssh-ed25519'; do
  (( SECONDS < deadline )) || exit 1
  sleep 3
done
ssh -tt -p 22101 -o StrictHostKeyChecking=no \
  -o UserKnownHostsFile=/dev/null "<Guest用户>@127.0.0.1"

密码只在交互提示中输入。进入 Guest 后读取真实状态:

printf 'ARCH='; uname -m
printf 'MODEL='; tr -d '\0' </proc/device-tree/model; echo
printf 'VCPU='; grep -c '^processor' /proc/cpuinfo
printf 'UPTIME_SEC='; cut -d' ' -f1 /proc/uptime
systemctl --state=running --type=service --no-legend --no-pager | wc -l

实机结果为 aarch64Hisilicon Hi1711 ASIC、8 vCPU 和 24 个 running service。上方仍是 QEMU 前台串口,下方才是 Guest shell。

结束时先退出 Guest,再通过本次私有 Monitor 端口关闭 QEMU:

exit
echo quit | nc -w 4 127.0.0.1 22106
sudo podman inspect "$CI_CTR" \
  --format 'CI_AFTER={{.State.Status}} STARTED={{.State.StartedAt}}'

CI_AFTERStartedAt 应与 CI_BEFORE 完全一致;否则不能声称本次演示没有影响原 CI。

5. 开销与工程结论

本次可视化复现的短时采样为:CPU 约 0.9~1 个宿主机逻辑核,内存约 1.33~1.55 GB,PIDs 15;Guest 配置为 8 vCPU/4 GiB,容器上限 8 CPU/12 GiB。它是单次即时采样,容量规划仍应看节点监控、队列和并发。

真正把演示变成 CI,还需要唯一运行 ID、独立目录与端口、/dev/kvm 显式透传、固定镜像 digest、SHA256 与原子交付、多协议分别探测、失败日志先收集后清理,以及 Robot 报告和 PR 状态回写。

最终得到的不是“后台跑了一个 QEMU”,而是一条可量化、可追溯的提交级质量门禁:

  • 代表性门禁约 26 分钟,编译仍是主成本;
  • QEMU 阶段通常约 6 分钟,占本次总时长约 23%;
  • 独立基准中,TCG 的协议就绪耗时为 KVM 的 3.5~6.8 倍;
  • 本次门禁在 37~105 秒间先后观测到协议就绪,Robot 46 条用例本身执行 66.088 秒;
  • 双终端实操只是生产 CI 的可见缩影,单板验证仍不可替代。

6. 开发者实操:统一镜像构建到 ARM KVM Guest

前文站在生产 CI 视角说明门禁如何隔离运行、交付产物、执行 Robot 冒烟并回写
PR;本章站在开发者视角,给出从源码到 Guest 的完整个人标准操作流程。

以下地址、账号和路径均为占位符,使用时替换为自己的环境。密码只在交互提示中输入,
不要写入命令、脚本或截图。

两部分不是重复文档,也不能混用命令:

维度 第 4 章:CI 可见缩影 第 6 章:开发者个人 SOP
目标 解释生产门禁的数据面和隔离机制 让开发者从零完成一次真实启动
镜像 固定 digest,保证门禁可复现 拉取 latest,同时记录实际镜像 ID
构建 唯一运行 ID、CI 工作区和 Runner 交付 后台构建容器、固定个人工作区
ARM 运行 Runner 容器与 CI 基线保护 ARM 主机原生 QEMU 前台运行
观察方式 QEMU 与 Guest 双终端展示 x86 构建、QEMU、Guest 三终端协作
输出 Robot 报告和 PR 结论 Guest 状态、人工验收和完整清理

6.1 阅读顺序与三个终端

建议使用三个 Windows Terminal 窗口:

终端 位置 用途
终端 1 x86_64 构建机 拉 manifest、启动统一镜像、构建并传包
终端 2 AArch64 KVM 主机 校验产物、准备 9p 目录、前台启动 QEMU
终端 3 AArch64 KVM 主机 等待并登录真实 Guest

标准顺序如下,首次操作时不要跳步:

  1. 从 manifest main 的新工作区开始并记录 commit。
  2. 拉取 openubmc_buildenv:latest,后台启动本轮构建容器。
  3. 执行 install_sdk,成功后再采集生效环境。
  4. 构建 QEMU 单包,检查内容并生成 SHA-256。
  5. 将产物传到 ARM,校验后准备三个 9p 目录。
  6. 在终端 2 前台启动 Hi1711 QEMU/KVM。
  7. 从终端 3 登录真实 Guest 并完成运行态检查。
  8. 停止 QEMU,释放端口并清理两端本轮资源。

终端 1 登录构建机:

ssh <构建用户>@<x86构建机>

需要管理员权限时再进入 root Shell:

sudo -i

确认构建机和 Docker:

uname -m
docker version --format 'Docker {{.Server.Version}}'

如果构建机有多个网络出口,先确认专用 Docker 网络绑定到可以访问代码、镜像和
Conan 服务的物理网卡:

docker network inspect <构建网络> --format 'parent={{index .Options "parent"}}'

多出口环境不要直接改用 --network host,否则容器可能从错误路由访问内部服务。

6.2 通过 HTTPS 拉取 manifest main

进入工作目录:

cd <工作目录>

确认本轮目录不存在:

test ! -e manifest

通过 HTTPS 拉取最新 manifest:

git clone https://<Gitea地址>/<组织>/manifest.git

进入仓库:

cd manifest

确认分支和 commit:

git switch main
git pull --ff-only origin main
git status --short --branch
git rev-parse HEAD

每次都要记录实际 commit,不复用旧工作区或旧产物。

6.3 后台启动统一构建镜像

拉取最新统一镜像:

docker pull <镜像仓>/openubmc_buildenv:latest

记录本次实际镜像 ID:

docker image inspect <镜像仓>/openubmc_buildenv:latest --format '{{.Id}}'

后台启动本轮容器:

docker run -d --rm \
  --name openubmc-qemu-build \
  --init \
  --pids-limit=65535 \
  --ulimit nproc=65535:65535 \
  --privileged \
  --network <构建网络> \
  -v <manifest绝对路径>:/home/workspace/source/manifest \
  -v <SSH目录>:/root/.ssh:ro \
  -w /home/workspace/source/manifest \
  <镜像仓>/openubmc_buildenv:latest \
  sleep infinity

检查容器:

docker ps --filter name=openubmc-qemu-build

检查默认路由:

docker exec openubmc-qemu-build ip -4 route show default

这里保留 -d--rm--init:容器在后台运行,停止后自动删除,init
负责回收构建过程中产生的子进程。

私有组件源码如果在 recipe 中仍使用 HTTPS,而构建凭据只提供 SSH,可只在本轮
容器中配置 URL 映射:

docker exec openubmc-qemu-build \
  git config --global \
  url."ssh://git@<Gitea地址>/".insteadOf \
  "https://<Gitea地址>/"

该操作不修改 manifest 和组件仓代码。

如 SSH 服务会自动更新主机密钥,可只在本轮容器中关闭该行为并验证仓库可达:

docker exec openubmc-qemu-build \
  git config --global core.sshCommand "ssh -o UpdateHostKeys=no"
docker exec openubmc-qemu-build \
  git ls-remote ssh://git@<Gitea地址>/<组织>/manifest.git HEAD

6.4 初始化并核对生效环境

先执行 manifest 声明的环境初始化:

docker exec openubmc-qemu-build \
  bmcgo build -t install_sdk -b openUBMC -r artifactory -ucc

首次执行可能下载较大的 SDK。只要进度或日志仍在变化,就不要并发启动第二个
install_sdk

只有该命令成功后,才采集生效环境:

docker exec openubmc-qemu-build bmcgo --version
docker exec openubmc-qemu-build bingo --version
docker exec openubmc-qemu-build conan remote list
docker exec openubmc-qemu-build readlink -f /opt/hi1711sdk

统一镜像启动时自带的版本只是初始环境。这里显示的 bmcgo、bingo、Conan 和 SDK
必须与本次 manifest 一致,否则不要继续构建。

6.5 构建并校验 QEMU 单包

执行 QEMU 目标构建:

docker exec openubmc-qemu-build \
  bmcgo build -b openUBMC -sc qemu -r artifactory

进入输出目录:

cd <manifest绝对路径>/output

检查产物:

ls -lh openUBMC_qemu_default.data.gz

检查包内容:

tar -tzf openUBMC_qemu_default.data.gz

至少应包含:

openUBMC_qemu_default/hi1711_9p8c.dtb
openUBMC_qemu_default/openUBMC_qemu_default.cpio.gz
openUBMC_qemu_default/zImage_openUBMC

生成校验文件:

sha256sum openUBMC_qemu_default.data.gz \
  > openUBMC_qemu_default.data.gz.sha256

显示并留存本轮校验值:

cat openUBMC_qemu_default.data.gz.sha256

6.6 传输并准备 ARM 运行目录

终端 2 登录 ARM KVM 主机:

ssh <ARM用户>@<ARM-KVM主机>

确认架构和 KVM:

uname -m
test -c /dev/kvm && echo KVM_OK

确认 Hi1711 machine 和 KVM accelerator:

<Hi1711-QEMU绝对路径>/qemu-system-aarch64 -machine help | grep -w hi1711
<Hi1711-QEMU绝对路径>/qemu-system-aarch64 -accel help | grep -w kvm

确认本轮端口未被占用:

ss -H -lntup | grep -E ':2210[1-6]\b'

无输出后确认本轮目录不存在:

test ! -e <ARM工作目录>.uploading
test ! -e <ARM工作目录>

两项都通过后创建临时上传目录:

mkdir <ARM工作目录>.uploading

回到终端 1,传输包和校验文件:

scp -O \
  openUBMC_qemu_default.data.gz \
  openUBMC_qemu_default.data.gz.sha256 \
  <ARM用户>@<ARM-KVM主机>:<ARM工作目录>.uploading/

如果目标未启用 SFTP subsystem,需要保留 -O 使用传统 SCP 协议。

回到终端 2,校验并原子改名:

cd <ARM工作目录>.uploading
sha256sum -c openUBMC_qemu_default.data.gz.sha256

必须显示 openUBMC_qemu_default.data.gz: OK,否则停止后续操作。

cd ..
mv <ARM工作目录>.uploading <ARM工作目录>

解压:

cd <ARM工作目录>
tar -xzf openUBMC_qemu_default.data.gz
cd openUBMC_qemu_default
mkdir -p share output/data

只提取 QEMU 需要的 9p 数据:

cd share
gzip -dc ../openUBMC_qemu_default.cpio.gz | \
  cpio -idmu --quiet \
  'data' 'data/*' \
  'mockdata' 'mockdata/*' \
  'opt/bmc/pram' 'opt/bmc/pram/*'
cd ..
cp -a share/data output/data/data
cp -a share/mockdata output/data/mockdata
cp -a share/opt/bmc/pram output/data/pram

检查 QEMU 最终需要的目录:

du -sh output/data/data output/data/mockdata output/data/pram

三个目录都必须存在,不能把 share 本身错误地作为 9p 根目录。

6.7 前台启动 QEMU/KVM

在终端 2 前台执行:

<Hi1711-QEMU绝对路径>/qemu-system-aarch64 \
  -machine hi1711 \
  -accel kvm \
  -nographic \
  -smp 8 \
  -m 4G \
  -kernel ./zImage_openUBMC \
  -dtb ./hi1711_9p8c.dtb \
  -initrd ./openUBMC_qemu_default.cpio.gz \
  -append 'root=/dev/ram0 ip=dhcp console=ttyS0 rdinit=/init memmap=1886M@0x87200000 ramdisk_size=30720 earlycon=serial-mm,0x08710000 printk.time=y ignore_loglevel ekbox=0x1f000$0x84be0000 kbox_mem=2432k@0x84900000' \
  -netdev 'user,id=eth0,hostfwd=tcp:127.0.0.1:22101-:22,hostfwd=tcp:127.0.0.1:22102-:443,hostfwd=tcp:127.0.0.1:22103-:23,hostfwd=udp:127.0.0.1:22104-:623,hostfwd=udp:127.0.0.1:22105-:161' \
  -fsdev 'local,security_model=passthrough,id=fsdev1,path=./output/data/pram' \
  -device 'virtio-9p-device,id=fs1,fsdev=fsdev1,mount_tag=pramshare' \
  -fsdev 'local,security_model=passthrough,id=fsdev0,path=./output/data/data' \
  -device 'virtio-9p-device,id=fs0,fsdev=fsdev0,mount_tag=hostshare' \
  -fsdev 'local,security_model=passthrough,id=fsdev2,path=./output/data/mockdata' \
  -device 'virtio-9p-device,id=fs2,fsdev=fsdev2,mount_tag=mockshare' \
  -monitor 'telnet:127.0.0.1:22106,server,nowait'

终端 2 必须保留在前台持续显示串口日志,不使用 -dnohup。端口开始监听
只表示 QEMU 已建立转发,不代表 Guest 服务已完成启动。

本次实测和生产门禁均使用 8 vCPU / 4 GiB。manifest 默认配置虽然是
4 vCPU / 4 GiB,但当前 Hi1711 设备树描述了 8 个 CPU;4 核启动会对
CPU4 到 CPU7 报 PSCI 启动失败。8 核不是为了堆资源提速,而是为了与设备树和
生产门禁保持一致。

6.8 登录并验收真实 Guest

终端 3 登录同一 ARM 主机:

ssh <ARM用户>@<ARM-KVM主机>

等待 Guest SSH 主机密钥:

ssh-keyscan -T 3 -p 22101 127.0.0.1 \
  > <ARM工作目录>/guest_known_hosts
test -s <ARM工作目录>/guest_known_hosts && echo GUEST_SSH_READY

没有输出时等待一段时间后重试,不要只根据端口监听判断 Guest 已启动完成。

登录 Guest:

ssh -tt -p 22101 \
  -o StrictHostKeyChecking=yes \
  -o UserKnownHostsFile=<ARM工作目录>/guest_known_hosts \
  <Guest用户>@127.0.0.1

进入 Guest 后检查:

uname -m
tr -d '\0' < /proc/device-tree/model
grep -c '^processor' /proc/cpuinfo
uname -r
systemctl --no-legend --type=service --state=running | wc -l

正常结果应能确认 aarch64Hisilicon Hi1711 ASIC、8 个处理器以及实际
kernel 版本。Guest 是精简系统时可能没有 getconf,处理器数量以
/proc/cpuinfo 为准。

上图左侧是仍在运行的 QEMU 前台串口,右侧才是通过转发端口登录的真实 Guest
Shell。仅看到 QEMU 日志不能替代 Guest 登录验收。

6.9 正常停止与完整清理

先在终端 3 退出 Guest:

exit

回到终端 2,可在 QEMU 前台依次按 Ctrl+A 和小写 x 停止。若前台转义键
不可用,再从终端 3 连接本轮 monitor:

nc 127.0.0.1 22106

看到 (qemu) 后输入:

quit

QEMU 退出后检查端口:

ss -H -lntup | grep -E ':2210[1-6]\b'

确认无输出,再删除 ARM 本轮目录:

rm -rf <ARM工作目录>

回到终端 1,停止并自动删除构建容器:

docker stop openubmc-qemu-build

确认容器已由 --rm 自动删除:

docker ps -a --filter name=openubmc-qemu-build

确认工作区不再需要后删除:

rm -rf <manifest绝对路径>

不要执行 docker system prune,也不要停止其他 CI 容器或 QEMU 实例。

6.10 常见故障与处理

现象 常见原因 处理
manifest 目录已存在 复用了旧工作区 先检查是否有未提交内容,再新建本轮目录;不要直接覆盖
clone 遇到证书错误 私有 CA 未进入系统信任链 优先安装 CA;不要全局关闭 Git TLS 校验
容器访问不到依赖服务 Docker 网络走错物理出口 回到 6.1、6.3,检查专用网络和容器默认路由
构建要求输入 Gitea HTTPS 账号 recipe URL 与 SSH 凭据不匹配 检查只读 SSH 挂载和本轮 URL 映射,不在构建日志输入密码
Conan 显示 anonymous 后停住 Conan 凭据或 buildenv 初始化未生效 检查 install_sdk 完整日志和 manifest buildenv 配置
fork: Resource temporarily unavailable 缺少 init 或进程额度不足 保留 --init--pids-limitnproc 参数
QEMU 包缺少 DTB、cpio.gz 或 kernel 构建未完成或取错输出目录 检查构建返回码和 output 目录,不继续传包
SCP 认证后提示 Connection closed ARM 未启用 SFTP subsystem 使用 scp -O 强制传统 SCP 协议
Guest 设备数据缺失 9p 目录层级错误 检查 output/data/{data,mockdata,pram}
端口监听但 Guest SSH 不通 QEMU 转发已建立,Guest 仍在启动 等待串口和 ssh-keyscan,不要只看端口
系统 QEMU 找不到 hi1711 使用了发行版通用 QEMU 切换到包含 Hi1711 machine 的 QEMU 绝对路径
4 核启动出现 CPU4~CPU7 PSCI 错误 vCPU 数少于当前设备树描述 使用与生产门禁一致的 8 vCPU / 4 GiB

6.11 实测耗时与社区基准

本轮标准关键路径如下:

阶段 实测耗时
HTTPS 拉取 manifest 0.816 s
拉取统一镜像 0.183 s,已有缓存
install_sdk 77.868 s
QEMU 构建 212.025 s
SCP 传输 85 MiB 30.189 s
ARM 解包与 9p 准备 1.373 s
8 核 QEMU 启动到内核标记 11.911 s
内核标记到五种协议全部就绪 95.440 s
可相加关键路径 429.805 s,即 7:09.805

该关键路径不包含人工输入、截图、Guest 手工登录和清理。审计式完整实操还执行了
4/8 核 A/B、异常恢复和证据采集,总墙钟时间为 45:33;正常只执行一次标准流程,
建议预留 10 到 12 分钟,首次下载镜像或 SDK 时另计网络时间。

为了与社区
《基于 KVM 的 QEMU 性能测试》
对齐,本轮复用了同一套 QA BootTimeMonitor 五协议并行探测语义:

协议 社区 KVM 基准 本轮 4 vCPU 本轮生产配置 8 vCPU
IPMI 94.55 s 93.17 s 93.17 s
Redfish 81.75 s 94.58 s 95.44 s
SSH 60.53 s 73.29 s 73.29 s
Telnet 16.25 s 29.02 s 29.02 s
Web 80.63 s 93.79 s 94.42 s

社区脚本原先等待的 " blocks" 串口标记在当前固件中没有出现,本轮改用首条
"Linux version" 内核日志作为 T0。协议检查语义一致,但固件样本、T0 文本和
Runner 负载并非完全相同,因此这里用于对齐数量级和趋势,不把十几秒差值直接解释
为性能回退。

4 核与 8 核的五协议就绪差异均小于 1 秒,说明当前服务启动主要是串行依赖,增加
vCPU 不能显著缩短启动时间。8 核配置的价值是消除 4 核时 CPU4 到 CPU7 的 PSCI
错误,并与当前设备树及生产 CI 保持一致。

社区基准记录的峰值 CPU 4.54 cores、平均 CPU 1.01 cores 是宿主机实际使用量,
不是 Guest vCPU 数。本轮 8 核运行态单点采样约为 0.53 个宿主机逻辑核,RSS 约
1.48 GiB,内存数量级与社区 KVM 基准一致。

个人复现的价值,是让开发者能够沿着与生产门禁一致的数据链独立定位构建、交付和
启动问题;它不替代正式 CI 的自动化结论,也不替代单板上的硬件行为和长稳验证。

1 个赞

修正+1