HyperEnclave启动和初始化流程

HyperEnclave启动和初始化流程
XR概述
最近在看蚂蚁的HyperEnclave,很好奇它是怎么实现的。其实HyperEnclave源码量并不多,非常方便我们去分析、学习。
本文将结合源码,分析HyperEnclave的启动和初始化流程。
启动整体流程图
sequenceDiagram
participant Driver as 内核驱动
participant Entry as entry函数
participant Main as main函数
participant Primary as Primary CPU
participant Secondary as Secondary CPU
participant VMM as VMM激活
Driver->>Entry: 调用entry(cpu_id, linux_sp)
Entry->>Main: 调用main(cpu_id, linux_sp)
Note over Main: 第一步:CPU同步和角色确定
Main->>Main: 获取CPU数据 PerCpu::from_id_mut(cpu_id)
Main->>Main: 获取在线CPU数量 HvHeader::get().online_cpus
Main->>Main: 确定Primary CPU ENTERED_CPUS.fetch_add(1)
Main->>Main: 等待所有CPU进入 wait_for_other_completed
Note over Main: 第二步:早期初始化阶段
alt Primary CPU
Main->>Primary: primary_init_early()
Primary->>Primary: 初始化日志系统 logging::init()
Primary->>Primary: 检查CPU数量限制 cpumask::check_max_cpus()
Primary->>Primary: 打印系统配置信息
Primary->>Primary: 初始化内存回收模块 reclaim::init()
Primary->>Primary: 初始化内存管理 memory::init()
Primary->>Primary: 初始化内存单元管理 cell::init()
Primary->>Primary: 标记early初始化完成 INIT_EARLY_OK.store(1)
else Secondary CPU
Main->>Secondary: 等待early初始化完成 wait_for_other_completed(&INIT_EARLY_OK, 1)
end
Note over Main: 第三步:CPU初始化
Main->>Main: CPU初始化 cpu_data.init(cpu_id, linux_sp, &cell::ROOT_CELL)
Main->>Main: 打印CPU初始化完成信息
Main->>Main: 递增已初始化CPU计数 INITED_CPUS.fetch_add(1)
Main->>Main: 等待所有CPU初始化完成 wait_for_other_completed(&INITED_CPUS, online_cpus)
Note over Main: 第四步:晚期初始化阶段
alt Primary CPU
Main->>Primary: primary_init_late()
Primary->>Primary: 初始化hypervisor日志 logging::hhbox_init()
Primary->>Primary: 版本检查 LIBTPM_VERSION vs RUST_HYPERVISOR_VERSION
alt 版本不匹配
Primary-->>Main: 返回EINVAL错误
else 版本匹配
Primary->>Primary: 打印版本信息
Primary->>Primary: 初始化IOMMU iommu::init()
Primary->>Primary: 初始化TPM和加密模块 tc::tc_init()
alt TPM初始化失败
Primary-->>Main: 返回EIO错误
else TPM初始化成功
Primary->>Primary: 标记late初始化完成 INIT_LATE_OK.store(1)
end
end
else Secondary CPU
Main->>Secondary: 等待late初始化完成 wait_for_other_completed(&INIT_LATE_OK, 1)
end
Note over Main: 第五步:激活VMM
alt 初始化成功
Main->>VMM: activate_vmm()
VMM->>VMM: 切换到hypervisor模式
VMM-->>Entry: 返回成功代码(0)
else 初始化失败
Note over Main: 任何步骤失败
Main-->>Entry: 返回错误代码
end
Entry->>Entry: restore_states(cpu_id)
Entry-->>Driver: 返回状态代码
Note over Driver: 成功返回0,失败返回错误代码
HyperEnclave的入口是 main.rs文件中的entry函数。
核心函数分析
Entry函数 - 系统入口点
1 | extern "sysv64" fn entry(cpu_id: usize, linux_sp: usize) -> i32 { |
关键点:
这是hypervisor的入口点,由内核驱动调用
参数:cpu_id(CPU ID)和linux_sp(Linux栈指针)
错误处理:如果main失败,记录错误代码
状态恢复:如果main失败,还会调用restore_states,恢复Linux状态(关闭IOMMU等硬件资源)
参数说明
cpu_id - CPU标识符
含义:物理CPU核心的标识符,表示当前正在启动hypervisor的CPU核心编号。通常从0开始,到系统CPU核心数-1
在hypervisor中用途:
用于标识每个CPU的PerCpu结构
在多CPU系统中进行协调和同步
确定哪个CPU是Primary CPU
linux_sp - Linux栈指针
含义:
Linux内核栈指针:保存Linux内核在调用hypervisor时的栈指针位置
上下文保存:用于保存Linux的执行上下文,以便后续恢复
栈切换:hypervisor需要切换到自己的栈,但需要记住Linux的栈位置
用途:保存Linux的执行上下文,确保hypervisor退出时能正确恢复Linux状态。
entry函数,确保了hypervisor能够安全地接管CPU控制权,同时也是hypervisor安全退出机制的关键组成部分,确保了即使在启动失败的情况下,系统也能安全地恢复到Linux状态。
Main函数 - 核心初始化逻辑
1 | fn main(cpu_id: usize, linux_sp: usize) -> HvResult |
参数说明:
cpu_id: CPU核心标识符
linux_sp: Linux栈指针,用于保存Linux执行上下文
返回值:HvResult(成功为Ok(()),失败为Err(HvError))
第一步:CPU数据获取和同步
1 | // 第1步:获取CPU数据和系统信息 |
详细分析:
- PerCpu::from_id_mut(cpu_id)
获取当前CPU的PerCpu数据结构
每个CPU都有独立的PerCpu实例
- HvHeader::get().online_cpus
获取系统中在线CPU的总数
用于后续的同步等待
- ENTERED_CPUS.fetch_add(1, Ordering::SeqCst) == 0
原子操作,递增已进入的CPU计数
第一个进入的CPU(返回0)被标记为Primary CPU
其他CPU被标记为Secondary CPU
- wait_for_other_completed(&ENTERED_CPUS, online_cpus)
等待所有CPU都进入hypervisor
确保多CPU同步启动
第二步:早期初始化阶段
1 | // 第2步:早期初始化阶段 |
Primary CPU执行primary_init_early()
1 | fn primary_init_early() -> HvResult { |
Secondary CPU等待早期初始化完成
1 | wait_for_other_completed(&INIT_EARLY_OK, 1)? |
第三步:CPU初始化
1 | // 第3步:CPU初始化 |
详细分析:
- cpu_data.init(cpu_id, linux_sp, &cell::ROOT_CELL)
初始化当前CPU的PerCpu结构
设置CPU状态、保存Linux上下文
初始化内存映射和VCPU
- INITED_CPUS.fetch_add(1, Ordering::SeqCst)
- 原子递增已初始化的CPU计数
- wait_for_other_completed(&INITED_CPUS, online_cpus)
- 等待所有CPU都完成初始化
第四步:晚期初始化阶段
1 | // 第4步:晚期初始化阶段 |
primary_init_late()函数分析:
1 | fn primary_init_late() -> HvResult { |
第五步:激活VMM
1 | // 第5步:激活VMM |
activate_vmm()的作用:
激活虚拟化硬件(Intel VMX或AMD SVM)
切换到hypervisor模式
开始处理虚拟化事件
底层实现机制
为什么看起来那么简单
我刚看的时候,感觉还是挺疑惑的,rust代码就这样就可以操作CPU了?其实刚刚我们看的Rust代码只是”胶水层”。实际上是有Rust代码,帮我们封装了 汇编指令与一些硬件虚拟化指令。
下面详细说下我们main函数入口,涉及的到的各种CPU操作底层是如何实现的。
CPU状态获取和同步操作
原子操作 - CPU计数
1 | // src/main.rs:171-172 |
底层实现:
1 | // 原子操作底层是CPU的LOCK前缀指令 |
CPU ID获取
1 | // src/main.rs:169 |
底层实现:
1 | // src/percpu.rs:65-72 |
内存映射和页表操作
内存映射设置
1 | // src/percpu.rs:100-115 |
底层实现:
1 | // 涉及页表操作,最终会执行: |
栈切换操作
1 | // src/percpu.rs:152 |
底层实现:
1 | // 直接操作栈指针寄存器 |
寄存器保存和恢复操作
Linux上下文保存
1 | // src/arch/x86_64/context.rs:128-135 |
底层实现:
1 | // 读取MSR寄存器 |
控制寄存器操作
1 | // src/arch/x86_64/context.rs:152-154 |
底层实现:
1 | // 读取控制寄存器 |
VMCS(Virtual Machine Control Structure)设置
VMCS初始化
1 | // src/arch/x86_64/intel/vcpu.rs:198-205 |
底层实现:
1 | // Intel VMX指令 |
Host状态设置
1 | // src/arch/x86_64/intel/vcpu.rs:207-235 |
底层实现:
1 | // 读取MSR寄存器 |
Guest状态设置
1 | // src/arch/x86_64/intel/vcpu.rs:247-289 |
底层实现:
1 | // 写入VMCS Guest字段 |
虚拟化启动操作
VCPU激活
1 | // src/arch/x86_64/intel/vcpu.rs:136-155 |
底层实现:
1 | // 设置寄存器 |
中断和异常处理设置
中断描述符表设置
1 | // src/arch/x86_64/context.rs:168-171 |
底层实现:
1 | // 加载GDT |
状态恢复操作
寄存器恢复
1 | // src/arch/x86_64/context.rs:175-225 |
底层实现:
1 | // 写入MSR寄存器 |
底层实现依赖总结
x86架构指令集
- MSR操作:
rdmsr/wrmsr指令 - 控制寄存器:
mov rax, cr0/mov cr0, rax等 - 段寄存器:
mov cs, selector等 - 页表操作:
mov rax, cr3、invlpg等
Intel VMX虚拟化指令
- VMCS操作:
vmclear、vmptrld、vmwrite、vmread - 虚拟化启动:
vmlaunch、vmresume - 虚拟化退出:
vmxoff
原子操作指令
- LOCK前缀:
lock add、lock xchg等 - 内存屏障:
mfence、sfence、lfence
内存管理指令
- 页表操作:
invlpg(刷新TLB) - 内存访问:直接内存读写操作
中断和异常处理
- 描述符表:
lgdt、lidt指令 - 中断控制:
cli、sti指令
这些操作都依赖于Intel x86架构的硬件支持,通过Rust的asm!宏直接执行汇编指令来实现对CPU的底层控制。













