Minos 虚拟化: Hypercall 与设备虚拟化

本文拆解 Minos 中 guest 与 hypervisor 的通信与设备模拟机制:Hypercall 的 ID 编码与调用约定、从 HVC 指令到 handler 的分发链路、VM 管理接口,并以 create_guest_vm 为线索串起 mvm—驱动—hypervisor 的完整数据流,最后落到 vdev 框架与 virtio-mmio 块设备虚拟化的实例。

1. 背景与设计选择

1.1 一个需要”双向通信”的系统

minos 采用”VM0 托管其他 VM”的模型:VM0(host Linux)负责创建、销毁、控制所有 guest VM,并通过用户态进程 mvm 来管理每个 guest。这个模型天然产生两个方向的通信需求

  1. VM0 → hypervisor(控制面):VM0 要”创建一个新 VM”、”启动一个 VM”、”把一块 guest 内存映射到自己的地址空间”——这些是改变系统状态的管理操作,需要一个同步的控制通道。这就是 hypercall(HVC)
  2. guest → VM0(数据面/服务面):guest 运行中遇到”需要 VM0 协助”的事件(访问 virtio 设备寄存器、请求时间、请求重启),需要一个异步的请求-应答通道。这就是第 5、6 章讲的 vmcs + MMIO trap

两个方向用不同的机制,因为它们的语义完全不同:

方向 机制 同步性 谁发起 用途
VM0 → hypervisor HVC hypercall 同步(hvc 指令陷入 EL2,处理完 eret 返回) VM0(host) 创建/销毁/控制 VM、内存映射、发 virq
guest → VM0 MMIO trap + vmcs 异步(guest 写共享内存,virq 通知 VM0,轮询应答) guest 设备模拟、时间、重启等服务请求

注意两者方向相反:hypercall 是”VM0 向 hypervisor 下达指令”,vmcs 是”guest 向 VM0 请求服务”。这一章主题是前者,但两者在 virtio 场景(第 7 章)会串联起来。

1.2 hypercall 与传统系统调用的区别

传统 OS 里,用户程序用 svc(system call)陷入内核请求服务;在 minos 里,VM0 的 Linux 用 hvc(hypercall)陷入 EL2 的 hypervisor 请求服务。两者共享一个关键点:调用方通过寄存器传参、通过寄存器取返回值(ARM 的 SMCCC 约定),但服务对象不同:

  • Linux 的 svc → 服务方是 Linux 内核本身
  • VM0 的 hvc → 服务方是 minos hypervisor(EL2)

第 2 章先讲 hypercall 的”号码”怎么编码,第 3 章讲号码怎么分发到 handler,第 4 章看 handler 具体做什么。


2. Hypercall 的 ID 编码与调用约定

2.1 为什么需要一套编码规则

hypervisor 面对两类调用:HVC(hypervisor 自有的 hypercall)SMC(ARM 标准调用,主要是 PSCI)。两者都要在 EL2 被分发给正确的 handler。为了让”分发”统一、可扩展,minos 复用了 ARM SMCCC(SMC Calling Convention)的函数标识符(Function Identifier)位布局:把”服务类别”编码进 ID 的固定位段,分发时只查这一个位段就能定位 handler。

2.2 Function Identifier 的位布局

arch/aarch64/include/asm/svccc.h:6-18 按 ARM_DEN0028B 定义了 ID 的位含义:

1
2
3
4
5
6
7
8
9
Function Identifier (32-bit) bit layout
┌──┬──┬──────────┬────────┬────────────────┐
│31│30│ 29..24 │ 23..16 │ 15..0 │
├──┼──┼──────────┼────────┼────────────────┤
│CT│BT│ STYPE │ zero │ function n │
└──┴──┴──────────┴────────┴────────────────┘
CT : call type 0 = yielding call 1 = fast call
BT : call type 0 = smc32/hvc32 1 = smc64/hvc64
STYPE: service call range (0x00..0x3f)

对应的掩码宏(svccc.h:15-18):

含义
SVC_CTYPE_MASK 0x80000000 bit31:fast call 标志
SVC_BTYPE_MASK 0x40000000 bit30:64 位调用标志
SVC_STYPE_MASK 0x3f000000 bit[29:24]:服务类别
SVC_FID_MASK 0x0000ffff bit[15:0]:函数号

分发只依赖 STYPE(bit[29:24]):hypervisor 从 ID 里取出这一位段,作为查表下标。svc_service.c:40 正是这么做的:type = (svc_id & SVC_STYPE_MASK) >> 24;

2.3 minos 的 hypercall ID 编码

include/virt/hypercall.h:10-17 定义编码规则:

1
2
3
4
5
6
#define HVC_CALL_BASE		(0xc0000000)	/* bit31+bit30 置位 = hvc64 fast call */

#define HVC_CALL_NUM(t, n) (HVC_CALL_BASE + (t << 24) + n)

#define HVC_VM0_FN(n) HVC_CALL_NUM(HVC_TYPE_VM0, n)
#define HVC_MISC_FN(n) HVC_CALL_NUM(HVC_TYPE_MISC, n)

服务类别(hypercall.h:5-8):

用途
HVC_TYPE_VM0 0x8 VM 生命周期管理(VM0 专用)
HVC_TYPE_MISC 0x9 杂项(取 vmid、让出 CPU、能力查询)
HVC_TYPE_VMBOX 0xa VM 间通信(vmbox,Phase 10)
HVC_TYPE_DEBUG_CONSOLE 0xb 调试控制台

HVC_VM_CREATE 为例(hypercall.h:20HVC_VM0_FN(0)):

1
2
3
4
5
6
HVC_VM_CREATE = HVC_CALL_BASE + (0x08 << 24) + 0
= 0xc0000000 + 0x08000000 + 0
= 0xc8000000
^^ ^^ ^^ ^
CT BT STYPE=0x08 fn=0
(fast call, 64-bit)

关键点:**HVC_VM_CREATE 的值 0xc8000000 里,0x8 正好落在 STYPE 位段(bit[29:24])**。所以第 3 章分发时 (0xc8000000 & 0x3f000000) >> 24 = 0x08,查到”VM0 服务”这一类的 handler。

同样地,PSCI 标准调用(第 3.4 章)的 ID 是 PSCI_0_2_FN_BASE = 0x84000000psci.h:14),取 STYPE 位段:(0x84000000 & 0x3f000000) >> 24 = 0x04,正好等于 SVC_STYPE_STDSMC——这就是为什么 PSCI 调用能不加修改地进入 std_smc_handler

2.4 调用侧的传参与返回

