TECH ARTICLE

dSPACE VEOS QEMU Coupling:
Yocto 鏡像製作與雙模式配置實務

以 VEOS 2026-A 的 QEMU Coupling(dsQEMU)為基礎,說明如何在 Build Server 上把一份 Yocto core-image-minimal 做成可用的 POSIX-Based V-ECU:icount 與 idle-detect 兩種時間同步模式各自需要的編譯環境設定、鏡像產出後在模擬環境端的配置,以及每個步驟的手動操作指令與對應驗證方法。

dSPACE VEOS 的 QEMU Coupling(本文簡稱 dsQEMU)讓一顆完整的 Linux 系統以 POSIX-Based V-ECU 的形式加入 VEOS 的模擬時間軸,與 Plant FMU、Classic AUTOSAR vECU 及 Restbus 容器在同一個 OSA 內協同運作。對於採用 Yocto 作為量產 Linux 發行版基礎的專案而言,這代表 SoC 端的應用可以在硬體到位之前,就與 MCU 端的 ECU 軟體一起進入整合驗證。

本文承接同系列的 Buildroot 篇,改以 Yocto Project(poky/scarthgap) 為對象。兩者的目標一致,但達成路徑差異相當大:Yocto 的 core-image-minimal 使用 sysvinit 與 eudev、預設 /dev 為空目錄、並帶有數支對虛擬 ECU 有害的開機腳本。這些差異在原生 QEMU 上完全不會顯現,卻會在 VEOS 環境下造成無任何錯誤訊息的開機停滯。以下把整段工作拆為「Build Server 端的編譯環境配置」與「鏡像產出後的模擬環境配置」兩個階段,逐步標示操作與驗證。

本文適用環境

模擬主機Ubuntu 24.04 LTS + dSPACE VEOS 2026-A(Simulator Console 26.1)、veos-qemu 26.1(QEMU 9.2.3 基礎)
Build ServerUbuntu 24.04 LTS、poky scarthgap(DISTRO_VERSION 5.0.19)、MACHINE = qemux86-64、image recipe core-image-minimal
Guestx86_64、linux-yocto 6.6.142(icount 模式使用)、sysvinit、rootfs 為單一 raw ext4 映像
idle-detect 專用Guest kernel 由 VEOS 強制替換為 veos-linux 6.6.119;模擬主機需具備 /dev/kvm

文中路徑以 $HOME/poky、$HOME/meta-veos(Build Server)與 ~/dsqemu(模擬主機)表示;主機位址、帳號、IP 請替換為您自身環境的值。所有指令均為一般使用者權限,不需要 sudo。

1. 為什麼原始 core-image-minimal 不能直接使用

這個問題必須分兩種模式回答,因為兩者的失敗性質完全不同。以未經調整的原始鏡像實測:

項目 icount 模式 idle-detect 模式
能開機嗎 ✅ 能,可開到 runlevel 5 ❌ 完全不能——整場開機連一行 INIT: 都不會出現
能執行測試嗎 ❌ 不能:沒有受測應用、eth0 沒有位址且會嘗試 DHCP ❌ 不能(尚未進入可觀測的 userspace)
問題性質 缺少測試載具與網路配置 結構性:開機路徑與鏡像的內建假設不符

1.1 idle-detect 模式的開機路徑差異

idle-detect 是 hypervisor 模式,VEOS 會強制指定自己的 guest kernel,並經由該 kernel 內建的 initramfs 進入您的 rootfs。實測到的關鍵事實是:它使用 chroot() 而非 switch_root,且僅建立了一個裝置節點。

# idle-detect 模式下 veos-linux initramfs 的實際動作(節錄自 guest console) [INFO] Root set to: /dev/vda [CALL] mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9)) [INFO] "1" > /sys/dsprobes/init [CALL] chroot("/mnt") ← 是 chroot,不是 switch_root [ 2.22] VEOS: Registered veos-clock (tick duration of 1000000ns). [ 2.25] dsprobes: Userspace enabled veos-clock. Entering VEOS simulator mode. [INFO] Attempting /sbin/init

Linux kernel 的 CONFIG_DEVTMPFS_MOUNT 自動掛載 /dev 只在「kernel 自行掛載 root 檔案系統」時觸發,走 initramfs + chroot 這條路徑不會發生。Yocto 在這裡的處境比 Buildroot 更嚴重:poky 的 USE_DEVFS 預設值為 1,語意是「讓 /dev 保持空目錄,因為 devtmpfs 會處理」。

# 未調整前,直接檢視鏡像內的 /dev(不需開機) debugfs -R 'ls -l /dev' core-image-minimal-qemux86-64.rootfs.ext4 84 40755 (2) 0 0 1024 9-Mar-2018 12:34 . 2 40755 (2) 0 0 1024 7-Aug-2026 15:11 .. # 只有 . 與 ..——完全是空的

1.2 為什麼「一行 INIT: 都沒有」

這是 Yocto 篇最重要的一個因果鏈,也是實務上最難自行診斷的症狀:

sysvinit 在讀取 /etc/inittab 之前,就先開啟 /dev/console 作為自己的標準輸出入。當該節點不存在時,init 及其後所有 rc 腳本的輸出全部落空——但 kernel 的 printk 仍然正常顯示,因為它走 console driver、不需要 /dev 節點。結果是一份「kernel 訊息完整、userspace 完全靜默」的 log,看起來像是 kernel 卡住,實際上 init 早已在執行。

關鍵推論:從 /etc/inittab 或 /etc/fstab 掛載 devtmpfs 都太晚。必須讓鏡像內本來就存在靜態的 /dev/console,這正是 §3 STEP 4 中 USE_DEVFS = "0" 這一行的意義。

1.3 Yocto 特有的三個障礙

越過 /dev 之後,還有三項在 Buildroot 上不存在、Yocto 專有的阻礙。三者的共同特徵是:都不會產生錯誤訊息,只會讓開機停在某一行不動。

障礙 實測症狀 成因與對策
eudev(udevd) 停在 udevd[71]: starting version 3.2.14;300 秒實時只推進 2.55 秒 guest 時間——是死結,不是慢 並非缺少 kernel 功能(SIGNALFD/TIMERFD/EPOLL/INOTIFY_USER/DEVTMPFS 在 veos-linux 皆為 y),而是裝置列舉流程與 veos-clock 推進協定的互動問題。對策:VIRTUAL-RUNTIME_dev_manager = "",改用純 devtmpfs
熵源缺失 Initializing random number generator... 永不繼續;移除該腳本後改停在 Waiting for kernel randomness to be initialised...(dropbear) veos-linux 沒有 CONFIG_RANDOM_TRUST_CPU,veos-qemu 預設也不提供 rng 裝置;決定性模擬環境沒有中斷抖動,熵池永遠填不滿。在 kernel 命令列加 random.trust_cpu=on 實測無效。對策:在 JSON 補上 virtio-rng 裝置(治本),並移除熵消費者
bootlogd / hwclock 通過前兩關後,停在 checkroot 之後完全沒有輸出 bootlogd 會配置 pty 並接管 /dev/console 直到開機結束——而 console 正是 VEOS 擷取 log 的通道;hwclock 會讀 RTC 覆寫系統時鐘,而該時鐘就是 VEOS 模擬時鐘。對策:於 image 後處理移除兩者的 rc link

