最近鴻蒙pc終于上線了“融合開發(fā)引擎”開發(fā)工具,可以在命令行終端里執(zhí)行大部分的linux命令了(openEuler 24.03 LTS-SP1 ),系統(tǒng)可用性大幅提升。但是在日常使用中,發(fā)現(xiàn)這個linux環(huán)境還是殘缺的,相較于完整虛擬機(jī)很多功能都無法使用,例如docker無法運(yùn)行,系統(tǒng)沒有systemd進(jìn)程等等。經(jīng)過對其運(yùn)行環(huán)境的分析,發(fā)現(xiàn)其既不是傳統(tǒng)意義上的虛擬機(jī),也不是標(biāo)準(zhǔn)的容器,這引起了我的興趣,現(xiàn)將分析結(jié)果記錄如下。
總體架構(gòu)
要理解這個融合開發(fā)引擎,核心是要理解stratovirt的架構(gòu),參考官方介紹文檔:https://www.openeuler.org/zh/blog/wangzhigang/2020-09-25-stratovirt-intro.html

環(huán)境檢測
進(jìn)入融合開發(fā)引擎創(chuàng)建的 Linux 環(huán)境,搜集以下信息:
層1 - 輕量虛擬機(jī):
- 設(shè)備樹顯示 linux,dummy-virt,這是 virt machine type 的標(biāo)志
- 中斷控制器是 GICv3(ARM 虛擬機(jī)標(biāo)配)
- 所有存儲設(shè)備都是 virtio(vda/vdb/vdc/vdd),沒有真實(shí)物理磁盤
- 網(wǎng)卡 MAC 地址是 52:54:00:xx:xx:xx,這是 輕量虛擬機(jī)默認(rèn) MAC 前綴
- BogoMIPS 只有 2000,典型虛擬 CPU 特征
層2 - OzoneC 容器:
- 根文件系統(tǒng)是 overlay,掛載路徑明顯看到 OzoneC(華為自研容器運(yùn)行時)
- /mnt/linux_share 通過 virtiofs 掛載(VM 和宿主機(jī)共享文件系統(tǒng))
- sysfs 是只讀掛載(ro),容器內(nèi)無法修改內(nèi)核參數(shù)
- 大量 /proc/sys/ 路徑被 tmpfs 覆蓋(容器隔離手段)
整體環(huán)境架構(gòu):
物理宿主機(jī)(鴻蒙PC aarch64,HarmonyOS)
└── StratoVirt 輕量虛擬機(jī)(aarch64,Guest Linux Kernel 6.6.0)
└── OzoneC 容器(openEuler 24.03 LTS-SP1 鏡像)
源碼分析
源碼基于官方代碼倉庫:https://atomgit.com/openeuler/stratovirt
將其中代碼結(jié)合本地實(shí)際運(yùn)行環(huán)境進(jìn)行分析:
1. 設(shè)備樹 compatible 字符串
當(dāng)前環(huán)境:linux,dummy-virt
StratoVirt 源碼 fdt.rs:
fdt.set_property_string("compatible", "linux,dummy-virt")
這個字符串是 StratoVirt aarch64 標(biāo)準(zhǔn)/輕量機(jī)型共用的 FDT 根節(jié)點(diǎn)標(biāo)識,不是 QEMU 獨(dú)有的。
2. 網(wǎng)卡 MAC 地址完全匹配
當(dāng)前環(huán)境 eth0:52:54:00:12:34:56
當(dāng)前環(huán)境 eth1:52:54:00:12:34:57
StratoVirt 源碼 virtio/src/device/net.rs:
let mac = "52:54:00:12:34:56"; ← eth0
let mac = "52:54:00:12:34:57"; ← eth1(測試用默認(rèn)值)
這是 StratoVirt 硬編碼的默認(rèn) MAC,直接對上了。
3. virtio-mmio 而非 virtio-pci
當(dāng)前環(huán)境中斷:virtio0 ~ virtio10(全部走 MMIO 總線)
lsblk:vda/vdb/vdc/vdd(virtio-blk-device 命名風(fēng)格)
StratoVirt microvm 用 virtio-mmio,標(biāo)準(zhǔn)機(jī)型(virt)才用 virtio-pci。當(dāng)前是 virtio-mmio,確認(rèn)是輕量模式。
4. GICv3 + PL031 RTC 匹配 microvm 內(nèi)存布局
StratoVirt aarch64/micro.rs 定義:
GicDist @ 0x0800_0000
GicCpu @ 0x0801_0000
GicRedist @ 0x080A_0000
Rtc(PL031) @ 0x0901_0000
當(dāng)前 /proc/interrupts:
GICv3 70 Level rtc-pl031 ← 完全對應(yīng)
5. seccomp 白名單(<55個syscall)
microvm 模式下 StratoVirt 用 seccomp 嚴(yán)格限制自身的 syscall,運(yùn)行時只允許約 55 個系統(tǒng)調(diào)用,這是其安全性的核心設(shè)計。
microvm vs 標(biāo)準(zhǔn) virt 機(jī)型的本質(zhì)區(qū)別(源碼層面)
特性 microvm(輕量) standard virt(標(biāo)準(zhǔn))
設(shè)備總線 virtio-mmio virtio-pci + PCIe
啟動固件 簡化 BootLoader UEFI (edk2)
ACPI 無 有
熱插拔 不支持 支持
設(shè)備枚舉 靜態(tài),啟動時確定 動態(tài),ACPI 驅(qū)動
啟動時間 ~50ms 慢(固件初始化)
內(nèi)存開銷 ~4MB 更高
攻擊面 最小化 較大
適用場景 容器/Serverless 傳統(tǒng)虛機(jī)替代
啟動流程
融合開發(fā)引擎 app(harmonyos app)
↓ 調(diào)用OzoneC 接口創(chuàng)建虛擬機(jī)
OzoneC(Rust)宿主機(jī)側(cè)
│ 讀取 config.json 發(fā)現(xiàn)有 vm 字段
↓ 啟動 StratoVirt microvm
StratoVirt(Rust)→ KVM → aarch64 VM
↓ VM 內(nèi)核啟動,執(zhí)行 hsl_init
OzoneC(Rust)VM 內(nèi)側(cè)
│ pivot_root → overlay rootfs
│ namespace 隔離
│ seccomp + capabilities
↓
openEuler 24.03 容器環(huán)境
↓
Linux App 進(jìn)程
這里面比較有意思的點(diǎn)是OzoneC分別在宿主機(jī)和虛擬機(jī)內(nèi)承擔(dān)了不同的角色,一共調(diào)用了兩次。
OzoneC介紹
OzoneC到底是什么?
從 Cargo.toml 定義一目了然:
description = "An OCI runtime implemented by Rust"
OzoneC 是一個 OCI 標(biāo)準(zhǔn)容器運(yùn)行時,類比 runc / crun,但是用 Rust 寫的,專門為 StratoVirt VM 內(nèi)部設(shè)計。
它實(shí)現(xiàn)了 OCI Runtime Spec 標(biāo)準(zhǔn)接口:
ozonec create → 創(chuàng)建容器
ozonec start → 啟動容器
ozonec state → 查詢狀態(tài)
ozonec kill → 發(fā)送信號
ozonec delete → 刪除容器
ozonec exec → 在容器內(nèi)執(zhí)行命令
StratoVirt 和 OzoneC 各自負(fù)責(zé)什么
這是問題的核心。兩者解決的是完全不同層面的問題:
StratoVirt microvm 負(fù)責(zé):
- KVM 硬件虛擬化隔離(VM 邊界)
- 虛擬硬件(virtio 設(shè)備、GICv3)
- 提供一個完整的 Linux 內(nèi)核運(yùn)行環(huán)境
- 把物理機(jī)上的多個 VM 彼此隔離
OzoneC 負(fù)責(zé)(在 VM 內(nèi)部):
- OCI 標(biāo)準(zhǔn)接口(讓 iSulad/Docker 能調(diào)用它)
- 每個容器的 Linux namespace 隔離
- 每個容器的文件系統(tǒng)(overlay rootfs + pivot_root)
- 每個容器的 seccomp syscall 白名單
- 每個容器的 capability 降權(quán)
- 每個容器的 cgroup 資源配額
- 每個容器的 sysctl 參數(shù)
StratoVirt 創(chuàng)造了一個"干凈的 Linux 環(huán)境",但它完全不知道這個環(huán)境里跑著什么應(yīng)用,也不管應(yīng)用之間的隔離。OzoneC 在這個環(huán)境里負(fù)責(zé)應(yīng)用級別的精細(xì)管理。
到這里其實(shí)可以看出來了,OzoneC 的設(shè)計初衷就是安全容器場景(和 katacontainer 非常相似,只是用了rust 編寫),業(yè)界既想用到vm的隔離性,也想用到容器的輕量性,搞出來的一個中間方案,這也很好理解,StratoVirt 本身就是華為云團(tuán)隊設(shè)計出來用于云計算場景做多租戶隔離的,設(shè)計完全是數(shù)據(jù)中心導(dǎo)向的,而并非用于個人電腦。
業(yè)界做安全容器的方案也不少,這邊做一個比較:
方案 隔離手段 內(nèi)核來源 語言
─────────────────────────────────────────────────────────
Kata Containers 輕量 VM(QEMU/FC) Guest Linux 內(nèi)核 Go
gVisor 用戶態(tài)內(nèi)核攔截 自實(shí)現(xiàn) syscall Go
Firecracker microVM Guest Linux 內(nèi)核 Rust
OzoneC microVM(StratoVirt) Guest Linux 內(nèi)核 Rust
OzoneC + StratoVirt 和 Firecracker 是最接近的對標(biāo),都是 Rust 寫的 microVM 方案,但有幾個關(guān)鍵設(shè)計差異。
OzoneC 的獨(dú)特設(shè)計:同一個二進(jìn)制,兩個角色
這是 OzoneC 最與眾不同的地方。
Kata Containers 需要兩個獨(dú)立組件:
宿主機(jī)側(cè):kata-shim(Go) ← 接收 OCI 調(diào)用,管理 VM 生命周期
VM 內(nèi)側(cè):kata-agent(Go) ← 在 VM 內(nèi)執(zhí)行容器操作
兩個進(jìn)程,兩套代碼,gRPC 通信
OzoneC 是同一個 Rust 二進(jìn)制,根據(jù)上下文切換角色:
// oci_spec/src/runtime.rs
pub struct RuntimeConfig {
pub vm: Option<VmPlatform>, // 有這個字段 → 宿主機(jī)角色:啟動 VM
pub linux: Option<LinuxPlatform>, // 有這個字段 → VM內(nèi)角色:建容器
}
宿主機(jī)側(cè):OzoneC 讀到 vm 字段 → 調(diào) StratoVirt 啟動 microvm
VM 內(nèi)側(cè):OzoneC(hsl_init 觸發(fā))讀到 linux 字段 → 建 namespace/rootfs
同一個二進(jìn)制,6127 行 Rust,替換掉 kata-shim + kata-agent 兩個 Go 組件
兩階段容器初始化(源碼里最精密的部分)
從 linux/container.rs 的 create() 函數(shù):
主進(jìn)程(OzoneC 宿主)
│
├─ fork → Stage 1 進(jìn)程 [ozonec:1:CHILD]
│ ├── set_user_namespace() 用戶命名空間
│ ├── set_pid_namespace() PID 命名空間
│ └─ fork → Stage 2 進(jìn)程 [ozonec:2:INIT]
│ ├── set_rest_namespaces() 其余 ns
│ ├── rootfs.prepare_rootfs() overlay 掛載
│ ├── pivot_root() 切換根文件系統(tǒng)
│ ├── set_seccomp() syscall 過濾
│ ├── drop_capabilities() 降權(quán)
│ └── exec_program() 執(zhí)行應(yīng)用
│
└─ 通過 Channel<Message> 同步各階段狀態(tài)
為什么要兩階段?User namespace 的 uid/gid mapping 必須由父進(jìn)程寫入(/proc/pid/uid_map),但寫入時子進(jìn)程必須已經(jīng)進(jìn)入新 namespace 且處于 dumpable 狀態(tài)。兩階段設(shè)計用 Unix pipe 做同步,精確控制這個時序,避免競爭條件。
安全縱深:四層疊加
層次 機(jī)制 在哪里實(shí)現(xiàn)
──────────────────────────────────────────────────────────
第一層 KVM 硬件虛擬化隔離 StratoVirt + CPU VT/Kunpeng-V
第二層 StratoVirt 進(jìn)程 seccomp micro_common/syscall.rs
(VM 進(jìn)程本身限制 <55 syscall)
第三層 Linux namespace 隔離 OzoneC linux/namespace.rs
(容器內(nèi) pid/mnt/uts/net/ipc)
第四層 容器級 seccomp OzoneC linux/seccomp.rs
(per-container syscall 白名單)
和 Kata Containers 的對比
維度 Kata Containers OzoneC + StratoVirt
────────────────────────────────────────────────────────────────
VMM 后端 QEMU / Firecracker 可選 只有 StratoVirt
宿主側(cè)組件 kata-shim(Go) OzoneC(Rust)
VM 內(nèi) agent kata-agent(Go) OzoneC(Rust)
通信協(xié)議 TTRpc over vsock OCI spec 直接實(shí)現(xiàn)
語言 Go(運(yùn)行時開銷,GC) Rust(無 GC,內(nèi)存安全)
代碼規(guī)模 shim+agent 合計 ~8萬行 OzoneC 6127 行
外部依賴 OpenStack 基金會項目 華為完全自主
Kata 最大的問題是架構(gòu)復(fù)雜度——shim、agent、TTRpc、各種 VMM 后端,組件多、版本管理復(fù)雜、Go 的 GC 在低延遲場景有抖動。OzoneC 用 6000 行 Rust 做了 Kata 兩個核心組件的事,是典型的"少即是多"策略。
結(jié)論
經(jīng)過以上分析,現(xiàn)在基本可以下一個結(jié)論了,即這個所謂的“融合開發(fā)引擎”就是使用 OzoneC 工具創(chuàng)建的一個“安全容器”。 具體來說,stratovirt 虛擬化工具創(chuàng)建了一個輕量化虛擬機(jī)(microvm,帶完整的 guest kernel),虛擬機(jī)里面使用 hsl_init 拉起了一套 OzoneC 管理的容器環(huán)境。
之前介紹過,這套安全容器架構(gòu)本身是華為云計算 openEuler 團(tuán)隊用于云計算場景下的多租戶隔離,這個本身沒有任何問題,工程方面的實(shí)現(xiàn)也非常優(yōu)秀。問題在于現(xiàn)在把這套技術(shù)用于鴻蒙pc,用于模擬 Linux 開發(fā)環(huán)境就產(chǎn)生了一種非常擰巴的效果:guest kernel 理論上是完整的,但是由于ozone這個安全沙盒的存在,導(dǎo)致很多內(nèi)核能力不可用(iptables、ipvs、cgroups等等),也導(dǎo)致了沒有systemd(1號進(jìn)程是hsl_init),同時也無法在容器內(nèi)再去掛載overlay,因?yàn)樗约罕旧砭鸵呀?jīng)是overlay了,屬于套娃了,導(dǎo)致docker也不可用。
華為使用這套基于云計算場景的架構(gòu)做pc 端 Linux 環(huán)境模擬的初衷目前暫未可知,對于融合開發(fā)引擎這個場景,完全可以用最簡單的方式:
stratovirt \
-machine microvm \
-kernel openeuler-6.6.bin \
-append "root=/dev/vda init=/sbin/init console=ttyAMA0" \
-drive file=linux-gnu-rootfs.ext4,id=rootfs \
-device virtio-blk-device,drive=rootfs \
...
VM 啟動,systemd 拉起,用戶得到一個完整的 Linux 環(huán)境,root 權(quán)限,Docker 正常工作,和使用真實(shí) Linux 機(jī)器沒有區(qū)別。OzoneC 這一層是云平臺加的多租戶隔離機(jī)制,在單用戶的桌面場景里沒有存在的必要。個人認(rèn)為微軟的 WSL2 是一個比較好的參考實(shí)現(xiàn),同樣也是使用了輕量化虛擬機(jī) Hyper-V vm,但其內(nèi)核能力是完整的,沒有安全沙盒這一層,所以安裝docker,systemd這些都完全沒有問題,希望后續(xù)融合開發(fā)引擎也往這個方向發(fā)展。