调用方(VM0 的 Linux)把 ID 放 x0,参数放 x1-x6,执行 hvc 指令陷入 EL2。arch/aarch64/virt/trap.c:124-141__arm_svc_handler 把寄存器搬到数组里交给分发函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static int __arm_svc_handler(gp_regs *reg, int smc)
{
uint32_t id;
unsigned long args[6];

id = reg->x0; /* Function Identifier */
args[0] = reg->x1; /* 参数 a0 - a5 */
args[1] = reg->x2;
args[2] = reg->x3;
args[3] = reg->x4;
args[4] = reg->x5;
args[5] = reg->x6;

if (!(id & SVC_CTYPE_MASK)) /* yielding call 允许开中断 */
local_irq_enable();

return do_svc_handler(reg, id, args, smc);
}

返回时用 SVC_RET1/RET2 宏把结果写回 x0/x1(svccc.h:37-46),eret 后调用方直接从寄存器取。这就是”同步控制通道”的含义:调用全程卡在 EL2,直到结果写回寄存器

补充:minos 的 hypercall ID 都在 HVC_CALL_BASE(0xc0000000) 之上,bit31 恒为 1(fast call),所以 __arm_svc_handler 里的 local_irq_enable()trap.c:137-138,只对 bit31=0 的 yielding call 生效)对 minos 自身的 hypercall 实际不会触发。

ID 编码定了,分发还需要一张”类别 → handler”的表。下一章看这张表怎么建、怎么查。


3. 分发机制:从 HVC 指令到 handler

3.1 异常入口:EC 分发

guest 执行 hvc 指令时,CPU 以 EC = HVC64(0x16)陷入 EL2。trap.c:392-420handle_vcpu_sync_exception 读出 ESR 的 EC 字段,查 guest_sync_descs[] 表:

1
2
3
4
5
6
7
esr_value = read_esr_el2();
ec_type = (esr_value & ESR_ELx_EC_MASK) >> ESR_ELx_EC_SHIFT;
...
ec = guest_sync_descs[ec_type];
...
regs->pc += ec->ret_addr_adjust; /* 部分异常需跳过指令 */
ec->handler(regs, ec_type, esr_value);

注意:ret_addr_adjust 对 HVC 是 0trap.c:363)——HVC 陷入时 ELR_EL2 已指向下一条指令,不需要再加;而 WFx 表项是 4trap.c:344)——WFI/WFE 陷入时 ELR_EL2 停在 WFI 指令本身,需 +4 跳过它,否则会再次执行 WFI。

HVC64 的表项(trap.c:363):

1
2
DEFINE_SYNC_DESC(guest_ESR_ELx_EC_HVC64, EC_TYPE_AARCH64,
aarch64_hypercall_handler, 1, 0);

aarch64_hypercall_handlertrap.c:320-329)先看 VM 有没有自定义的 HVC 处理钩子(arm_data->hvc_handler),没有就走通用路径 __arm_svc_handler(reg, 0)(第 2.4 节),最终到 do_svc_handlerSMC64 同理,走 aarch64_smccall_handlertrap.c:331-340)→ __arm_svc_handler(reg, 1)

3.2 handler 注册表:section 收集

分发表本身由 编译期 section + 链接期收集 构建,这与 Phase 5 的 DEFINE_SYNC_DESC、Phase 7 的 DEFINE_PLATFORM 是同一套模式。svccc.h:78-94 定义两个注册宏:

1
2
3
4
5
6
7
8
9
10
11
#define DEFINE_HVC_HANDLER(n, start, end, h)	\
static struct svc_desc __hvc_##h __used \
__section(".__hvc_handler") = { \
.name = n, \
.type_start = start, \
.type_end = end, \
.handler = h, \
}

#define DEFINE_SMC_HANDLER(n, start, end, h) \
... __section(".__smc_handler") ...

struct svc_descsvccc.h:71-76)含 type_start/type_end(可服务的一整段类别)和 handler。各模块用宏把自己”登记”进对应 section:

  • hypercall.c:144-148vm_hvc_handler 覆盖 HVC_TYPE_VM0misc_hvc_handler 覆盖 HVC_TYPE_MISC
  • smc_service.c:110-111std_smc_handler 覆盖 SVC_STYPE_STDSMC(PSCI 标准服务)。

开机时 svc_service_initsvc_service.c:77-87)把 section 展开填进两张表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static struct svc_desc *smc_descs[SVC_STYPE_MAX];	/* svc_service.c:26-27 */
static struct svc_desc *hvc_descs[SVC_STYPE_MAX];

static void parse_svc_desc(unsigned long start, unsigned long end,
struct svc_desc **table)
{
struct svc_desc *desc;
int32_t j;

section_for_each_item_addr(start, end, desc) {
BUG_ON((desc->type_start > desc->type_end) ||
(desc->type_end >= SVC_STYPE_MAX));
for (j = desc->type_start; j <= desc->type_end; j++)
table[j] = desc;
}
}

即:hvc_descs[0x08] = vm_hvc_handlerhvc_descs[0x09] = misc_hvc_handlersmc_descs[0x04] = std_smc_handler。**表的下标就是 STYPE**。

3.3 查表分发:do_svc_handler

do_svc_handlersvc_service.c:29-57)是分发核心:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int do_svc_handler(gp_regs *regs, uint32_t svc_id, uint64_t *args, int smc)
{
uint16_t type;
struct svc_desc **table;
struct svc_desc *desc;

if (smc)
table = smc_descs;
else
table = hvc_descs;

type = (svc_id & SVC_STYPE_MASK) >> 24; /* 取 STYPE 作下标 */

if (unlikely(type > SVC_STYPE_MAX))
goto invalid;

desc = table[type];
if (unlikely(!desc))
goto invalid;

return desc->handler(regs, svc_id, args);
invalid:
SVC_RET1(regs, -EINVAL);
}

一次 HVC 调用的完整分发路径:

phase8_hvc_flow

关键理解:**svc_service.c 只负责”把 ID 的 STYPE 位段翻译成 handler 指针”**,不关心具体业务。加一种新的 hypercall 类别 = 用 DEFINE_HVC_HANDLER 注册一个新 handler 覆盖一个未用的 STYPE,无需改分发代码——这就是”可扩展”的来源。

3.4 SMC 通道:PSCI 的虚拟化