1.4 一句話總結

core-image-minimal 是「能在 QEMU 裡開機的最小 Linux」;而 dsQEMU 需要的是「能在 VEOS 的時間軸上、透過虛擬 Ethernet 與其他模擬參與者交換資料、且不與模擬時鐘與 console 通道衝突的 V-ECU」。本文其餘篇幅就是在填補這段差距。

2. 兩種同步模式如何決定配置策略

在動手改任何設定之前,必須先理解兩種模式對鏡像的支配關係,否則很容易把「該改 JSON 的事」改進鏡像裡,付出不必要的重建成本。

面向 icount(CPU 模擬) idle-detect(Hypervisor)
Guest kernel 您自己編譯的 linux-yocto 6.6.142 VEOS 的 veos-linux 6.6.119,由 VEOS 以 -kernel 指定,您的 bzImage 不會被使用
KVM 不需要 必須具備 /dev/kvm
時間同步程度 部分同步(按指令數截斷) 完全同步(偵測 idle 才推進時間)
rootfs 的角色 與自家 kernel 搭配,行為與原生 QEMU 一致 rootfs 必須能被「別人的 kernel」以 chroot 方式帶起來——這是所有額外配置的來源
Guest /lib/modules 可用(版本相符) 不可用(為 6.6.142 編譯,kernel 為 6.6.119)。非致命,virtio 驅動皆為 built-in
init 系統選擇 不受限 建議維持 sysvinit:veos-linux 沒有 CONFIG_MEMCG,systemd 的 memory cgroup controller 不可用
典型加速比 約 0.95–0.98 約 1.000(多節點時可達 lockstep)

單一鏡像同時支援兩種模式是可行且建議的做法。本文的 Build Server 配置對 icount 完全無害(多出來的靜態 /dev 節點與 devtmpfs 掛載不影響自家 kernel),因此不需要維護兩份 rootfs——差異全部集中在兩份 JSON 配置檔。

2.1 這五件事不要改鏡像——改 JSON 就好

下列項目經常被誤認為「鏡像要改」,實際上都屬於模擬環境端的配置:

情況 在 JSON 的處理方式
Yocto qemuboot 建議 -smp 4寫 -smp 1。idle-detect 下 VEOS 會強制為 1;icount 下維持 1 可獲得較佳重現性
網卡型號預設為 e1000寫 model=virtio-net-pci。veos-linux kernel 沒有 e1000 驅動,用錯型號時 guest 內看不到網卡
idle-detect 下 console 全黑加 -serial stdio。VEOS 在該模式追加 -nodefaults,取消了預設 serial 裝置
熵源不足導致開機停滯加 -object rng-random,... + -device virtio-rng-pci。veos-linux 具備 CONFIG_HW_RANDOM_VIRTIO=y
需要臨時切換 IP 或啟用 gdbserver以 kernel 命令列參數傳遞(例如 veos.gdb=1),由 init 腳本讀取 /proc/cmdline。改 JSON 即可,不需重建鏡像

3. Build Server 端:編譯環境配置

本節目標產物為兩個檔案:tmp/deploy/images/qemux86-64/bzImage-qemux86-64.bin 與 tmp/deploy/images/qemux86-64/core-image-minimal-qemux86-64.rootfs.ext4。所有變更收斂在一個獨立 layer(meta-veos)與 local.conf 的一個設定區塊,不修改 poky 樹內任何檔案,以確保上游可隨時更新。以下每個步驟都分為操作與驗證兩部分。

STEP 1 / 8

取得 Yocto 原始碼、建立基準線並備份組態

在加入任何客製內容之前,先把 poky 取得到一個已知版本、確認未修改的狀態能完成一次建置,並保留 local.conf 與 bblayers.conf 的原始版本。這一步讓後續每一項變更都可回溯、可還原,也是日後判斷「問題出在我方客製還是上游」的基準。

(a) 建置主機的一次性前置需求

Yocto 對建置主機有明確的套件需求。以下為 Ubuntu 24.04 LTS 的清單,這是整份流程中唯一需要 sudo 的動作,且只需執行一次。

操作
# Yocto Project Reference Manual — Required Packages for the Build Host(Ubuntu/Debian) sudo apt update sudo apt install -y gawk wget git diffstat unzip texinfo gcc build-essential \ chrpath socat cpio python3 python3-pip python3-pexpect xz-utils \ debianutils iputils-ping python3-git python3-jinja2 python3-subunit \ zstd liblz4-tool file locales libacl1 # bitbake 要求 UTF-8 語系,否則解析階段即會失敗 sudo locale-gen en_US.UTF-8
驗證
lsb_release -d # 預期:Ubuntu 24.04 LTS python3 --version ; git --version ; gcc --version | head -1 locale -a | grep -i 'en_US.utf8' # 必須有輸出 df -h $HOME | tail -1 # 建議可用空間 ≥ 100 GB id -u # 不可為 0——bitbake 拒絕以 root 執行

三項常見的環境陷阱:① 不可以 root 身分建置,bitbake 會直接拒絕;② 建置目錄不要放在 NFS/SMB 等網路磁碟或大小寫不敏感的檔案系統上,會出現難以診斷的失敗;③ 若主機透過 Proxy 連外,需另行設定 git 與 bitbake 的 Proxy,否則 do_fetch 會逾時。

(b) 取得 poky 原始碼並固定版本

poky 是 Yocto Project 的參考發行版,內含 bitbake、meta、meta-poky 與 meta-yocto-bsp。本文使用 scarthgap 分支(Yocto 5.0 LTS)。

操作
cd $HOME git clone -b scarthgap https://git.yoctoproject.org/poky cd $HOME/poky # ▼ 建議:釘選到具體的釋出標籤,讓建置可重現 git fetch --tags git tag -l 'yocto-5.0*' | tail -5 # 檢視可用的 LTS 點版本 git checkout -b veos-baseline yocto-5.0.19 # 以標籤建立本地工作分支 # ▼ 若沿用分支頭部而不釘選標籤,至少要記錄當下的 commit # git rev-parse HEAD > $HOME/poky-baseline.txt
驗證
cd $HOME/poky git remote -v # 預期:origin https://git.yoctoproject.org/poky git branch --show-current # 預期:scarthgap 或 veos-baseline git describe --tags # 預期:yocto-5.0.19(或 yocto-5.0.19-NN-gXXXXXXX) git log -1 --format='%H %ad %s' --date=short grep -m1 DISTRO_VERSION meta-poky/conf/distro/poky.conf # 預期:DISTRO_VERSION = "5.0.19" ls -d meta meta-poky meta-yocto-bsp bitbake # 預期:四個目錄都存在

