Minos 虚拟化: 从零复现 Mino Hypervisor

本文是基于 WSL2+Docker 从零复现 Minos Hypervisor 的过程记录。

1. 总体流程

1
2
3
4
5
6
7
8
9
10
11
WSL2 (Ubuntu 26.04, x86_64)
└─ Docker 容器 (ubuntu:26.04)
├─ git clone 原始 minos 源码(未打补丁)
├─ 打补丁 1(Makefile / GCC15 兼容)
├─ 编译 minos → minos.bin
├─ 编译 u-boot → u-boot.bin
├─ 编译 Linux 4.19.238 → Image(集成 minos 驱动)
└─ 打包 ramdisk(官方 qemu-virt.dtb + Image)
└─ 宿主机
├─ 改造官方 virtio-sd.img(写入 boot 分区)
└─ QEMU 启动 → guest shell

产物目录:容器 /home/minos/work = 宿主机 ~/minos-work(挂载共享)


2. 安装 Docker

2.1 安装 Docker Engine

1
curl -fsSL https://get.docker.com | sh

注意:WSL 环境下脚本会打印提示:

1
2
3
WSL DETECTED: We recommend using Docker Desktop for Windows.
You may press Ctrl+C now to abort this script.
+ sleep 20

这是正常提示,不是错误。不要按 Ctrl+C,脚本 sleep 20 秒后仍会继续安装 Docker Engine。

中间可能出现的无害输出

1
sudo: preserving the entire environment is not supported, '-E' is ignored

这只是 sudo 忽略 -E 参数的 warning,不影响安装。

2.2 验证安装

1
2
docker --version
# Docker version 29.7.2, build a7dcaa6

2.3 加入 docker 组

1
sudo usermod -aG docker $USER

必须让组生效:关掉当前终端,重新开一个新终端(最可靠),或 newgrp docker
验证:groups 输出里应看到 docker


3. 配置镜像加速器

3.1 背景

第一次 docker build / docker pull 报错:

1
2
failed to do request: Head "https://registry-1.docker.io/v2/library/ubuntu/manifests/26.04":
dial tcp 173.252.108.3:443: i/o timeout

原因:Docker Hub(registry-1.docker.io)在本地网络不可达。

3.2 排查各镜像源可达性

1
2
3
4
timeout 10 curl -sI https://registry-1.docker.io/v2/ | head -1      # 超时(不可达)
timeout 10 curl -sI https://docker.mirrors.ustc.edu.cn/v2/ | head -1 # 超时
timeout 10 curl -sI https://registry.cn-hangzhou.aliyuncs.com | head -1 # HTTP/2 200
timeout 10 curl -sI https://docker.m.daocloud.io/v2/ | head -1 # HTTP/2 401(可达)

结果

结果 判断
Docker Hub 超时
中科大 超时
阿里云 200 ⚠️ 可达但它是 ACR 端点,不一定代理 docker.io
DaoCloud HTTP/2 401 可达(401 是 registry 未带 token 访问 /v2/ 的正常响应)

3.3 配置 daemon.json

1
sudo vim /etc/docker/daemon.json

写入:

1
2
3
4
5
{
"registry-mirrors": [
"https://docker.m.daocloud.io"
]
}

保存退出:vim 里 i 编辑 → 粘贴 → Esc:wq 回车。

3.4 重启 + 验证

1
2
3
sudo systemctl restart docker
docker pull hello-world
# Status: Downloaded newer image for hello-world:latest

4. 创建 Dockerfile 并 build 容器

4.1 项目结构

1
2
3
4
5
6
7
~/workspace/docker-minos/
├── Dockerfile
├── build.sh # 分步构建辅助脚本
├── qemu-virt-minimal.dts # 精简 host dtb(备用,官方 dtb 不可用时)
├── boot.cmd # U-Boot 启动脚本源(备用)
├── patches/ # 补丁文件
└── minos/ # (早期 COPY 用的源码,后改为容器内 clone)