smc_service.c 用同一套机制处理 guest 的 PSCI 调用(CPU 开关、系统复位等)。ARM 的 PSCI 标准函数 ID 落在 SVC_STYPE_STDSMC = 0x04svccc.h:24),因此 DEFINE_SMC_HANDLER(..., SVC_STYPE_STDSMC, SVC_STYPE_STDSMC, std_smc_handler) 就把所有 PSCI 调用引到 std_smc_handlersmc_service.c:24-108):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static int std_smc_handler(gp_regs *c, uint32_t id, unsigned long *args)
{
switch (id) {
case PSCI_0_2_FN64_CPU_ON:
case PSCI_0_2_FN_CPU_ON:
ret = vcpu_power_on(get_current_vcpu(), args[0], args[1], args[2]);
...
case PSCI_0_2_FN_CPU_OFF:
ret = vcpu_off(get_current_vcpu());
...
case PSCI_0_2_FN_SYSTEM_OFF:
vm_power_off(get_vmid(get_current_vcpu()), NULL, VM_PM_ACTION_BY_SELF);
...
}
SVC_RET1(c, PSCI_RET_SUCCESS);
}

这里把 guest 的 CPU_ON/CPU_OFF/SYSTEM_RESETPSCI 调用翻译成对 vCPU/VM 的操作——相当于 hypervisor 替 guest 模拟了”电源管理固件”。其中 vcpu_power_on 就是 Phase 7 讲过的多 vCPU 二次启动入口。

理解点:HVC 与 SMC 只是入口指令不同,分发框架完全复用(smc 标志选择 smc_descs 还是 hvc_descs)。PSCI 之所以无需额外注册就能接入,是因为它的标准 ID 本身就带 STYPE=0x04


4. Hypercall 的 VM 管理接口

4.1 一个函数管理所有 VM 操作

vm_hvc_handlervirt/hypercall.c:28-107)是 VM0 侧所有 VM 管理 hypercall 的总入口,按 ID 的函数号(bit[15:0])switch 分发。开头有一段重要的安全检查:

1
2
3
4
5
6
7
8
9
static int vm_hvc_handler(gp_regs *c, uint32_t id, uint64_t *args)
{
int vmid = -1, ret;
unsigned long addr;
unsigned long hbase = 0;
struct vm *vm = get_vm_by_id((uint32_t)args[0]);

if (!vm_is_host_vm(get_current_vm()))
panic("only vm0 can call vm related hypercall\n");

vm_is_host_vm(get_current_vm()) 检查发起者是否为 VM0——这是”VM0 中心模型”在安全上的体现:只有 VM0 能执行 VM 生命周期操作,其他 guest 调用这里直接 panic。

各 case 几乎都是”取参数 → 调 virt/vm.c 或 virt/vm_pm.c 里的函数 → HVC_RET1 回传结果”的薄封装。分类如下。

4.2 VM 生命周期:create / destroy / restart / power

ID 实现 作用
HVC_VM_CREATE HVC_VM0_FN(0) create_guest_vm(vm.c:593) 创建 guest,返回新 vmid
HVC_VM_DESTORY HVC_VM0_FN(1) destroy_guest_vm(vm_pm.c:315) 销毁 guest
HVC_VM_RESTART HVC_VM0_FN(2) vm_reset(vm_pm.c:229) 复位 guest
HVC_VM_POWER_UP HVC_VM0_FN(3) power_up_guest_vm(vm_pm.c:431) 上电启动 guest
HVC_VM_POWER_DOWN HVC_VM0_FN(4) vm_power_off(vm_pm.c:322) 下电 guest

create_guest_vm 的完整数据流留到第 5 章专门展开;vm_reset/vm_power_off 按”native VM / guest VM”分两条路(vm_pm.c:209-227__vm_reset)。guest 路径并不由 hypervisor 直接重启__vm_reset 先停所有 vCPU(vcpu_enter_poweroff),guest 分支 guest_vm_resetvm_pm.c:201-207)只把 vm->stateVM_STATE_OFFLINE,再经 vmcs 异步通知 mvm(trap_vcpu_nonblock(VMTRAP_REASON_REBOOT));真正重载镜像并 IOCTL_POWER_UP_VMdo_start_vm 的是 mvm 侧(tools/mvm/main/mvm.c:578-608__vm_reboot)。

4.3 内存与资源:mmap / create resource / virtio mmio init

ID 实现 作用
HVC_VM_MMAP HVC_VM0_FN(5) create_vm_mmap(vm.c:578) 把 guest 内存映射进 VM0,返回 VM0 侧 IPA
HVC_VM_CREATE_RESOURCE HVC_VM0_FN(13) os_create_guest_vm_resource(os.c:119) 从 guest dtb 解析创建 VM 资源(vdev/virqchip 等)
HVC_VM_VIRTIO_MMIO_INIT HVC_VM0_FN(11) virtio_mmio_init(virtio_mmio.c:224) 为 virtio 设备分配共享内存并双向映射

这三个分别在后续章节展开:HVC_VM_MMAP 在第 5.4 节,HVC_VM_VIRTIO_MMIO_INIT 在第 7.4 节,HVC_VM_CREATE_RESOURCE 对应第 5 章的资源装配。先记住一句话:**HVC_VM_MMAP 让 VM0 获得访问 guest 内存的能力,HVC_VM_VIRTIO_MMIO_INIT 让 VM0 获得操作 virtio 寄存器文件的能力**——它们共同支撑了”VM0 替 guest 处理事务”。

4.4 中断与 vmcs:send virq / create vmcs

ID 实现 作用
HVC_VM_SEND_VIRQ HVC_VM0_FN(7) send_virq_to_vm(virq.c:299) 向 guest 注入一个 SPI virq
HVC_VM_CREATE_VMCS HVC_VM0_FN(8) vm_create_vmcs(vmcs.c:154) 创建 VMCS 共享内存并映射到 VM0
HVC_VM_CREATE_VMCS_IRQ HVC_VM0_FN(9) vm_create_vmcs_irq(vmcs.c:189) 为每个 vCPU 分配一个 hvm virq
HVC_VM_REQUEST_VIRQ HVC_VM0_FN(10) request_vm_virqs(vm.c:474) 批量申请一批 virq

send_virq_to_vmvirq.c:299)要求 virq ≥ VM_LOCAL_VIRQ_NR(只支持 SPI),找到 desc 后走 Phase 5 的注入路径。这组接口是第 7 章 virtio 完成”数据就绪 → 通知 guest”的通道:后端在 VM0 处理完请求,通过 HVC_VM_SEND_VIRQ 让 hypervisor 往 guest 注入中断

4.5 杂项:misc_hvc_handler

misc_hvc_handlerhypercall.c:122-142)给所有 VM(不只 VM0)提供几个通用服务:

ID 作用
HVC_GET_VMID HVC_MISC_FN(0) 返回当前 VM 的 vmid
HVC_SCHED_OUT HVC_MISC_FN(1) 主动让出 CPU(sched()
HVC_GET_VM_CAP HVC_MISC_FN(2) 返回 VM 能力位(VM_CAP_HOST/VM_CAP_NATIVE

get_vm_capabilityhypercall.c:112-120)用宏把 VM 的 flags 翻译成能力位:VM_FLAGS_HOST → VM_CAP_HOSTVM_FLAGS_NATIVE → VM_CAP_NATIVE。VM0 的 Linux 驱动通过 HVC_GET_VM_CAP 判断自身是否为 host(minos_hypercall.h:172-175vm_is_host_vm())。

第 4 章看到的是”hypervisor 侧”的接口;第 5 章把它接到 VM0 侧的实际调用链上,回答”mvm 用户进程怎么一步步调到这里”。


5. 完整数据流:create_guest_vm 的旅程

5.1 mvm:用户态发起者

mvm 是运行在 VM0 里的用户态进程(tools/mvm/main/mvm.c),一个 mvm 进程管理一个 guest。它的初始化流程(mvm_mainmvm.c:704-816)顺序固定:

phase8_mvm_main

其中 “创建 VM”“启动 VM” 是分开的两步create_and_init_vm 里的 create_new_vmmvm.c:121-154)构造 struct vmtag(名称、OS、核数、内存、入口地址)后发 IOCTL_CREATE_VM;真正的上电在 mvm_main_loop 里发 IOCTL_POWER_UP_VM。这与 Phase 7 的 NO_AUTO_START + vcpu_online 对应:创建只建立 VM 结构,上电才让 vCPU 进就绪队列。

5.2 内核驱动:ioctl → hvc 的翻译层

VM0 内核里加载了 generic/minos-linux-driver/minos.c(mvm 设备驱动)。它把 mvm 的 ioctl 翻译成 hypercall。mvm 的 ioctl(IOCTL_CREATE_VM, &vmtag) 落到驱动的 vm0_ioctlminos.c:1006-1021)→ ioctl_create_vmminos.c:984-1004):

1
2
3
4
5
6
7
8
9
10
11
static int ioctl_create_vm(struct file *filp, void __user *p)
{
int vmid;
struct vmtag vmtag;

vmid = copy_from_user(&vmtag, p, sizeof(struct vmtag)); /* 取用户态参数 */
...
vmid = create_new_vm(&vmtag); /* 定义见 minos.c:951 */
...
return vmid;
}

create_new_vmminos.c:951-982)调用 hypercall 封装 hvc_vm_create(info),再为这个 vmid 建立设备节点、注册 mmu_notifier(guest 内存被释放时通知 hypervisor 解除映射):

1
2
3
4
5
6
7
8
9
10
11
static int create_new_vm(struct vmtag *info)
{
...
vmid = hvc_vm_create(info); /* → HVC_VM_CREATE */
...
ret = create_vm_device(vmid, info); /* /dev/mvm/mvm<vmid> 设备节点 */
...
vm->mmu_notifier.ops = &mvm_mmu_notifier_ops;
mmu_notifier_register(&vm->mmu_notifier, vm->mm);
return vmid;
}

hypercall 的最终封装在 minos_hypercall.h:50-63,用 Linux 的 SMCCC 辅助函数直接执行 hvc

1
2
3
4
5
6
7
8
9
10
11
12
static inline unsigned long __minos_hvc(uint32_t id, unsigned long a0, ...)
{
struct arm_smccc_res res;
arm_smccc_hvc(id, a0, a1, a2, a3, a4, a5, 0, &res);
return res.a0;
}

#define minos_hvc1(id, a) minos_hvc(id, a, 0, 0, 0, 0, 0)
static inline int hvc_vm_create(void *vmtag)
{
return minos_hvc1(HVC_VM_CREATE, vmtag);
}

HVC_VM_CREATE 进 x0、vmtag 进 x1。)

至此链条是:mvm → ioctl → 驱动 → hvc 指令 → EL2。第 3 章的分发机制接手后进入第 4 章的 create_guest_vm

5.3 hypervisor 侧:create_guest_vm

create_guest_vmvirt/vm.c:593-623):

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
29
30
31
int create_guest_vm(struct vmtag __guest *tag)
{
int ret = 0;
struct vm *vm;
struct vmtag vmtag;

memset(&vmtag, 0, sizeof(struct vmtag));
ret = copy_from_guest(&vmtag, tag, sizeof(struct vmtag)); /* ① 从 VM0 内存拷参数 */
if (ret != 0) {
pr_err("copy vmtag from guest failed\n");
return -EFAULT;
}

ret = vmtag_check_and_config(&vmtag); /* ② 校验并配置 vmtag */
if (ret)
return ret;

vmtag.vmid = 0;
vmtag.flags |= VM_FLAGS_CAN_RESET;
vm = create_vm(&vmtag, NULL); /* ③ 分配 vmid、建 VM 结构 */
if (!vm)
return -ENOMEM;

ret = guest_mm_init(vm, vmtag.mem_base, vmtag.mem_size); /* ④ 初始化 stage2 内存 */
if (ret) {
destroy_vm(vm);
return ret;
}

return vm->vmid;
}

四步对应四个关注点:

  1. copy_from_guest(vm.c:600)tag 是 VM0 内核的虚拟地址,hypervisor 要把这块数据从 VM0 的地址空间拷到 EL2。它逐页做”VM0 虚拟地址 → IPA → 物理地址 → 临时映射 → memcpy”(vmm.c:500-529)。这也是学习计划里提到的安全边界——guest 输入都从这里进来。
  2. vmtag_check_and_config(vm.c:441):校验 vmtag 的合法性,检查/分配 vCPU 亲和性(Phase 7 的 vcpu_affinity 动态分配就在这)。
  3. create_vm(vm.c:885):分配新 vmid(alloc_new_vmid)、__create_vm 建 VM 结构、create_vcpus(vm.c:786)逐个建 vCPU 任务。与 Phase 7 完全衔接。
  4. guest_mm_init(vm.c:563):建立 guest 的 stage2 页表,映射 guest 内存。

5.4 mmap:把 guest 内存映射给 VM0

VM 创建后,mvm 还需要”访问 guest 内存”的能力(比如把 kernel 拷进去、让 virtio 后端读写 vring)。这就是 map_vm_memorymvm.c:70-119):

phase8_mmap_flow

映射完成后,mvm 进程得到一个连续的虚拟地址区间(vm->mmap),guest 的物理内存与 mvm 的进程内存共享同一块物理页gpa_to_hvm_vatools/mvm/include/minos/vm.h:100-101)就是做这个换算的:

1
2
#define gpa_to_hvm_va(gpa) \
(unsigned long)(mvm_vm->mmap + ((gpa) - mvm_vm->mem_start))