請記錄此處的版本代號(本文為 scarthgap)——STEP 2 的 LAYERSERIES_COMPAT_veos 必須與它一致,否則 bitbake 會拒絕解析 meta-veos。日後升級 poky 時,這兩處要一併更新。

(c) 初始化建置環境

oe-init-build-env 會建立 build/ 並產生預設的 local.conf 與 bblayers.conf,同時把 bitbake 加入 PATH。

操作
cd $HOME/poky source oe-init-build-env build # 執行後工作目錄會切換到 $HOME/poky/build # ⚠ 這個動作只對「當前 shell」有效。每開一個新終端機都要重新 source 一次, # 否則會出現 bitbake: command not found。
驗證
pwd # 預期:/home/<user>/poky/build which bitbake # 預期:.../poky/bitbake/bin/bitbake bitbake --version # 預期:BitBake Build Tool Core version 2.8.x ls conf/ # 預期:bblayers.conf local.conf templateconf.cfg

(d) 設定 MACHINE 與建置資源

預設的 local.conf 已將 MACHINE 設為 qemux86-64,正是本文所需;平行度與快取目錄則建議依主機資源明確指定。

操作
# 確認(多數情況不需修改) grep -E '^(MACHINE|DISTRO)' $HOME/poky/build/conf/local.conf # 依主機核心數調整平行度(示例為 16 核心) cat >> $HOME/poky/build/conf/local.conf <<'EOF' BB_NUMBER_THREADS = "16" PARALLEL_MAKE = "-j 16" EOF # 選用:把下載與 sstate 快取移到共用路徑,讓多個 build 目錄重複使用 # DL_DIR ?= "${HOME}/yocto-cache/downloads" # SSTATE_DIR ?= "${HOME}/yocto-cache/sstate-cache"
驗證
nproc # 對照設定值是否合理 bitbake -e core-image-minimal | grep -E '^(MACHINE|BB_NUMBER_THREADS|PARALLEL_MAKE)=' # 預期:MACHINE="qemux86-64",平行度與上述設定一致 # 使用 bitbake -e 讀「最終展開值」,比直接看 local.conf 可靠

(e) 建立基準線:先建一次未客製的鏡像

這一次建置不加入任何 VEOS 客製。目的是確認工具鏈、網路與主機資源都正常——若此時就失敗,問題與本文後續配置無關。首次完整建置在 16 核心機器上約需 1–2 小時。

操作
cd $HOME/poky/build bitbake core-image-minimal
驗證
D=$HOME/poky/build/tmp/deploy/images/qemux86-64 ls -laL $D/core-image-minimal-qemux86-64.rootfs.ext4 ls -laL $D/bzImage-qemux86-64.bin # 預期(未客製的基準線):rootfs.ext4 約 40 MB、bzImage 約 12 MB # 對照 STEP 7 的客製版本約 242 MB,差額即為除錯工具與受測應用 # 選用:以原生 QEMU 確認基準鏡像可開機(此步驟與 VEOS 無關) # runqemu qemux86-64 nographic # 離開:Ctrl-A 再按 X

此時可順手記錄一項對照數據:以 debugfs -R 'ls -l /dev' <rootfs.ext4> 檢視基準鏡像的 /dev,應該只有 . 與 ..(見 §1.1)。這是 USE_DEVFS 預設值造成的結果,也是 STEP 4 第一行設定要解決的問題——先看過「改之前」的樣子,後續的驗證才有比較基準。

(f) 備份組態

在加入任何客製之前保留原始組態,使整個變更集可回溯、可還原。