4.2 Dockerfile 最终内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
FROM ubuntu:26.04

ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update && apt-get install -y --no-install-recommends \
gcc-aarch64-linux-gnu \
device-tree-compiler \
bison flex libssl-dev swig python3-dev \
python3-setuptools python3-pyelftools \
git wget curl ca-certificates xz-utils file patch vim sudo \
&& rm -rf /var/lib/apt/lists/*

# 创建 minos 用户(uid=1000 与宿主机对齐)
# ubuntu:26.04 自带 uid=1000 的 ubuntu 用户,先删除避免冲突
RUN userdel -r ubuntu \
&& groupadd -g 1000 minos \
&& useradd -m -s /bin/bash -u 1000 -g 1000 minos

COPY --chown=minos:minos build.sh /home/minos/work/build.sh
COPY --chown=minos:minos qemu-virt-minimal.dts /home/minos/work/qemu-virt-minimal.dts
COPY --chown=minos:minos boot.cmd /home/minos/work/boot.cmd

RUN chmod +x /home/minos/work/build.sh

VOLUME ["/home/minos/output"]

USER minos
WORKDIR /home/minos/work

4.3 Dockerfile 的踩坑修复史

坑 1:UID 1000 is not unique

1
useradd: UID 1000 is not unique

ubuntu:26.04 基础镜像自带 ubuntu 用户(uid=1000)。需先删它:

1
RUN userdel -r ubuntu && useradd -m -s /bin/bash -u 1000 -g 1000 minos

userdel -r ubuntu 会连主组一起删 → 下一步报 group '1000' does not exist

坑 2:group '1000' does not exist
需先重建 gid=1000 的组,再建用户:

1
2
3
RUN userdel -r ubuntu \
&& groupadd -g 1000 minos \
&& useradd -m -s /bin/bash -u 1000 -g 1000 minos

坑 3:git clone SSL 证书错误

1
fatal: unable to access 'https://github.com/...': Problem with the SSL CA cert

容器缺 ca-certificates 包 → Dockerfile 补装 ca-certificates(连同 curl)。

4.4 build 容器

1
2
cd ~/workspace/docker-minos
docker build --network host -t minos-build .

--network host 加速 apt/下载。首次会拉 ubuntu:26.04(走 DaoCloud 加速器)。

4.5 验证容器身份

1
2
3
4
5
docker run --rm \
-v ~/minos-output:/home/minos/output \
-v ~/workspace/docker-minos:/home/minos/host \
minos-build bash -c 'whoami; pwd; id'
# 预期: minos / /home/minos/work / uid=1000(minos) gid=1000(minos)

5. 进入容器,从零 clone 源码

5.1 创建并进入容器

1
2
3
4
5
6
mkdir -p ~/minos-work ~/minos-output
docker run -it --name minos-dev \
-v ~/minos-work:/home/minos/work \
-v ~/minos-output:/home/minos/output \
-v ~/workspace/docker-minos:/home/minos/host \
minos-build

A+B 结合--name minos-dev(命名容器)+ -v ~/minos-work:/home/minos/work(源码挂载宿主机,永久保留)。

之后重新进入

1
2
docker start -i minos-dev        # 恢复原会话
docker exec -it minos-dev bash # 新开 shell

5.2 容器内从零 clone 原始 minos

1
2
cd /home/minos/work
git clone https://github.com/minosproject/minos.git

5.3 验证拿到的是干净源码

1
2
3
4
cd /home/minos/work/minos
grep -c 'fno-PIE' Makefile # 预期 0
grep -n 'default:' arch/aarch64/virt/stage2.c # 看 default 分支是否 S2_AP_NON
# 第 158/207 行 default: → S2_AP_NON(原始),第 152/201 行 S2_AP_RW(case 分支,本来就有)

6. 补装构建工具

6.1 发现缺 make/gcc

clone 后执行 makemake: command not found。检查确认容器缺 make/gcc/g++。

6.2 以 root 进入容器安装

1
2
3
4
docker exec -it -u root minos-dev bash
apt-get update
apt-get install -y make gcc g++
exit

minos 用户无 sudo 权限(Dockerfile 没配置),所以用 -u root 进容器安装。
装完 exit 回 minos 用户再构建,保证源码文件属主正确。

6.3 验证

1
2
which make gcc g++
# /usr/bin/make /usr/bin/gcc /usr/bin/g++

7. 复现编译错误

7.1 复现编译错误

1
2
3
cd /home/minos/work/minos
make qemu_arm64_defconfig
make -j$(nproc) CROSS_COMPILE=aarch64-linux-gnu-

复现的报错

1
2
cc1: sorry, unimplemented: code model 'large' with '-fPIC'
fatal error: config/config.h: No such file or directory

7.2 根因分析

错误 根因
code model 'large' with '-fPIC' Ubuntu 26.04 GCC 15 默认启用 PIE(--enable-default-pie),而 minos 的 -mcmodel=large 与 PIE 冲突
config/config.h 缺失 连锁反应:asm-offset.s 因上一个错误编译失败,依赖它的头文件没生成

验证根因(可选加深理解):

1
2
3
4
5
6
7
cd /tmp
echo 'int x;' > t.c
aarch64-linux-gnu-gcc -mcmodel=large -c t.c -o t.o # 失败
aarch64-linux-gnu-gcc -mcmodel=large -fno-PIE -c t.c -o t.o # 成功
echo 'typedef int bool;' > t2.c
gcc -std=c23 -c t2.c -o t2.o # 失败('bool' 是 c23 关键字)
gcc -std=gnu89 -c t2.c -o t2.o # 成功

7.3 补丁 1:Makefile / GCC15 兼容

补丁内容patches/01-makefile-gcc15.patch):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
--- a/Makefile
+++ b/Makefile
@@ -145,7 +145,8 @@ MBUILD_CFLAGS := -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs \
-fno-strict-aliasing -fno-common -fshort-wchar \
-Werror-implicit-function-declaration \
-Wno-format-security -O$(O_LEVEL) -DBUILD_HYPERVISOR \
- -std=gnu89 --static -nostdlib -fno-builtin -g $(MINOSINCLUDE)
+ -std=gnu89 --static -nostdlib -fno-builtin -g \
+ -fno-PIE -fno-pic $(MINOSINCLUDE)
@@ -293,7 +294,7 @@ endif
$(offset_s): $(offset_c)
$(Q) echo " CC $(offset_s)"
- $(Q) gcc $(MINOSINCLUDE) -S $< -o $@
+ $(Q) gcc $(MINOSINCLUDE) -std=gnu89 -fno-PIE -S $< -o $@

为什么这两处

  1. -fno-PIE -fno-pic:解决 -mcmodel=large 与 PIE 冲突(所有 aarch64 编译)
  2. asm-offset 规则加 -std=gnu89:该规则用宿主 gcc 编译,没走 MBUILD_CFLAGS,需单独指定(避免 c23 的 bool 关键字问题)

7.4 应用补丁

1
2
3
cd /home/minos/work/minos
patch -p1 < /home/minos/host/patches/01-makefile-gcc15.patch
# patching file Makefile

验证:

1
grep -n 'fno-PIE' Makefile    # 两处

7.5 并行编译 bug

直接 make -j$(nproc) 会报:

1
make[1]: *** No rule to make target 'apps/esh/built-in.o', needed by 'apps/built-in.o'.  Stop.

根因:Minos 的构建脚本(scripts/Minos.build.mk)用递归 make 生成子目录 built-in.o,并行时父目录依赖检查和子目录生成规则竞态。

解决:用 -j1 单线程构建。

7.6 编译成功

1
2
make -j1 CROSS_COMPILE=aarch64-linux-gnu-
ls -la minos.bin # 320441 字节

7.7 编译 mkrmd

1
2
make mkrmd
ls -la tools/mkrmd/mkrmd # 22336 字节, ELF x86-64

mkrmd 用宿主 gcc 编译,验证了补丁 1 的 asm-offset 规则(-std=gnu89)生效。


8. 构建 U-Boot

1
2
3
4
5
cd /home/minos/work
git clone --depth 1 https://github.com/minosproject/u-boot.git
cd u-boot
make qemu_arm64_defconfig
make -j1 CROSS_COMPILE=aarch64-linux-gnu-

8.1 踩坑:bc 缺失

末尾报错:

1
2
/bin/sh: 1: bc: not found
CFGCHK u-boot.cfg

原因:u-boot 的 CFGCHK 步骤需要 bc(命令行计算器)。不影响 u-boot.bin 生成,但 make 返回非零。

解决

1
2
3
4
5
docker exec -it -u root minos-dev bash
apt-get install -y bc
exit
cd /home/minos/work/u-boot
make -j1 CROSS_COMPILE=aarch64-linux-gnu-

产物u-boot.bin(591400 字节)、u-boot/tools/mkimage(226400 字节)。


9. 构建 Linux 内核

1
2
3
4
5
6
7
8
9
10
cd /home/minos/work
wget -c https://cdn.kernel.org/pub/linux/kernel/v4.x/linux-4.19.238.tar.xz
tar xf linux-4.19.238.tar.xz

cd linux-4.19.238
cp -r /home/minos/work/minos/generic/minos-linux-driver drivers/minos
echo 'obj-y += minos/' >> drivers/Makefile

make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- defconfig
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j$(nproc) Image

要点

  • 下载用 cdn.kernel.org(文档的 mirrors.edge.kernel.org 慢/超时)
  • 集成 minos 驱动:obj-y 强制内置(不是模块),guest 启动后驱动自动加载
  • 产物:arch/arm64/boot/Image(19173888 字节,约 19MB)

为什么需要自编译内核(rootfs 用官方但内核必须自己编):
官方镜像 boot 分区虽有个 Image,但那是 4.2.0 通用内核(无 minos 驱动)。我们编译的 4.19.238 内置 minos-linux-driver(guest↔hypervisor 通信的桥梁:console/vmbox/hypercall),是虚拟化栈核心。


10. 打包 ramdisk

10.1 获取官方 host dtb

官方文档 005 提供网盘下载:qemu-virt.dtb(6899 字节,与文档日志一致)。
下载后放到 ~/minos-work/downloads/(容器内可见为 /home/minos/work/downloads/)。

10.2 打包

1
2
3
4
5
cd /home/minos/work
/home/minos/work/minos/tools/mkrmd/mkrmd \
-f /home/minos/work/ramdisk.bin \
/home/minos/work/linux-4.19.238/arch/arm64/boot/Image \
/home/minos/work/downloads/qemu-virt.dtb

预期输出

1
2
3
4
Image f_offset 0x0 0x19173888
qemu-virt.dtb f_offset 0x19177472 0x6899
generate ramdisk done
ramdisk size 0x124d000

产物ramdisk.bin(19189760 字节)。


11. 构建可启动镜像

11.1 准备工作

官方镜像 virtio-sd.img(512MB)含:

  • 分区 1(boot,76MB FAT16):放启动文件
  • 分区 2(rootfs,436MB ext4):Linux 用户态系统

需要把 minos 的 3 个启动文件写入 boot 分区(分区 1),rootfs 分区(分区 2)保留官方内容。

11.2 挂载方式说明

文档 005 用 mount -o loop,offset=32256(offset=63扇区×512=32256 手动指定),本记录用 losetup -P(自动解析分区表,更通用):

11.3 命令(宿主机执行)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# E0. 建固定挂载目录
mkdir -p ~/minos-work/mnt/boot

# E1. 备份官方镜像
ls -la ~/minos-work/virtio-sd-run.img 2>/dev/null || cp ~/minos-work/downloads/virtio-sd.img ~/minos-work/virtio-sd-run.img

# E2. 写入 boot 分区(3 个文件,不写 boot.scr)
sudo losetup -fP ~/minos-work/virtio-sd-run.img
sudo losetup -l | grep virtio-sd-run
DEV=$(sudo losetup -l | grep virtio-sd-run | awk '{print $1}' | head -1)
sudo mount ${DEV}p1 ~/minos-work/mnt/boot

sudo cp ~/minos-work/minos/minos.bin ~/minos-work/mnt/boot/kernel.bin
sudo cp ~/minos-work/minos/dtbs/qemu-arm64.dtb ~/minos-work/mnt/boot/
sudo cp ~/minos-work/ramdisk.bin ~/minos-work/mnt/boot/

ls -la ~/minos-work/mnt/boot/

sudo umount ~/minos-work/mnt/boot
sudo losetup -d $DEV

写入的文件

文件 内容 谁加载
kernel.bin minos.bin(hypervisor) U-Boot
qemu-arm64.dtb hypervisor 设备树 U-Boot
ramdisk.bin guest 内核+dtb 打包 minos(guest 启动时)

12. QEMU 启动

12.1 启动命令

1
2
3
4
5
qemu-system-aarch64 -nographic -bios /home/finfen01/minos-work/u-boot/u-boot.bin \
-M virtualization=on,gic-version=3 \
-cpu cortex-a53 -machine type=virt -smp 4 -m 2G \
-drive if=none,file=/home/finfen01/minos-work/virtio-sd-run.img,format=raw,id=hd0 \
-device virtio-blk-device,drive=hd0

踩坑:用 ~/minos-work/... 时 QEMU 可能报 Could not open ...: No such file or directory
(波浪号未被展开)。改用绝对路径解决。

12.2 预期成功输出

1
2
3
4
5
6
7
8
9
10
11
12
13
U-Boot 2019.07-rc4 ...
Starting kernel ...
[ 0.000000] Starting Minos AARCH64
[ 0.000000] Minos v0.4.00
[ 0.000000] current EL is 2 ← hypervisor 在 EL2
[ 0.000000] vgicv3: nr_lrs 4 nr_prs 5
...
[ 0.000000] Booting Linux on physical CPU ...
[ 0.000000] Linux version 4.19.238 ... ← 我们编译的内核
[ 0.000000] Memory: 963728K/1048576K available
[ 0.123662] CPU: All CPU(s) started at EL1
[ 0.651527] VFS: Mounted root (ext4 filesystem) on device 254:2.
root@genericarmv8:~# ← 进入 shell!

13. 最终验证

13.1 确认内核版本

1
2
root@genericarmv8:~# uname -a
Linux genericarmv8 4.19.238 #1 SMP PREEMPT Mon Aug 10 05:44:36 UTC 2026 aarch64 GNU/Linux

13.2 确认 minos 驱动加载

1
2
3
4
root@genericarmv8:~# dmesg | grep -i minos
[ 0.000000] Linux version 4.19.238 (minos@c38964958dc3) (gcc version 15.2.0...)
[ 0.550523] Minos Hyperviosr Driver Init ...
[ 0.551254] Minos Hyperviosr Driver Init Done

Minos Hyperviosr Driver Init Done 是最终确认:guest 内核里的 minos 驱动成功与 EL2 的 hypervisor 建立通信(HVC hypercall / vmbox),完整 Type-1 虚拟化栈工作正常。

注意:官方 rootfs 是精简 BusyBox,head -5 不支持,用 head -n 5 或直接 head


14. 记录中踩到的坑汇总

# 现象 根因 解决
1 Docker Hub 拉镜像超时 registry-1.docker.io 网络不可达 配置 DaoCloud 镜像加速器
2 Dockerfile UID 1000 is not unique ubuntu:26.04 自带 ubuntu 用户 userdel -r ubuntu
3 group '1000' does not exist userdel 连主组一起删 groupadd -g 1000 minos
4 git clone SSL 证书错误 容器缺 ca-certificates Dockerfile 补装 ca-certificates
5 make: command not found Dockerfile 漏装 make/gcc 容器内 apt install make gcc g++
6 code model 'large' with '-fPIC' GCC 15 默认 PIE 补丁 1:-fno-PIE -fno-pic
7 No rule to make target .../built-in.o Minos 构建脚本并行竞态 make -j1
8 u-boot bc: not found CFGCHK 需要 bc apt install bc
9 QEMU Could not open '~/...' 波浪号未展开 用绝对路径

15. 关键产物位置

产物 容器路径 宿主机路径
minos.bin /home/minos/work/minos/minos.bin ~/minos-work/minos/minos.bin
qemu-arm64.dtb /home/minos/work/minos/dtbs/qemu-arm64.dtb ~/minos-work/minos/dtbs/qemu-arm64.dtb
mkrmd /home/minos/work/minos/tools/mkrmd/mkrmd ~/minos-work/minos/tools/mkrmd/mkrmd
u-boot.bin /home/minos/work/u-boot/u-boot.bin ~/minos-work/u-boot/u-boot.bin
Image /home/minos/work/linux-4.19.238/arch/arm64/boot/Image ~/minos-work/linux-4.19.238/arch/arm64/boot/Image
ramdisk.bin /home/minos/work/ramdisk.bin ~/minos-work/ramdisk.bin
官方 dtb /home/minos/work/downloads/qemu-virt.dtb ~/minos-work/downloads/qemu-virt.dtb
官方镜像 /home/minos/work/downloads/virtio-sd.img ~/minos-work/downloads/virtio-sd.img
可启动镜像 - ~/minos-work/virtio-sd-run.img

容器minos-dev(挂载 ~/minos-work,源码永久保留)