含义:guest 物理地址 → mvm 进程虚拟地址,靠”减去 guest 内存基址、加上 mvm 映射基址”完成。virtio 后端用它对 vring 描述符做地址换算(第 7.6 节)。vm_load_images 正是先 mmap、再往这块共享内存里写入 kernel/dtb。

完整创建数据流:

phase8_create_vm

5.5 vCPU 线程与 vmcs 事件循环

创建完 VM、映射完内存,mvm 为每个 vCPU 建一个线程(vm_create_vcpusmvm.c:311-374)来响应 guest 的服务请求。每个 vCPU 线程绑定一个 eventfd,并把它注册到 VM0 内核(IOCTL_REGISTER_VCPUregister_vm_eventminos.c:338-390)。之后线程在 epoll 上等事件(vm_vcpu_threadmvm.c:510-561):

1
2
3
4
5
6
while (1) {
ret = epoll_wait(epfd, &ep_events, 1, -1); /* 等 guest 来请求 */
...
eventfd_read(eventfd, &value);
handle_vcpu_event(vmcs); /* mvm.c:482-508:读 vmcs 处理 */
}

handle_vcpu_event 读 vmcs 的 trap_type/trap_reason/trap_data,按类型分派:VMTRAP_TYPE_MMIO 交给 vcpu_handle_mmiomvm.c:427-453)遍历 VM 的 vdev 列表模拟设备访问;VMTRAP_TYPE_COMMON 处理重启/关机/取时间等。处理完 vmcs_ackmvm.c:418-425)把 guest_index++,guest 端的等待循环才放行(Phase 7 第 6 章)。

到这里,”VM0 怎么管 VM”已经走通。但 guest 想要”用设备”还差关键一环:guest 访问设备寄存器时,是谁、在哪一步把请求交给 VM0 的 mvm? 这就是第 6 章的 vdev 机制。


6. vdev 机制:设备虚拟化的框架

6.1 为什么需要 vdev

虚拟设备有两种实现位置:hypervisor 内部实现(如 vGIC、vtimer,直接操作硬件状态)和 VM0 用户态实现(如 virtio 块设备,读写镜像文件)。对于后者,hypervisor 需要一种统一的方式:把 guest 对一段地址的 MMIO 访问,翻译成对 VM0 后端的一次请求

struct vdevinclude/virt/vdev.h:14-29)就是这样一个抽象:它描述”虚拟设备”,持有该设备在 guest 地址空间里的 MMIO 区间,以及 read/write 回调:

1
2
3
4
5
6
7
8
9
10
11
12
13
struct vdev {
char name[VDEV_NAME_SIZE + 1];
int host;
struct vm *vm;
struct vmm_area *gvm_area; /* 设备占用的 guest MMIO 区间链表 */
struct list_head list; /* 挂到 vm->vdev_list */

int (*read)(struct vdev *, gp_regs *, int, unsigned long, unsigned long *);
int (*write)(struct vdev *, gp_regs *, int, unsigned long, unsigned long *);
void (*deinit)(struct vdev *vdev);
void (*reset)(struct vdev *vdev);
...
};

vdev_add_iomem_rangevdev.c:102-123)在 guest 的 mm 里 split_vmm_area 划出一段 VM_GUEST_VDEV 区,挂进 gvm_area 链表——**这段区间就是 vdev 在 guest 眼中的”设备寄存器文件”**。

6.2 vdev 的注册:从设备树节点创建

hypervisor 侧的 vdev 由 资源解析 创建:resource.c 遍历 VM 的 dtb,遇到”设备类”节点就查 __vdev section 里的创建函数。virtio_mmio.c:222 的注册:

1
VDEV_DECLARE(virtio_mmio, virtio_match_table, virtio_create_device);

virtio_create_devicevirtio_mmio.c:170-221)做的事:

1
2
3
4
5
6
7
8
9
10
ret = translate_device_address(node, &base, &size);	/* 取设备 MMIO 基址 */
...
vdev = &virtio_dev->vdev;
host_vdev_init(vm, vdev, "virtio-mmio");
vdev_add_iomem_range(vdev, base, VIRTIO_DEVICE_IOMEM_SIZE); /* 划 guest MMIO 区 */
...
translate_guest_ipa(&vm->mm, base, (unsigned long *)&virtio_dev->iomem); /* 换算共享内存地址 */
vdev->read = virtio_mmio_read;
vdev->write = virtio_mmio_write;
vdev_add(vdev); /* 挂进 vm->vdev_list */

6.3 MMIO trap → vdev_mmio_emulation

guest 访问 vdev 的 MMIO 区间时产生数据异常,走 Phase 3 的 dataabort_tfl_handlertrap.c:241-294),最终调到 vdev_mmio_emulationvdev.c:198-238):

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
29
30
int vdev_mmio_emulation(gp_regs *regs, int write,
unsigned long address, unsigned long *value)
{
struct vm *vm = get_current_vm();
struct vdev *vdev;
struct vmm_area *va;
int idx, ret = 0;

list_for_each_entry(vdev, &vm->vdev_list, list) {
idx = 0;
va = vdev->gvm_area;
while (va) {
if ((address >= va->start) && (address <= va->end)) {
ret = handle_mmio(vdev, regs, write, idx,
address - va->start, value); /* 命中 → vdev->write/read */
...
return 0;
}
idx++;
va = va->next;
}
}

/* 没有 hypervisor 侧 vdev 能处理 → 转发给 VM0 */
if (vm_is_native(vm))
return -EACCES;

ret = trap_vcpu(VMTRAP_TYPE_MMIO, write, address, value);
...
}

逻辑分两层:

  1. 先在 hypervisor 的 vdev_list 里找:命中就调用该 vdev 的 read/write 回调(virtio 的实现在第 7 章)。
  2. 找不到 → 转发给 VM0trap_vcpu(VMTRAP_TYPE_MMIO, ...)vmcs.h:46-50)→ __vcpu_trapvmcs.c:24-129)走 vmcs 共享内存协议,把”这次 MMIO 读/写”投递给 VM0 的 mvm(第 5.5 节的事件循环会收到并处理)。native VM 例外,直接返回 -EACCES 让 hypervisor 注入 abort。

这里能看到 minos 的”双层设备模型”:能快速处理的(vGIC/vtimer)留在 hypervisor,费资源的(块/网络)下沉到 VM0 用户态。第 7 章 virtio 用的就是下沉路径。

6.4 转发链路小结

guest 一次 MMIO 写请求落到 mvm 的完整转发链(第 7 章会具体到 virtio):

phase8_forward_chain


7. virtio-mmio:块设备虚拟化实例