操作
cd $HOME/poky/build/conf cp -n local.conf local.conf.bak.orig # -n 避免覆蓋既有備份 cp -n bblayers.conf bblayers.conf.bak.orig
驗證
ls -l $HOME/poky/build/conf/*.bak.orig diff $HOME/poky/build/conf/local.conf $HOME/poky/build/conf/local.conf.bak.orig # 預期:兩份備份存在;此刻 diff 應無輸出(尚未開始客製) # 完整還原本機到基準線的方式(日後需要時): # cp -f local.conf.bak.orig local.conf # cp -f bblayers.conf.bak.orig bblayers.conf # rm -rf $HOME/meta-veos
STEP 2 / 8

建立 meta-veos layer 骨架

所有 VEOS 專用內容集中於一個獨立 layer,與 poky 樹完全隔離。這是本配置能長期維護的基礎。

操作
mkdir -p $HOME/meta-veos/conf mkdir -p $HOME/meta-veos/recipes-veos/veos-sut/files mkdir -p $HOME/meta-veos/recipes-core/init-ifupdown/files/qemux86-64 mkdir -p $HOME/meta-veos/recipes-core/base-files mkdir -p $HOME/meta-veos/recipes-core/sysvinit mkdir -p $HOME/meta-veos/recipes-core/images # 完成後的目錄結構 meta-veos/ ├── conf/layer.conf ├── recipes-veos/veos-sut/ │ ├── veos-sut_1.0.bb # 受測應用 recipe │ └── files/{veos-sut.c, veos-sut.conf, veos-sut-init} └── recipes-core/ ├── init-ifupdown/ │ ├── init-ifupdown_%.bbappend │ └── files/qemux86-64/interfaces # 靜態 IP ├── base-files/base-files_%.bbappend # fstab 加 devtmpfs ├── sysvinit/ │ ├── sysvinit_%.bbappend # rcS VERBOSE=yes │ └── sysvinit-inittab_%.bbappend # 早期掛載 devtmpfs └── images/core-image-minimal.bbappend # 移除三支 init 腳本

conf/layer.conf 的內容:

BBPATH .= ":${LAYERDIR}" BBFILES += "${LAYERDIR}/recipes-*/*/*.bb \ ${LAYERDIR}/recipes-*/*/*.bbappend" BBFILE_COLLECTIONS += "veos" BBFILE_PATTERN_veos = "^${LAYERDIR}/" BBFILE_PRIORITY_veos = "10" # 高於 poky meta(5),確保 bbappend 的覆寫生效 LAYERDEPENDS_veos = "core" LAYERSERIES_COMPAT_veos = "scarthgap" # 必須包含目前的 LAYERSERIES_CORENAMES
驗證
# 檔案權限 chmod 755 $HOME/meta-veos/recipes-veos/veos-sut/files/veos-sut-init find $HOME/meta-veos -name '*.bb' -o -name '*.bbappend' -o -name 'layer.conf' | xargs chmod 644 # 換行字元:自 Windows 複製過來的檔案務必是 LF file $HOME/meta-veos/recipes-veos/veos-sut/files/veos-sut-init # 預期:不出現 "with CRLF line terminators" # 若出現:sed -i 's/\r$//' <檔案>

LAYERSERIES_COMPAT_veos 的值必須與 poky 版本代號一致,否則 bitbake 會直接拒絕解析此 layer。升級 poky 時務必同步更新此行。

STEP 3 / 8

註冊 layer

將 meta-veos 加入 bblayers.conf。

操作
# bblayers.conf 的 BBLAYERS 應成為: BBLAYERS ?= " \ $HOME/poky/meta \ $HOME/poky/meta-poky \ $HOME/poky/meta-yocto-bsp \ $HOME/meta-veos \ " # 亦可使用官方工具(會自行處理路徑與換行) cd $HOME/poky/build bitbake-layers add-layer $HOME/meta-veos
驗證
bitbake-layers show-layers # 預期輸出中包含(priority 必須是 10): layer path priority ========================================================================== meta /home/<user>/poky/meta 5 meta-poky /home/<user>/poky/meta-poky 5 meta-yocto-bsp /home/<user>/poky/meta-yocto-bsp 5 veos /home/<user>/meta-veos 10
STEP 4 / 8

local.conf:七個關鍵賦值

這個區塊是整份配置中影響最大的部分。每一行都對應一個實測到的具體失敗,並非預防性寫法。

操作
cat >> $HOME/poky/build/conf/local.conf <<'EOF' # >>> VEOS QEMU Coupling additions USE_DEVFS = "0" VIRTUAL-RUNTIME_dev_manager = "" EXTRA_IMAGE_FEATURES:append = " tools-debug" IMAGE_INSTALL:append = " veos-sut gdbserver iproute2 ethtool strace socat dropbear" IMAGE_ROOTFS_EXTRA_SPACE = "131072" IMAGE_FSTYPES = "ext4 tar.bz2" SERIAL_CONSOLES = "115200;ttyS0" EOF
設定 理由
USE_DEVFS = "0" 最關鍵的一項。改用 makedevs 依 IMAGE_DEVICE_TABLES(預設 meta/files/device_table-minimal.txt)產生靜態節點,其中包含 /dev/console(5:1) 與 /dev/null(1:3)。這是讓 sysvinit 能寫出 console 輸出的前提(見 §1.2)
VIRTUAL-RUNTIME_dev_manager = "" 移除 eudev,改用純 devtmpfs。V-ECU 的虛擬硬體組成固定,不需要動態裝置管理;此配置與已驗證可行的 Buildroot 組態一致。副作用:套件數自 131 降至 78
EXTRA_IMAGE_FEATURES:append = " tools-debug" 帶入 gdb/strace 等除錯工具。刻意不加 dbg-pkgs:實測會使 rootfs 自 40 MB 暴增至 1.27 GB,且遠端 GDB 所需的符號應置於 GDB host 的 sysroot 而非 target
IMAGE_INSTALL:append 受測應用與診斷工具。除 veos-sut 為自建 recipe 外,其餘六項 poky 本身即具備,不需要額外 layer
IMAGE_ROOTFS_EXTRA_SPACE = "131072" 額外 128 MB(單位為 KB)。最終 rootfs 約 242 MB
IMAGE_FSTYPES = "ext4 tar.bz2" ext4 為 dsQEMU 使用的磁碟映像;保留 tar.bz2 便於在不使用 debugfs 或 loop mount 的情況下檢視內容
SERIAL_CONSOLES = "115200;ttyS0" 原值含 ttyS1。VEOS 會加上 -nographic,guest 僅有一個 serial port,ttyS1 上的 getty 會不斷 respawn 直到被停用(INIT: Id "S1" respawning too fast),純粹浪費模擬指令
驗證
cd $HOME/poky/build bitbake -e core-image-minimal | grep -E '^(USE_DEVFS|IMAGE_FSTYPES|SERIAL_CONSOLES|IMAGE_ROOTFS_EXTRA_SPACE)=' bitbake -e core-image-minimal | grep -m1 '^VIRTUAL-RUNTIME_dev_manager=' # 預期:USE_DEVFS="0"、VIRTUAL-RUNTIME_dev_manager=""、其餘與上表一致 # bitbake -e 讀的是「最終展開值」,比直接看 local.conf 更可信
STEP 5 / 8

受測應用 recipe:veos-sut_1.0.bb

沒有受測應用,dsQEMU 就只是一台會開機的虛擬機。這支 recipe 負責編譯應用、安裝設定檔,並註冊為 sysvinit 服務。

操作 — recipes-veos/veos-sut/veos-sut_1.0.bb
SUMMARY = "VEOS QEMU Coupling system-under-test application" LICENSE = "CLOSED" SRC_URI = "file://veos-sut.c \ file://veos-sut.conf \ file://veos-sut-init \ " # poky 5.0 (scarthgap) 會把 file:// 來源直接解到 WORKDIR; # UNPACKDIR 自 5.1 才引入,此處寫錯會找不到來源檔。 S = "${WORKDIR}" inherit update-rc.d INITSCRIPT_NAME = "veos-sut" # start 90:必須排在 init-ifupdown(start 01)之後,eth0 要先有位址 INITSCRIPT_PARAMS = "start 90 2 3 4 5 . stop 20 0 1 6 ." # -g:讓 gdbserver 連線時可解析符號 do_compile() { ${CC} ${CFLAGS} ${LDFLAGS} -g -O2 -Wall -Wextra \ -o ${B}/veos-sut ${S}/veos-sut.c } do_install() { install -d ${D}/opt/sut install -m 0755 ${B}/veos-sut ${D}/opt/sut/veos-sut install -d ${D}${sysconfdir} install -m 0644 ${S}/veos-sut.conf ${D}${sysconfdir}/veos-sut.conf install -d ${D}${sysconfdir}/init.d install -m 0755 ${S}/veos-sut-init ${D}${sysconfdir}/init.d/veos-sut } FILES:${PN} += "/opt/sut" CONFFILES:${PN} = "${sysconfdir}/veos-sut.conf" # 設定檔內含本節點的靜態 IP,故套件為 machine-specific PACKAGE_ARCH = "${MACHINE_ARCH}"

對應的 files/veos-sut.conf(節點參數,與位址規劃一致):

# EthBackbone cluster 位址規劃 # 192.168.60.10 Buildroot POSIX-Based V-ECU # 192.168.60.20 Yocto POSIX-Based V-ECU ← 本節點 PEER_IP=192.168.60.10 PEER_PORT=30490 LOCAL_PORT=30490 PERIOD_MS=10 REPORT_EVERY=100

files/veos-sut-init 為 LSB 格式的 sysvinit 腳本,設計上有三個要點:

  • 輸出導向 /dev/console,且不使用 --background——後者會把輸出丟進 /dev/null,導致應用在執行卻看不到任何輸出。
  • 先確保 sysfs 已掛載,這是 VEOS pre-start 初始化機制的前提。
  • 可選功能以 kernel 命令列開關(例如 veos.gdb=1 啟用 gdbserver),使測試情境切換不需重建鏡像。
驗證
cd $HOME/poky/build bitbake veos-sut bitbake -e veos-sut | grep -E '^(S|PACKAGE_ARCH|INITSCRIPT_PARAMS)=' # 預期:S 指向 WORKDIR、PACKAGE_ARCH 為 qemux86-64、INITSCRIPT_PARAMS 含 start 90 # 若出現 do_fetch/do_unpack 找不到 veos-sut.c,通常即為 S 設定錯誤

修改 veos-sut.c 之後必須執行 bitbake -c cleansstate veos-sut。否則 sstate 快取會沿用舊的二進位檔並回報建置成功——這是一個不會產生任何錯誤訊息的陷阱,實測曾導致以兩個不同版本的應用進行比較。

STEP 6 / 8

五個 recipes-core 覆寫

這五個檔案處理網路位址、/dev、開機可觀測性,以及移除對 V-ECU 有害的 init 腳本。

(a) 靜態 IP — init-ifupdown_%.bbappend

VEOS 的 Ethernet cluster 內沒有 DHCP server,而 core-image-minimal 預設會以 busybox-udhcpc 嘗試 DHCP 並等到逾時。在 icount 模式下,這段等待是實際消耗的實時成本。

# init-ifupdown_%.bbappend FILESEXTRAPATHS:prepend := "${THISDIR}/files:" # files/qemux86-64/interfaces —— 放在 ${MACHINE} 子目錄,優先於通用版 auto lo iface lo inet loopback auto eth0 iface eth0 inet static address 192.168.60.20 netmask 255.255.255.0

(b) fstab 加入 devtmpfs — base-files_%.bbappend

do_install:append() { printf 'devtmpfs /dev devtmpfs mode=0755,nosuid 0 0\n' \ >> ${D}${sysconfdir}/fstab }

由 rcS 的 S03mountall.sh 掛載。此項單獨並不足夠——sysvinit 開啟 /dev/console 的時機比它更早,因此仍需 USE_DEVFS = "0" 作為主要修法;本項的作用是讓執行期的 /dev 是真正的 devtmpfs,而非靜態快照。

(c) 更早掛載 devtmpfs — sysvinit-inittab_%.bbappend

do_install:append() { sed -i '/^si::sysinit:/i \ # Mount devtmpfs before anything else - see meta-veos sysvinit-inittab bbappend.\ dv::sysinit:/bin/mount -t devtmpfs devtmpfs /dev' ${D}${sysconfdir}/inittab } # 產生的 inittab: dv::sysinit:/bin/mount -t devtmpfs devtmpfs /dev si::sysinit:/etc/init.d/rcS

(d) 開機可觀測性 — sysvinit_%.bbappend

do_install:append() { if ! grep -q '^VERBOSE=' ${D}${sysconfdir}/default/rcS; then echo 'VERBOSE=yes' >> ${D}${sysconfdir}/default/rcS else sed -i 's/^VERBOSE=.*/VERBOSE=yes/' ${D}${sysconfdir}/default/rcS fi }