7.1 整体架构:三方协作

virtio 是”前端-后端”模型,在 minos 里被拆成三方

phase8_virtio_arch

  • guest 侧:Linux 自带的 virtio-mmio 前端驱动,把 virtio 设备当作 MMIO 寄存器 + 共享内存中的 vring 队列。
  • hypervisor 侧virt/virtio_mmio.c):分配一块共享内存作为”设备寄存器文件”,同时映射到 guest 和 VM0;guest 的 MMIO 写会 trap 进来,hypervisor 负责把写操作转发给 VM0 后端。
  • VM0 侧tools/mvm/devices/virtio/):mvm 里的 virtio 后端(virtio_block.c 等),真正读写磁盘/网络设备,通过 vring 与 guest 交换数据。

7.2 guest 侧如何发现并使用 virtio 设备

前面几节默认”guest 直接操作 MMIO 寄存器”,但 guest 怎么知道有这样一个设备、怎么初始化它,需要补上。答案分两步:mvm 先把设备”装进”guest 的 dtb,Linux 内核再按标准 virtio-mmio 流程探测和初始化

第 1 步:mvm 把设备写进 guest dtb。 guest 启动装配时(mvm 侧 os_setup_vmlinux_setup_envvdev_setup_envvdev_setup_dtbtools/mvm/devices/vdev.c:232-241),mvm 遍历自己创建的所有 virtio vdev,用 dtb_add_virtiovdev.c:167-208)在 guest 设备树 /smb 节点下加一个子节点:

1
2
3
4
5
6
7
fdt_setprop(dtb, node, "compatible", "virtio,mmio", 12);	/* 匹配 Linux 驱动 */
fdt_setprop(dtb, node, "virtual_device", "", 1);
/* reg = {guest_iomem, iomem_size}(MMIO 基址 + 大小) */
args[0] = cpu_to_fdt32(vdev->guest_iomem);
args[1] = cpu_to_fdt32(vdev->iomem_size);
fdt_setprop(dtb, node, "reg", (void *)args, 2 * sizeof(uint32_t));
/* interrupts = {0, gvm_irq - 32, 4}(一个 SPI 中断) */

这个节点告诉 guest 内核:地址 0x1fe00000 + index*0x1000VM_VIRTIO_IOMEM_BASE)处有一个 virtio-mmio 设备,中断号是 gvm_irqguest 内核和 mvm 都靠同一个 vring/寄存器文件通信,而设备”长什么样”由 dtb 描述。

第 2 步:Linux 内核探测并初始化。 guest 的 Linux 自带 drivers/virtio/virtio_mmio.c 驱动,按平台设备匹配 "virtio,mmio" 后走标准 virtio 初始化流程:

phase8_virtio_guest

流程要点:

  1. 探测virtio_mmio_probe 读寄存器文件确认设备存在——读 MagicValue(应为 0x74726976)、VersionDeviceIDVendorID。这些是读操作,而寄存器文件映射进 guest 是 VM_IO|VM_RO(第 7.3 节),读直接命中共享内存,不 trap
  2. 特性协商:写 Status=ACKNOWLEDGE → 读 HostFeatures(设备能力)→ 写 GuestFeatures(协商结果)→ 写 Status=DRIVER_OK。这些写操作 trap 到 hypervisor,经 virtio_mmio_write 转发给 mvm 后端(第 7.5 节)。
  3. 建队列:guest 在自己的普通内存里分配 vring 三区(desc/avail/used),把地址写进 QueueDesc/Avail/Used 寄存器,写 QueueReady=1 告知设备”队列就绪”。
  4. 使用:此后每次 I/O,guest 把请求写进 vring 的 desc/avail 环,写 QueueNotify 通知;mvm 后端处理完写 used 环并发中断,guest 中断处理里读 used 环拿结果——这就是第 7.7 节的完整链路。

关键理解:guest 侧不需要任何 minos 专用代码——它只是一个标准 Linux virtio-mmio 设备。三方里只有 mvm 和 hypervisor 在”造假设备”(dtb 节点 + 共享寄存器文件 + 事件转发),guest 完全按标准流程使用。

补充(vring 地址的换算):

  1. 寄存器里存的是 guest 的 IPA——guest 视角的”物理地址”,实际是 Stage2 翻译的输入(Phase 4)。
  2. mvm 能直接读写 vring,因为同一块物理页被同时映射进三个地址空间:guest 的 Stage2、VM0 的 Stage2、mvm 进程页表(第 5.4 节建 guest 时映射好)。
  3. gpa_to_hvm_va 只算偏移vm->mmap + (gpa - mem_start),数据仍在那块物理页上,两端用不同地址称呼它,无数据搬运。

7.3 寄存器文件与共享内存

virtio-mmio 规范定义了一组寄存器(generic/include/generic/virtio_mmio.h),hypervisor 把这组寄存器放在一块共享物理内存里,同时映射给 guest 和 VM0。关键寄存器(virtio_mmio.h:8-48):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
virtio-mmio register file (4K, shared between guest and VM0)
┌────────────┬──────┬──────────────────────────────────────┐
│ offset │ size │ meaning │
├────────────┼──────┼──────────────────────────────────────┤
│ 0x000 │ 4 │ MagicValue (0x74726976) │
│ 0x004 │ 4 │ Version │
│ 0x008 │ 4 │ DeviceID │
│ 0x00c │ 4 │ VendorID │
│ 0x010/0x014│ 4 │ HostFeatures / HostFeaturesSel │
│ 0x020/0x024│ 4 │ GuestFeatures / GuestFeaturesSel │
│ 0x030 │ 4 │ QueueSel │
│ 0x034/0x038│ 4 │ QueueNumMax / QueueNum │
│ 0x044 │ 4 │ QueueReady │
│ 0x050 │ 4 │ QueueNotify (queue index) │
│ 0x060/0x064│ 4 │ InterruptStatus / InterruptACK │
│ 0x070 │ 4 │ Status │
│ 0x080-0x0a4│ 4 │ QueueDesc/Avail/Used Low/High │
│ 0x100 │ 4 │ Config (device specific) │
└────────────┴──────┴──────────────────────────────────────┘

各寄存器用途(含义):QueueNotify 写队列号触发通知;Status 承载设备状态机(0x070);QueueDesc/Avail/Used 三组寄存器保存 vring 三个区的地址(0x080-0x0a4);Config 是设备配置区(0x100),如块设备的容量。

vring 本身不在寄存器文件里,而是由 guest 分配在自己的普通内存中(通过 QueueDesc/Avail/Used 寄存器把地址告诉设备)。后端通过第 5.4 节的 gpa_to_hvm_va 把 vring 地址从 guest IPA 换算成 mvm 虚拟地址,直接读写。