在 veos-linux kernel 下,guest console 是唯一的診斷通道:沒有 shell、網路啟動前沒有 SSH、也無法附加到停滯的開機流程。VERBOSE=no(poky 預設)時,一支阻塞的腳本完全不留痕跡,log 的最後一行會屬於前一支腳本,使診斷指向錯誤的位置。

/etc/default/rcS 由 sysvinit 提供,不是 initscripts。寫成 initscripts_%.bbappend 會在建置時失敗(sed: can't read .../etc/default/rcS)。查證方式:find $HOME/poky/meta -name 'rcS*' -path '*default*'。

(e) 移除三支 init 腳本 — core-image-minimal.bbappend

veos_trim_initscripts() { rm -f ${IMAGE_ROOTFS}${sysconfdir}/rc*.d/[SK][0-9][0-9]bootlogd rm -f ${IMAGE_ROOTFS}${sysconfdir}/rc*.d/[SK][0-9][0-9]urandom rm -f ${IMAGE_ROOTFS}${sysconfdir}/rc*.d/[SK][0-9][0-9]hwclock.sh } ROOTFS_POSTPROCESS_COMMAND += "veos_trim_initscripts;"

三支腳本來自三個不同套件(sysvinit、initscripts、busybox),在 image 後處理一次移除比撰寫三個 bbappend 簡潔。各自的問題已列於 §1.3。

此段必須寫在 recipe 內,不能寫進 local.conf。local.conf 由 bitbake 的 ConfHandler 解析,只接受賦值,不能定義 shell function,寫錯會直接造成解析錯誤。

STEP 7 / 8

執行建置

由於 STEP 1(e) 已完成一次基準線建置,此處多數產物可自 sstate 快取重用,通常只需數分鐘至十餘分鐘;若跳過基準線而直接建置,則與首次建置同樣需要約 1–2 小時。

操作
cd $HOME/poky source oe-init-build-env build bitbake core-image-minimal # 若在 SSH 工作階段中長時間建置,建議使用 nohup 或 tmux 避免連線中斷 # nohup bitbake core-image-minimal > $HOME/yocto-build.log 2>&1 & # tail -f $HOME/yocto-build.log
驗證
D=$HOME/poky/build/tmp/deploy/images/qemux86-64 ls -laL $D/core-image-minimal-qemux86-64.rootfs.ext4 ls -laL $D/bzImage-qemux86-64.bin # 預期:rootfs.ext4 約 242 MB;bzImage 約 12 MB # 注意 deploy 目錄多為 symlink,使用 -L 追蹤實體檔
STEP 8 / 8

鏡像內容驗證(不需開機)

這是整份流程中投資報酬率最高的一個步驟。以 debugfs 在進入模擬環境之前確認鏡像內容,可將「配置沒進鏡像」與「模擬環境配置錯誤」兩類問題徹底分離。

操作+驗證
D=$HOME/poky/build/tmp/deploy/images/qemux86-64 R=$D/core-image-minimal-qemux86-64.rootfs.ext4 # ① 套件是否都進去了 grep -E '^(veos-sut|gdbserver|iproute2|ethtool|strace|socat|dropbear) ' \ $D/core-image-minimal-qemux86-64.rootfs.manifest # ② udev 是否確實移除(必須為 0) grep -c -E '^(eudev|udev) ' $D/core-image-minimal-qemux86-64.rootfs.manifest # ③ /dev 是否有靜態節點 ← 對 idle-detect 最關鍵 debugfs -R 'ls /dev' $R | tr -s ' ' '\n' | grep -E 'console|null|ttyS' # ④ 三支有害的 init 腳本是否已移除 debugfs -R 'ls /etc/rcS.d' $R | tr -s ' ' '\n' | grep -E '^S[0-9]' # ⑤ 開機訊息是否會輸出 debugfs -R 'cat /etc/default/rcS' $R | grep VERBOSE # ⑥ 網路是否為靜態位址 debugfs -R 'cat /etc/network/interfaces' $R # ⑦ 受測應用是否安裝到位 debugfs -R 'ls -l /opt/sut' $R
通過時的預期結果
① manifest 含 veos-sut / gdbserver / iproute2 / ethtool / strace / socat / dropbear ② eudev / udev 計數 = 0 ③ /dev 含 console、null、ttyS0、ttyS1 ④ /etc/rcS.d 應為: S02banner.sh S02sysfs.sh S03mountall.sh S05checkroot.sh S06devpts.sh S06modutils.sh S29read-only-rootfs-hook.sh S36bootmisc.sh S37populate-volatile.sh S38dmesg.sh S39hostname.sh ↑ 不應出現 S07bootlogd / S38urandom / S40hwclock.sh ⑤ VERBOSE=yes ⑥ 靜態位址 192.168.60.20 / 255.255.255.0 ⑦ /opt/sut/veos-sut 存在且可執行

4. 鏡像產出後:模擬環境端的配置

本節以 icount 模式為主軸——它不需要 KVM,是導入時的第一階段目標,也是日常回歸測試的主要模式。idle-detect 的差異部分另於 §4.6 說明。

在非互動式 SSH 工作階段中,veos-env.sh 不會生效。請一律使用絕對路徑,或於腳本開頭設定:V=/opt/dspace/veos2026a/bin/veos。本節指令均採此寫法。

STEP 1 / 6

部署鏡像並保留乾淨母版

guest 會以 rw 掛載 rootfs,每執行一次就會被寫入。因此必須保留一份 .golden 母版,並在每輪測試前還原,否則測試結果不可重現。

操作
# 在模擬主機上建立工作目錄 mkdir -p ~/dsqemu/yocto ~/dsqemu/osa ~/dsqemu/configs ~/dsqemu/log # 自 Build Server 取得鏡像(deploy 目錄為 symlink,務必加 -L) scp -L <build-user>@<build-server>:~/poky/build/tmp/deploy/images/qemux86-64/bzImage-qemux86-64.bin \ ~/dsqemu/yocto/bzImage scp -L <build-user>@<build-server>:~/poky/build/tmp/deploy/images/qemux86-64/core-image-minimal-qemux86-64.rootfs.ext4 \ ~/dsqemu/yocto/rootfs.ext4 # 建立乾淨母版 cp -f ~/dsqemu/yocto/rootfs.ext4 ~/dsqemu/yocto/rootfs.ext4.golden
驗證
# 兩端比對 SHA-256,確認傳輸完整 sha256sum ~/dsqemu/yocto/bzImage ~/dsqemu/yocto/rootfs.ext4 # 於 Build Server 端執行同樣的指令並比對雜湊值 # 預期:兩端逐字相同
STEP 2 / 6

撰寫 icount 模式的 JSON 配置

dsQEMU 的 V-ECU 由一份 JSON 描述,經 veos model import 轉為 OSA 內的 VPU。以下為完整可用的配置。

操作 — configs/yocto-x86_64-icount.json
{ "$schema": "file:///opt/dspace/veos2026a/tools/schemas/DsVeosQemu.schema.json", "Type": "DsVeosQemu", "Name": "YoctoQemux8664-icount", ← 名稱不可含空白 "StepSize": 0.001, ← 與 vECU base step、sim -e 對齊 "MetaData": { "Author": "<your-name>", "Description": "Yocto poky core-image-minimal qemux86-64 as POSIX-based V-ECU, icount mode." }, "Network": { "ETH": { "eth0": { "ClusterName": "EthBackbone", ← 相同名稱者自動接到同一 cluster "MacAddress": "9a:a2:36:d2:90:20", ← 務必自訂,勿留預設值 "BitsPerSecond": 1000000000, "Qemu": { "OptionKind": "nic", "Arguments": "model=virtio-net-pci,id=vnic0" } } } }, "Veos": { "SynchronizationMode": "icount", "RemotePort": 0 ← 0 = 本機執行 }, "Qemu": { "Architecture": "x86_64", "AllowOverridingDefaultQemuArgs": false, "Arguments": [ "-name YoctoQemux8664-icount", "-M q35,i8042=off", "-cpu IvyBridge", "-m 512", "-smp 1", "-kernel ${Resource:bzImage}", "-drive if=virtio,file=${Resource:rootfs.ext4},format=raw", "-append", "root=/dev/vda rootwait rw console=ttyS0,115200 tsc=reliable no_timer_check rcupdate.rcu_expedited=1 swiotlb=0 panic=-1" ], "RedirectStdout": "both", "RedirectStderr": "both" }, "Resources": [ "/home/<user>/dsqemu/yocto/bzImage", "/home/<user>/dsqemu/yocto/rootfs.ext4" ] }
欄位 說明
StepSize同步時間步(秒)。應與 vECU 的 base step size 及 veos sim config -e 三者一致,否則會出現封包遺失或額外抖動
ClusterName宣告相同名稱的多份 JSON,veos model import 會自動建立 cluster、channel 並完成接線,不需手動執行 model add 或 model connect
MacAddress預設值為全零,多節點時必然衝突。每個節點都應指定唯一位址
Resources僅接受絕對路徑。列出的檔案會於匯入時嵌入 OSA,並以 ${Resource:<basename>} 引用
console=ttyS0,115200VEOS 會加上 -nographic;保留 tty1 沒有意義且可能造成 getty 阻塞
RedirectStdout/Stderrboth 表示同時寫入 VEOS log 與檔案,產生 <Name>.qemu.stdout.log,即後續讀取 guest console 的來源
驗證
# 以原廠 schema 驗證(Windows 端或任何具 jsonschema 的環境) python -c "import json,jsonschema,sys; \ s=json.load(open(r'DsVeosQemu.schema.json')); \ d=json.load(open(r'yocto-x86_64-icount.json')); \ jsonschema.validate(d,s); print('schema OK')" # 建議另外加上四項專案自訂檢查: # ① Name 不含空白 ② 網卡為 virtio-net-pci # ③ MAC 非預設值  ④ idle-detect 未自訂受 VEOS 管控的參數
STEP 3 / 6

清場

每一輪測試都應自相同的初始狀態開始。這一步能避免大量無法重現的疑難排解。

操作
V=/opt/dspace/veos2026a/bin/veos $V sim down < /dev/null ; pkill -9 veos-qemu 2>/dev/null cp -f ~/dsqemu/yocto/rootfs.ext4.golden ~/dsqemu/yocto/rootfs.ext4
驗證
$V sim info < /dev/null 2>&1 | grep State # 預期:State: Unloaded pgrep -af 'veos-qemu-sys[t]em' || echo "no qemu" cmp -s ~/dsqemu/yocto/rootfs.ext4 ~/dsqemu/yocto/rootfs.ext4.golden && echo "rootfs == golden"

每一個 veos model 指令都必須附加 < /dev/null。Model Console 會讀取標準輸入以等待修改指令,在非互動環境下會一直阻塞至連線逾時——而且不會顯示任何提示。

STEP 4 / 6

匯入產生 OSA 並確認拓撲

將 JSON 轉為 OSA。若需要多節點聯測,重複匯入即可——相同 ClusterName 會自動接線。

操作
cd ~/dsqemu V=/opt/dspace/veos2026a/bin/veos # 建立新 OSA 並匯入 Yocto 節點(-n = 建立新檔) $V model import -n ./osa/yocto.osa -p ./configs/yocto-x86_64-icount.json \ -o ./osa/yocto.osa < /dev/null # (選用)加入第二個節點做雙節點聯測,不加 -n $V model import ./osa/yocto.osa -p ./configs/br-x86_64-icount.json \ -o ./osa/yocto.osa < /dev/null
驗證
$V model get ./osa/yocto.osa < /dev/null | head -40 # 或以 jq 直接取出關鍵欄位 $V model get ./osa/yocto.osa < /dev/null | jq '.Vpus[].Name, .BusClusters[].Name' # 預期: # VPU 名稱為 YoctoQemux8664-icount # BusClusters 含 EthBackbone # 每個 VPU 的 eth0 connector 之 ChannelReference 不可為 null

OSA 會在匯入時把 rootfs 內嵌進去。重新部署鏡像之後若沒有重建 OSA,執行的仍是舊 guest,而且不會有任何警告。凡是重新部署鏡像,就必須重新執行本步驟。

STEP 5 / 6

載入、執行與觀察

sim load 需要解開內嵌的 rootfs,對於 242 MB 的映像通常需要數十秒,屬正常現象。

操作
$V sim config --enable-sim-log --enable-bus-log -e 0.001 < /dev/null $V sim load ./osa/yocto.osa < /dev/null $V sim info < /dev/null # 確認 State 與 components $V sim start 60s < /dev/null # 執行 60 秒模擬時間 # 讀取 guest console $V sim show-log < /dev/null # 列出所有 log 檔 $V sim show-log YoctoQemux8664-icount.qemu.stdout.log < /dev/null | tail -30
通過時的 console 樣貌
EXT4-fs (vda): mounted filesystem ... ordered data mode INIT: version 3.04 booting Starting udev ← 已改為純 devtmpfs,此行不再阻塞 Configuring network interfaces... done. INIT: Entering runlevel: 5 Starting veos-sut: OK veos-sut: start local=30490 peer=192.168.60.10:30490 period=10ms veos-sut: t=1000ms tx=100 rx=98 veos-sut: t=2000ms tx=200 rx=198
驗證
$V sim info < /dev/null | grep -E 'SimulationTime|ElapsedTime' # 加速比 = SimulationTime / ElapsedTime,icount 模式應 ≥ 0.9 # 例:SimulationTime 60.000 / ElapsedTime 61.006 → 0.984
STEP 6 / 6

收工

釋放授權席次並還原鏡像,讓環境回到可重複的初始狀態。

操作
$V sim pause < /dev/null $V sim down < /dev/null cp -f ~/dsqemu/yocto/rootfs.ext4.golden ~/dsqemu/yocto/rootfs.ext4 $V sim config --disable-sim-log --disable-bus-log < /dev/null
驗證
$V sim info < /dev/null | grep State # 預期:State: Unloaded

未執行 sim down 時,授權席次會持續被占用。下一輪載入會出現 Error 212 / user maximum ... reached——這是席次未釋放,並非授權伺服器故障。

4.6 idle-detect 模式的配置差異

模擬環境端的流程完全相同,差異僅在 JSON。核心原則是:凡由 VEOS 管控的參數一律不要自行指定。

參數 在 idle-detect 下的處理
-kernel/-cpu/-smp/-enable-kvm/-rtc/-veos一律不可自行指定。VEOS 會自行加入;自訂會被拒絕。bzImage 也不需要列入 Resources
-serial stdio必須自行加入。VEOS 在此模式追加 -nodefaults,取消了預設 serial 裝置;缺少此項時 console 完全空白
virtio-rng建議加入,用以解決 §1.3 的熵源問題
kernel 命令列VEOS 會自行追加 hpet=disable tsc=reliable nohz=off veos.clock_tick_ns=<StepSize>,不需重複設定
操作 — configs/yocto-x86_64-idle-detect.json(差異段)
"Veos": { "SynchronizationMode": "idle-detect", "RemotePort": 0 }, "Qemu": { "Architecture": "x86_64", "AllowOverridingDefaultQemuArgs": false, "Arguments": [ "-name YoctoQemux8664-idledetect", "-M q35,i8042=off", "-m 512", // 無 -cpu、無 -smp、無 -kernel —— 由 VEOS 管控 "-drive if=virtio,file=${Resource:rootfs.ext4},format=raw", "-object rng-random,filename=/dev/urandom,id=rng0", "-device virtio-rng-pci,rng=rng0", "-serial stdio", "-append", "root=/dev/vda rootwait rw console=ttyS0,115200 panic=-1" ] }, "Resources": [ "/home/<user>/dsqemu/yocto/rootfs.ext4" ← 只需 rootfs ]

關於 rng-random 的取捨:它提供的是主機的隨機性,嚴格而言會使每次執行無法逐位元重現。若測試需要位元級可重現性,可改用 rng-builtin(QEMU 內建 PRNG),或維持「移除所有熵消費者」的做法(此時 dropbear 亦須移除)。

驗證(執行前的前置條件)
ls -l /dev/kvm # 必須存在 id | grep -o kvm # 使用者需具備存取權限 grep -c -E ' vmx| svm' /proc/cpuinfo # 於虛擬機內執行時,須先啟用巢狀虛擬化 # 若模擬主機本身是 VMware 虛擬機,須勾選 "Virtualize Intel VT-x/EPT", # 並執行完整的關機→開機循環(guest 內 reboot 不會使該設定生效)。

5. 驗證判準

本專案的判準遵循一項原則:判準必須在量測之前訂定,且必須是可由輸出直接判讀的具體條件,而非主觀印象。

5.1 icount 模式

# 判準 怎麼看
1rootfs 正確掛載console 出現 EXT4-fs (vda): mounted filesystem
2userspace 確實啟動出現 INIT: version 3.04 booting 與 Entering runlevel: 5。只有 kernel 訊息而無 INIT 行,即為 §1.2 的 /dev/console 問題
3受測應用啟動出現 veos-sut: start ...
4應用持續發送tx 隨 t= 單調遞增,且比例符合設定週期(10 ms → 每秒 100 筆)
5雙向通訊成立(多節點時)rx 追平 tx,允許因兩節點開機時間差造成的固定落差
6時間同步品質加速比(SimulationTime / ElapsedTime)≥ 0.9

5.2 idle-detect 模式:額外的判準

# 這五行是 idle-detect 專屬的通過條件 Linux version 6.6.119-…-veos26.1.0 ← VEOS kernel 確實被使用 Entering VEOS simulator mode ← veos-clock 已啟動 (不應出現 "can't open /dev/null") ← /dev 已正確填充 veos-sut: start … ← 受測應用已啟動 veos-sut: t=… tx=… ← 應用持續運作 # 另外兩項量化判準: # 加速比 ≈ 1.000 # 多節點時兩邊的 t= 完全相同(lockstep),封包遺失為 0

5.3 沒有 KVM 也能先驗證 idle-detect 相容性

這是一個實務價值很高的技巧。idle-detect 對 rootfs 的唯一影響,是 VEOS 將 guest kernel 換成 veos-linux 並改由 initramfs 進入;而 icount 模式允許指定任意 kernel。

因此,只要在 icount 模式下以 veos-linux kernel 開機自家 rootfs,測到的正是 idle-detect 的鏡像條件。這能在尚未具備 KVM 的環境中,先把「鏡像相容性」與「主機能力」兩件事分離,避免把主機設定問題誤判為鏡像問題。

操作
# VEOS 自帶的 kernel 位置 ls /opt/dspace/veos2026a/simulator/extensions/veos-linux/images/x86/ # 複製一份 icount JSON,把 -kernel 指向 veos-linux 的 bzImage, # 並補上 virtio-rng 兩行,其餘不變,然後照 §4 STEP 4–5 執行。
驗證
# 判準即為 §5.2 的五行。全部命中,即代表鏡像已具備 idle-detect 相容性, # 屆時只要主機取得 /dev/kvm 即可直接切換模式。

6. 失敗模式與判讀

Yocto 在 VEOS 環境下的失敗,絕大多數不會以錯誤訊息呈現,而是表現為「停在某一行不動」或「輸出憑空消失」。以下對照表可將症狀直接對應到成因。

觀察到的現象 成因與處理
kernel 訊息完整,但一行 INIT: 都沒有 鏡像內 /dev/console 不存在。檢查 USE_DEVFS 是否為 "0",並以 STEP 8 的 ③ 直接驗證鏡像
停在 udevd: starting version ... VIRTUAL-RUNTIME_dev_manager 未清空。判斷是「慢」還是「死結」的方法見下方
停在 Initializing random number generator... 或 Waiting for kernel randomness... 熵源問題。於 JSON 補上 virtio-rng 裝置(治本),或確認 S38urandom 已被移除
checkroot 之後完全沒有輸出 bootlogd 接管了 console。確認 image bbappend 的移除動作已生效(STEP 8 的 ④)
INIT: Id "S1" respawning too fast: disabled for 5 minutes SERIAL_CONSOLES 仍包含 ttyS1。不致命,但會白白消耗模擬指令
應用回報 tx 持續增加,但對端 rx 為 0 依序檢查:兩份 JSON 的 ClusterName 是否一致;MacAddress 是否重複;網卡型號是否為 virtio-net-pci;veos-sut.conf 的 PEER_IP 是否指向對端實際位址
明明改了設定,行為卻沒變 三個常見原因:① 修改 veos-sut.c 後未執行 cleansstate;② 重新部署鏡像後未重建 OSA;③ 未自 .golden 還原 rootfs。三者都不會產生錯誤訊息

6.1 如何區分「慢」與「死結」

在 icount 模式下,guest 執行速度本來就比實時慢,因此「停住」與「很慢」在觀感上難以區分。可靠的判斷依據是模擬時間的推進速率,而非牆鐘時間:

$V sim info < /dev/null | grep -E 'SimulationTime|ElapsedTime' # 間隔 30 秒再讀一次,比較兩次的 SimulationTime # 有推進(即使緩慢)→ 是慢,可加長執行時間或提高主機資源 # 幾乎不推進 → 是死結,應對照 §6 的表格逐項排查 # 實測參考:udevd 死結時,300 秒實時只推進 2.55 秒模擬時間

7. 回歸測試與工程化建議

01

組態即程式碼

layer、recipe、bbappend、JSON 與受測應用原始碼全部納入版本控管,並保留 local.conf/bblayers.conf 的原始備份。任何一項客製都應能說明它對應哪一個具體失敗。

02

先驗鏡像,再進模擬

以 debugfs 在開機前確認鏡像內容(STEP 8)。這能把「配置沒進鏡像」與「模擬環境配置錯誤」兩類問題徹底分離,省下大量診斷時間。

03

用 JSON 換行為

把 gdbserver 開關、對端位址、應用選項做成讀取 kernel 命令列的參數。切換測試情境只需改 JSON,不需重建鏡像——單次迭代由數十分鐘降到數秒。

每次變更鏡像設定或 JSON 之後,建議至少執行以下三項回歸:

  • 配置檔驗證以原廠 schema 檢查所有 JSON,並加上專案自訂規則(名稱不含空白、網卡型號、MAC 非預設值、idle-detect 未自訂受管參數)。
  • 雙節點 icount 聯測Yocto 與另一個 guest 掛入同一 Ethernet cluster,確認雙向通訊與封包計數關係成立——這一項同時涵蓋開機、應用、網路與拓撲四個面向。
  • rootfs × veos-linux kernel 相容性探測即 §5.3 的做法。它讓「鏡像是否具備 idle-detect 相容性」成為一項可在 CI 中日常執行的檢查,而非只能在具備 KVM 的機器上驗證。

結語

把 Yocto 鏡像帶進 VEOS QEMU Coupling,工作量並不在於「改很多設定」,而在於理解每個設定對應哪一個具體失敗。本文的配置清單並不長——local.conf 的七行、一個獨立 layer、六份 recipe 與 bbappend、兩份 JSON——但每一項都源自一個實測到的症狀。

與 Buildroot 相比,Yocto 的差異集中在發行版預設值的假設:USE_DEVFS 假設 devtmpfs 會自動掛載、eudev 假設可以自由地與 kernel 往返協商、bootlogd 假設 console 可以被獨占。這些假設在一般硬體或原生 QEMU 上都成立,在 VEOS 的模擬時間軸上則不然。而它們失效時不會拋出錯誤,只會安靜地停下來。

這也是為什麼本文堅持每個步驟都附上驗證方法:在虛擬 ECU 的整合環境裡,「沒有報錯」從來不等於「配置正確」。建立一套可重複執行、判準事前訂好的驗證流程,是讓 SIL 測試結果可信的前提。

需要導入協助?

捷策動能提供 dSPACE VEOS/SystemDesk 虛擬驗證平台的導入規劃、環境建置、鏡像客製與 SIL 測試流程設計服務,涵蓋 Yocto/Buildroot 客製 Linux、Classic AUTOSAR vECU、Restbus 模擬與 CI 整合。歡迎與我們的技術團隊討論您的專案需求。

本文技術內容基於 dSPACE VEOS 2026-A 實機環境驗證,所引用的行為與數值均為實測結果。相關原廠規範請參閱 dSPACE VEOS Manual「Basics on VEOS QEMU Coupling」與「How to Debug a POSIX-Based V-ECU Remotely with GDB」等章節;Yocto 相關設定請對照 Yocto Project Reference Manual 之 USE_DEVFS、VIRTUAL-RUNTIME_dev_manager、IMAGE_FSTYPES 等變數說明。文中路徑、位址與名稱請依實際環境調整。