7.4 virtio_mmio_init:共享内存的建立

virtio_mmio_initvirtio_mmio.c:224-262)由 HVC_VM_VIRTIO_MMIO_INIT(第 4.3 节)触发,参数是 guest 侧的基址 gbase

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
int virtio_mmio_init(struct vm *vm, unsigned long gbase,
size_t size, unsigned long *hbase)
{
void *iomem = NULL;
unsigned long hva;

size = PAGE_BALIGN(size);
iomem = alloc_shmem(PAGE_NR(size)); /* ① 分配物理共享内存 */
if (!iomem)
return -ENOMEM;
memset(iomem, 0, size);

hva = create_hvm_shmem_map(vm, (unsigned long)iomem, size); /* ② 映射进 VM0 */
if (hva == BAD_ADDRESS) {
free_shmem(iomem);
return -ENOMEM;
}

if (create_guest_mapping(&vm->mm, gbase, (unsigned long)iomem,
size, VM_IO | VM_RO)) { /* ③ 映射进 guest */
free_shmem(iomem);
return -ENOMEM;
}

*hbase = hva; /* ④ 把 VM0 侧地址回传给调用方 */
return 0;
}

同一块物理内存,两个视角

  • 映射进 guest 的 gbase(guest 读寄存器直接命中,写会 trap);
  • 映射进 VM0 的 hva(mvm 通过 vdev_map_iomemhvm_map_iomemmvm.c:285-297)再 mmap 进自己进程)。

7.5 寄存器写转发:virtio_mmio_write

guest 写寄存器会 trap 到 EL2,vdev_mmio_emulation 命中 virtio vdev 后调 virtio_mmio_writevirtio_mmio.c:40-147)。它分三类处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
switch (offset) {
case VIRTIO_MMIO_HOST_FEATURES_SEL: /* ① 直接读写共享内存 */
tmp = ioread32(iomem + VIRTIO_MMIO_HOST_FEATURE0 + value * 4);
iowrite32(tmp, iomem + VIRTIO_MMIO_HOST_FEATURES);
break;
...
case VIRTIO_MMIO_QUEUE_NOTIFY: /* ② 只转发:队列就绪通知 */
trap_mmio_write_nonblock(address, write_value);
break;
case VIRTIO_MMIO_STATUS: /* ③ 先写共享内存再转发 */
tmp = ioread32(iomem + VIRTIO_MMIO_STATUS);
value = value - tmp;
*write_value = value;
iowrite32(value, iomem + VIRTIO_MMIO_STATUS);
trap_mmio_write(address, write_value);
break;
...
case VIRTIO_MMIO_QUEUE_READY: /* ④ 转发时带上队列号 */
value = ioread32(iomem + VIRTIO_MMIO_QUEUE_SEL);
*write_value = value;
trap_mmio_write(address, write_value);
break;

三类动作(代码里的 ①②③④ 按行为归类):

  • 纯共享内存读写(① HOST_FEATURES_SELQUEUE_SEL/QUEUE_NUM 等):直接把值写进 dev->iomem(共享内存),VM0 后端能直接看到,不需要额外通知;
  • 只转发(② QUEUE_NOTIFY):用 trap_mmio_write_nonblockvmcs.h:79-84nonblock=1)异步通知 VM0”有队列要处理”,不等应答;
  • 改值后转发(③④ STATUS/QUEUE_READY):STATUS 算出本次新增的状态位(value - tmp)写回共享内存再转发;QUEUE_READYQUEUE_SEL 的值替换写入值再转发,让后端知道”哪个队列就绪”。两者都用阻塞版 trap_mmio_write 通知并等待处理结果。

trap_mmio_write/trap_mmio_write_nonblockvmcs.h:72-84)是第 6.4 节 __vcpu_trap 的薄封装,把请求经 vmcs 投递给 VM0。**hypervisor 在这里不实现设备逻辑,只做”地址/值的中转”**——真正的设备在 mvm。

7.6 VM0 后端的 virtio 实现

mvm 里 virtio 后端分为两层:通用 virtio 框架(tools/mvm/devices/virtio/virtio.c)+ 具体设备(virtio_block.c/virtio_net.c/virtio_console.c)。本节看它的结构接口;一条完整的数据链路见第 7.7 节的时序图。

设备初始化(如 virtio_blk_initvirtio_block.c:286-397)做三件事:

  1. virtio_device_initvirtio.c:466-530)→ hv_create_virtio_devicevirtio.c:68-89)算 guest 侧基址(VM_VIRTIO_IOMEM_BASE + index * 0x1000gvm.h:60-61),发 IOCTL_VIRTIO_MMIO_INIT 触发第 7.4 节的共享内存建立,拿到 VM0 侧地址;
  2. __virtio_vdev_initvirtio.c:390-418)往寄存器文件里写 MagicValue/Version/DeviceID/VendorID/QueueNumMax,申请设备中断 vdev_alloc_irqvdev.c:157-160);
  3. 初始化 vring 的三个指针区。

初始化时序(从 mvm 创建设备到共享寄存器文件就绪):

phase8_virtio_beinit

MMIO 事件入口:mvm 的 vcpu_handle_mmiomvm.c:427-453)按 guest 地址匹配 vdev,调 vdev->ops->event(virtio 的 virtio_handle_mmiovirtio.c:681-689)→ virtio_mmio_writevirtio.c:653-679)。后端只关心三个寄存器:

寄存器 后端处理 作用
STATUS virtio_status_event(virtio.c:532) 设备状态变化,FEATURES_OK 时读 acked_features 做特性协商
QUEUE_READY virtio_buffer_event(virtio.c:593) 读 vring 三个区地址,gpa_to_hvm_va 换算成 mvm 进程 VA
QUEUE_NOTIFY virtio_queue_event(virtio.c:565) 队列可处理,调 queue->callback(如 virtio_blk_notify

其中 virtio_buffer_event 用第 5.4 节的 gpa_to_hvm_vaQueueDesc/Avail/Used 从 guest IPA 换算成 mvm 进程地址——vring 描述符从此在 mvm 侧直接可见。之后各设备就在这块共享 vring 上取请求、做 I/O、回写 used 环,这就是第 7.7 节时序图展示的完整过程。

7.7 一次块设备读写的完整链路

把第 5、6、7 章串起来,guest 发一次块读请求的完整旅程:

phase8_virtio_flow

完整链路分五段:

  1. guest 提交请求:guest 的 virtio-blk 驱动把”读扇区 X”写入 vring 的 desc/avail 环(这块内存是 guest 普通内存,无 trap),再写 QueueNotify 寄存器触发通知。
  2. hypervisor 转发QueueNotify 写 trap 到 EL2 → vdev_mmio_emulationvirtio_mmio_writeQUEUE_NOTIFY 分支 → trap_mmio_write_nonblock → vmcs 写请求、host_index++、发 virq 通知 VM0。
  3. VM0 处理:mvm vcpu 线程 epoll 醒来 → handle_vcpu_eventvcpu_handle_mmiovirtio_queue_eventvirtio_blk_notify → 从共享 vring 取请求 → blockif_read 异步读镜像文件。
  4. 完成后通知:磁盘线程读完数据写进 guest 内存 → virtio_blk_done 写状态字节 → virtq_add_used_and_signal 更新 used 环 → virtio_send_irqtools/mvm/include/minos/virtio.h:170-181)置 InterruptStatusvdev_send_irqsend_virq_to_vm → ioctl IOCTL_SEND_VIRQminos.c:502-504)。
  5. 回到 guest:驱动 hvc_send_virqHVC_VM_SEND_VIRQhypercall.c:69-72)→ hypervisor send_virq_to_vm 注入中断 → guest 中断处理里读 used 环拿结果。

对照学习计划里的实验”跟踪 create_guest_vm 的完整数据流”和”新增一个 hypercall ID”,第 5 章与第 4 章给出了完整的对照路径。


8. 关键知识点

  1. 两条通道方向相反:VM0→hypervisor 用同步的 HVC hypercall(管理面);guest→VM0 用异步的 MMIO trap + vmcs(服务面)。virtio 把两者串联:guest 写寄存器(trap+vmcs 下去),VM0 处理完再经 HVC_VM_SEND_VIRQ 注入中断(hypercall 上来)。
  2. hypercall ID 复用了 SMCCC 位布局HVC_CALL_BASE(0xc0000000) + (STYPE << 24) + fn。分发只查 STYPE 位段(svc_service.c:40(svc_id & SVC_STYPE_MASK) >> 24),加新类别只需注册新 handler。
  3. 分发表由 section 收集构建DEFINE_HVC_HANDLER/DEFINE_SMC_HANDLERstruct svc_desc 放进 __hvc_handler/__smc_handler section,svc_service_init 展开填进 hvc_descs/smc_descs 表(下标 = STYPE)。
  4. PSCI 复用同一分发框架:PSCI 标准函数 ID 自带 STYPE=0x04std_smc_handler 注册在 SVC_STYPE_STDSMC,把 guest 的 CPU 电源操作翻译成 vcpu_power_on/vcpu_off 等。
  5. VM 管理 hypercall 只许 VM0 调用vm_hvc_handler 开头 vm_is_host_vm(get_current_vm()) 检查,非 VM0 直接 panic——这是”VM0 中心模型”的安全边界。
  6. create_guest_vm 四步copy_from_guest(拷 vmtag)→ vmtag_check_and_config(校验/亲和性)→ create_vm(vmid + vCPU)→ guest_mm_init(stage2)。创建与上电分离(HVC_VM_POWER_UP 才让 vCPU 上线)。
  7. guest 内存以共享物理页暴露给 VM0HVC_VM_MMAPvm_mmap 在 VM0 stage2 里映射 guest 物理页;mvm 再 mmap 到自己进程。gpa_to_hvm_va 是两端地址换算的关键。
  8. vdev 双层模型:能快速处理的设备留 hypervisor(vGIC/vtimer),费资源的走 vdev_mmio_emulation 未命中路径 trap_vcpu 下沉到 VM0 用户态。
  9. virtio 寄存器文件是共享物理页virtio_mmio_init 一块内存映射进 guest(VM_IO|VM_RO)和 VM0;guest 读直接命中、写 trap,hypervisor 只做寄存器值的中转,设备逻辑全在 mvm。vring 在 guest 普通内存,后端用 gpa_to_hvm_va 直接访问。

8.1 代码文件关联速查表

本文各章讲到的机制主要落在几个关键文件(行号对应正文引用的关键位置):

文件 在本文的作用 关键位置
arch/aarch64/include/asm/svccc.h ID 位布局 + handler 注册宏SVC_STYPE_MASKDEFINE_HVC/SMC_HANDLERSVC_RET1 svccc.h:17/78/87
arch/aarch64/virt/svc_service.c 分发核心do_svc_handler 查表、parse_svc_desc 建表 svc_service.c:29/59
arch/aarch64/virt/smc_service.c PSCI 虚拟化std_smc_handler smc_service.c:24
arch/aarch64/virt/trap.c 异常入口aarch64_hypercall_handlerdataabort_tfl_handler、EC 分发 trap.c:320/241/392
include/virt/hypercall.h hypercall ID 编码HVC_CALL_NUMHVC_VM_* hypercall.h:10/20
virt/hypercall.c VM 管理 hypercall 入口vm_hvc_handler/misc_hvc_handler hypercall.c:28/122
virt/vm.c VM 生命周期create_guest_vmcreate_vm_mmapcreate_vm vm.c:593/578/885
virt/vmm.c 内存映射vm_mmapcreate_hvm_shmem_mapcopy_from_guest vmm.c:589/483/500
virt/vdev.c vdev 框架vdev_mmio_emulationvdev_add_iomem_range vdev.c:198/102
virt/vmcs.c / include/virt/vmcs.h 共享内存协议__vcpu_traptrap_mmio_write vmcs.c:24 / vmcs.h:72
virt/virtio_mmio.c hypervisor 侧 virtiovirtio_mmio_writevirtio_mmio_init virtio_mmio.c:40/224
generic/include/generic/virtio_mmio.h virtio 寄存器定义 virtio_mmio.h:8
generic/minos-linux-driver/minos.c VM0 内核驱动:ioctl→hypercall 翻译、设备节点 minos.c:951/478/1106
generic/minos-linux-driver/minos_hypercall.h hypercall 封装__minos_hvchvc_vm_create minos_hypercall.h:50/73
tools/mvm/main/mvm.c mvm 主流程create_new_vmmap_vm_memoryhandle_vcpu_event mvm.c:121/70/482
tools/mvm/devices/virtio/virtio.c mvm 侧 virtio 框架virtio_buffer_eventvirtio_mmio_write virtio.c:593/653
tools/mvm/devices/virtio/virtio_block.c 块设备后端virtio_blk_notifyvirtio_blk_proc virtio_block.c:240/150

其余辅助文件(virt/vm_pm.cvirt/os/os.ctools/mvm/devices/vdev.ctools/mvm/main/mevent.c 等)在正文引用处标注了行号,需要时按引用回查即可。